spring缓存机制(EhCache配置方式)

一、SPRING与EhCache缓存的联系
调用一个缓存方法时会把该方法参数和返回结果作为一个键值对存放在缓存中,等到下次利用同样的参数来调用该方法时将不再执行该方法,而是直接从缓存中获取结果进行返回。所以在使用Spring Cache的时候我们要保证我们缓存的方法对于相同的方法参数要有相同的返回结果。

Spring为我们提供了几个注解来支持Spring Cache。其核心主要是@Cacheable和@CacheEvict。使用@Cacheable标记的方法在执行后Spring Cache将缓存其返回结果,而使用@CacheEvict标记的方法会在方法执行前或者执行后移除Spring Cache中的某些元素。
二、EhCache实现的配置

  1. 需要导入的jar包:ehcache-core-2.4.3.jar,slf4j-api-1.6.1.jar(该缓存工具所使用的日志工具)
  2. 在类加载路径下添加一个ehcache.xml配置文件
<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:noNamespaceSchemaLocation="ehcache.xsd"
         updateCheck="true" monitoring="autodetect"
         dynamicConfig="true">
    <diskStore path="java.io.tmpdir"/>
    <transactionManagerLookup class="net.sf.ehcache.transaction.manager.DefaultTransactionManagerLookup"
                              properties="jndiName=java:/TransactionManager" propertySeparator=";"/>
    <cacheManagerEventListenerFactory class="" properties=""/>
    <cacheManagerPeerListenerFactory
            class="net.sf.ehcache.distribution.RMICacheManagerPeerListenerFactory"/>
   <!-- 
    maxElementsInMemory:设置缓存中最多可放多少个对象
    eternal:设置缓存是否永久有效
    timeToIdleSeconds:设置缓存的对象在多少秒没被使用的被清除掉
    timeToLiveSeconds:设置对象在过期前可缓存多少秒
    -->
    <!--
        <diskStore>==========当内存缓存中对象数量超过maxElementsInMemory时,将缓存对象写到磁盘缓存中(需对象实现序列化接口)
        <diskStore path="">==用来配置磁盘缓存使用的物理路径,Ehcache磁盘缓存使用的文件后缀名是*.data和*.index
        name=================缓存名称,cache的唯一标识(ehcache会把这个cache放到HashMap里)
        maxElementsOnDisk====磁盘缓存中最多可以存放的元素数量,0表示无穷大
        maxElementsInMemory==内存缓存中最多可以存放的元素数量,若放入Cache中的元素超过这个数值,则有以下两种情况
                             1)若overflowToDisk=true,则会将Cache中多出的元素放入磁盘文件中
                             2)若overflowToDisk=false,则根据memoryStoreEvictionPolicy策略替换Cache中原有的元素
        eternal==============缓存中对象是否永久有效,即是否永驻内存,true时将忽略timeToIdleSeconds和timeToLiveSeconds
        timeToIdleSeconds====缓存数据在失效前的允许闲置时间(单位:秒),仅当eternal=false时使用,默认值是0表示可闲置时间无穷大,此为可选属性
                             即访问这个cache中元素的最大间隔时间,若超过这个时间没有访问此Cache中的某个元素,那么此元素将被从Cache中清除
        timeToLiveSeconds====缓存数据在失效前的允许存活时间(单位:秒),仅当eternal=false时使用,默认值是0表示可存活时间无穷大
                             即Cache中的某元素从创建到清楚的生存时间,也就是说从创建开始计时,当超过这个时间时,此元素将从Cache中清除
        overflowToDisk=======内存不足时,是否启用磁盘缓存(即内存中对象数量达到maxElementsInMemory时,Ehcache会将对象写到磁盘中)
                             会根据标签中path值查找对应的属性值,写入磁盘的文件会放在path文件夹下,文件的名称是cache的名称,后缀名是data
        diskPersistent=======是否持久化磁盘缓存,当这个属性的值为true时,系统在初始化时会在磁盘中查找文件名为cache名称,后缀名为index的文件
                             这个文件中存放了已经持久化在磁盘中的cache的index,找到后会把cache加载到内存
                             要想把cache真正持久化到磁盘,写程序时注意执行net.sf.ehcache.Cache.put(Element element)后要调用flush()方法
        diskExpiryThreadIntervalSeconds==磁盘缓存的清理线程运行间隔,默认是120秒
        diskSpoolBufferSizeMB============设置DiskStore(磁盘缓存)的缓存区大小,默认是30MB
        memoryStoreEvictionPolicy========内存存储与释放策略,即达到maxElementsInMemory限制时,Ehcache会根据指定策略清理内存
                                         共有三种策略,分别为LRU(最近最少使用)、LFU(最常用的)、FIFO(先进先出)
-->
    <defaultCache
            maxElementsInMemory="10000"         
            eternal="false"
            timeToIdleSeconds="120"
            timeToLiveSeconds="120"
            overflowToDisk="true"
            diskSpoolBufferSizeMB="30"
            maxElementsOnDisk="10000000"
            diskPersistent="false"
            diskExpiryThreadIntervalSeconds="120"
            memoryStoreEvictionPolicy="LRU"
            statistics="false"
            />
    <cache name="user1"
           maxElementsInMemory="10000"
           eternal="false"
           overflowToDisk="true"
           timeToIdleSeconds="300"
           timeToLiveSeconds="600"
      />
    <cache name="user2"
           maxElementsInMemory="10000"
           eternal="false"
           overflowToDisk="true"
           timeToIdleSeconds="300"
           timeToLiveSeconds="600"
      />
     <cache name="user3"
           maxElementsInMemory="10000"
           eternal="false"
           overflowToDisk="true"
           timeToIdleSeconds="300"
           timeToLiveSeconds="600"
      />
       <cache name="user4"
           maxElementsInMemory="10000"
           eternal="false"
           overflowToDisk="true"
           timeToIdleSeconds="300"
           timeToLiveSeconds="600"
      />
</ehcache>

3.spring使用EhCacheCacheManager作为Ehcache缓存实现缓存管理器,因此只要该对象在Spring容器中,它就可以作为缓存管理器使用,但EhCacheCacheManager底层要依赖一个net.sf.ehcache.CacheManager作为实际的缓存管理器。Spring配置文件如下:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:p="http://www.springframework.org/schema/p"
       xmlns:cache="http://www.springframework.org/schema/cache"  
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
           http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
           http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
            http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd
            http://www.springframework.org/schema/cache  
            http://www.springframework.org/schema/cache/spring-cache-3.2.xsd"     
      default-lazy-init="true" default-merge="true">
    <context:annotation-config/>
    <!-- 自动扫描指定包及其子包下的所有bean类 -->
    <context:component-scan base-package="com.aspect.service,com.aspect.service.impl,com.home.bean,com.home.model,cache">
    <!-- 只是以Axe结尾的类当成Spring容器中的bean -->
    <!-- type:指定过滤器类型
         1.annotation:Annotation过滤器,该过滤器需要指定一个Annotation名,如lee.AnnotationTest
         2.assignable:类过滤器,该过滤器直接指定一个java类
         3.regex,正则表达式过滤器,指定过滤规则,使用如下
         4.aspectJ:AspectJ过滤器,如org.example.*Service+
     -->
    <!--<context:include-filter type="regex" expression=".*Axe"/>  -->
    <context:include-filter type="annotation" expression="org.aspectj.lang.annotation.Aspect"/>
    <context:exclude-filter type="regex" expression=".*TeacherConfig"/>
    <context:exclude-filter type="assignable" expression="org.springframework.context.support.ResourceBundleMessageSource"/>
    <context:exclude-filter type="regex" expression="com.home.model.C*"/>
    </context:component-scan>
     <!-- 启动@AspectJ支持 -->
    <aop:aspectj-autoproxy/>
    <!-- 启用缓存注解功能(请将其配置在Spring主配置文件中) -->  
     <cache:annotation-driven cache-manager="cacheManager"/>  
    <!-- 国际化配置开始 -->
    <bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource" lazy-init="true">
       <!-- 驱动spring调用messageSource bean的setBasenems()方法,该方法需要一个数组参数,使用list元素配置多个数组元素 -->
      <property name="basenames">
            <list>
                <value>message</value>
            </list>
            <!-- 如果有多个资源文件,全部列在此处 -->
       </property></bean>
    <bean id="persons" class="com.home.bean.Persons"></bean>
    <!-- 国际化配置结束 -->
    <!-- 数据库连接池 -->
    <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close" >
        <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
        <property name="url" value="jdbc:mysql://localhost:3306/onlinebookshop"/>
        <property name="username" value="root"/>
        <property name="password" value=""/>
    </bean>
    <!-- 有了这个bean的声明,spring才会去扫描@Autowired注解,@Autowired也才有用 -->
    <bean class="org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor"/>
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"/><!--  -->
    </bean>
     <!-- 缓存配置 -->  
        <!-- 启用缓存注解功能(请将其配置在Spring主配置文件中) -->  
        <cache:annotation-driven cache-manager="cacheManager"/>  
        <!-- Spring自己的基于java.util.concurrent.ConcurrentHashMap实现的缓存管理器(该功能是从Spring3.1开始提供的) -->  
        <!--   
        <bean id="cacheManager" class="org.springframework.cache.support.SimpleCacheManager">  
            <property name="caches">  
                <set>  
                    <bean name="myCache" class="org.springframework.cache.concurrent.ConcurrentMapCacheFactoryBean"/>  
                </set>  
            </property>  
        </bean>  
         -->  
        <!-- 若只想使用Spring自身提供的缓存器,则注释掉下面的两个关于Ehcache配置的bean,并启用上面的SimpleCacheManager即可 -->  
        <!-- Spring提供的基于的Ehcache实现的缓存管理器 -->  
        <bean id="cacheManagerFactory" class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean">  
            <property name="configLocation" value="classpath:ehcache.xml"/>  
        </bean>  
        <bean id="cacheManager" class="org.springframework.cache.ehcache.EhCacheCacheManager">  
            <property name="cacheManager" ref="cacheManagerFactory"/>  
        </bean> 

三、缓存注解的说明

1.@Cacheable
可以标记在一个方法上,也可以标记在一个类上。当标记在一个方法上时表示该方法是支持缓存的,当标记在一个类上时则表示该类所有的方法都是支持缓存的。对于一个支持缓存的方法,Spring会在其被调用后将其返回值缓存起来,以保证下次利用同样的参数来执行该方法时可以直接从缓存中获取结果,而不需要再次执行该方法。Spring在缓存方法的返回值时是以键值对进行缓存的,值就是方法的返回结果,至于键的话,Spring又支持两种策略,默认策略和自定义策略。
需要注意的是当一个支持缓存的方法在对象内部被调用时是不会触发缓存功能的。@Cacheable可以指定三个属性,value、key和condition。

2.@CachePut
在支持Spring Cache的环境下,对于使用@Cacheable标注的方法,Spring在每次执行前都会检查Cache中是否存在相同key的缓存元素,如果存在就不再执行该方法,而是直接从缓存中获取结果进行返回,否则才会执行并将返回结果存入指定的缓存中。而@CachePut也可以声明一个方法支持缓存功能。与@Cacheable不同的是使用@CachePut标注的方法在执行前不会去检查缓存中是否存在之前执行过的结果,而是每次都会执行该方法,并将执行结果以键值对的形式存入指定的缓存中。
@CachePut也可以标注在类上和方法上。使用@CachePut时我们可以指定的属性跟@Cacheable是一样的。
3.@CacheEvict
@CacheEvict是用来标注在需要清除缓存元素的方法或类上的。当标记在一个类上时表示其中所有的方法的执行都会触发缓存的清除操作。@CacheEvict可以指定的属性有value、key、condition、allEntries和beforeInvocation。其中value、key和condition的语义与@Cacheable对应的属性类似。即value表示清除操作是发生在哪些Cache上的(对应Cache的名称);key表示需要清除的是哪个key,如未指定则会使用默认策略生成的key;condition表示清除操作发生的条件。
4.主要属性如下:
key:缓存的Key,当我们没有指定该属性时,Spring将使用默认策略生成key(表示使用方法的参数类型及参数值作为key),key属性是用来指定Spring缓存方法的返回结果时对应的key的。该属性支持SpringEL表达式。我们还可以自定义策略:自定义策略是指我们可以通过Spring的EL表达式来指定我们的key。这里的EL表达式可以使用方法参数及它们对应的属性。使用方法参数时我们可以直接使用“#参数名”或者“#p参数index”
key的生成策略有两种:一种是默认策略,一种是自定义策略
¹默认的key生成策略是通过KeyGenerator生成的,其默认策略如下:
1.如果方法没有参数,则使用0作为key。
2.如果只有一个参数的话则使用该参数作为key。
3.如果参数多余一个的话则使用所有参数的hashCode作为key
²自定义策略是指我们可以通过Spring的EL表达式来指定我们的key。这里的EL表达式可以使用方法参数及它们对应的属性。使用方法参数时我们可以直接使用“#参数名”或者“#p参数index

condition:有的时候我们可能并不希望缓存一个方法所有的返回结果。通过condition属性可以实现这一功能。condition属性默认为空,表示将缓存所有的调用情形。其值是通过SpringEL表达式来指定的,当为true时表示进行缓存处理;当为false时表示不进行缓存处理,即每次调用该方法时该方法都会执行一次。如下示例表示只有当user的id为偶数时才会进行缓存
allEntries:是boolean类型,表示是否需要清除缓存中的所有元素。默认为false,表示不需要。当指定了allEntries为true时,Spring Cache将忽略指定的key。有的时候我们需要Cache一下清除所有的元素,这比一个一个清除元素更有效率。
beforeInvocation:
清除操作默认是在对应方法成功执行之后触发的,即方法如果因为抛出异常而未能成功返回时也不会触发清除操作。使用beforeInvocation可以改变触发清除操作的时间,当我们指定该属性值为true时,Spring会在调用该方法之前清除缓存中的指定元素。
@CacheEvict(value=”users”, beforeInvocation=true)
public void delete(Integer id) {
System.out.println(“delete user by id: ” + id);
}
注意我们也可以使用ehcache的去除策略最近使用(LRU)”策略,其它还有先入先出FIFO,最少使用LFU,较少使用LRU
基于注解配置:
配置Spring对基于注解的Cache的支持,首先我们需要在Spring的配置文件中引入cache命名空间,其次通过就可以启用Spring对基于注解的Cache的支持。
有一个mode属性,可选值有proxy和aspectj。默认是使用proxy。当mode为proxy时,只有缓存方法在外部被调用的时候Spring Cache才会发生作用,这也就意味着如果一个缓存方法在其声明对象内部被调用时Spring Cache是不会发生作用的。而mode为aspectj时就不会有这种问题。另外使用proxy时,只有public方法上的@Cacheable等标注才会起作用,如果需要非public方法上的方法也可以使用Spring Cache时把mode设置为aspectj。此外,还可以指定一个proxy-target-class属性,表示是否要代理class,默认为false。我们前面提到的@Cacheable、@cacheEvict等也可以标注在接口上,这对于基于接口的代理来说是没有什么问题的,但是需要注意的是当我们设置proxy-target-class为true或者mode为aspectj时,是直接基于class进行操作的,定义在接口上的@Cacheable等Cache注解不会被识别到,那对应的Spring Cache也不会起作用了。
需要注意的是只会去寻找定义在同一个ApplicationContext下的@Cacheable等缓存注解。

四、使用实例


 1. 封装一个实体类
 package cache;
public class User {
    private String name;
    private int age;
    public User(String name,int age){
        this.name=name;
        this.age=age;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
}
2、接口
package cache;
public interface UserService {
    public User getUserByNameAndAge(String name,int age);
    public User getAnothreUser(String name,int age);
    public User getAnotherUser(String name,int age);
    public User getKeyValue(String name,int age);
    public User getKeyValues(String name,int age);
    public User getCondition(String name,int age);
    public User getConditions(String name,int age);
    public void evictUser(String name,int age);
    public void evictAll();
}

 1. 类级别的缓存
 package cache;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
/**
 * 类级别的缓存
 */
/*
 * 当condition指定的条件为true时,spring的缓存机制才会执行缓存;当unless指定的条件为true时,spring的缓存机制就不执行缓存
 */
@Service("userService")
@Cacheable(value="user1",condition="#age<100")
public class UserServiceImpl implements UserService{
    public User getUserByNameAndAge(String name, int age) {
        // TODO Auto-generated method stub
        System.out.println("------正在执行getUserByNameAndAge方法-----");
        return new User(name,age);
    }
    public User getAnothreUser(String name, int age) {
        // TODO Auto-generated method stub
        System.out.println("---------正在执行getAnothreUser方法------");
        return new User(name, age);
    }
    public User getAnotherUser(String name, int age) {
        return null;
    }
    public User getKeyValue(String name, int age) {
        System.out.println("------正在执行getKeyValue---");
        return new User(name, age);
    }
    public User getKeyValues(String name, int age) {
        System.out.println("------正在执行getKeyValues---");
        return new User(name, age);
    }
    public User getCondition(String name, int age) {
        // TODO Auto-generated method stub
        System.out.println("--------正在执行getCondition--------");
        return new User(name, age);
    }
    public User getConditions(String name, int age) {
        // TODO Auto-generated method stub
        System.out.println("--------正在执行getConditions--------");
        return new User(name, age);
    }
    public void evictUser(String name, int age) {
        // TODO Auto-generated method stub

    }
    public void evictAll() {
        // TODO Auto-generated method stub

    }
}
package spring;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.FileSystemXmlApplicationContext;
import cache.User;
import cache.UserService;
public class CacheTest {
    public static void main(String[] args) {
        ApplicationContext fsc=new FileSystemXmlApplicationContext("classpath:applicationContext.xml");
        UserService us=fsc.getBean("userService",UserService.class);
        //第一次调用us对象的方法时会执行该方法,并缓存方法的结果
        User u1=us.getUserByNameAndAge("xieyongxue",24);
        //第二次调用us对象的方法时直接利用缓存的数据,并不真正执行该方法
        User u2=us.getAnothreUser("xieyongxue", 24);
        System.out.println(u1==u2);//输出true
    }
}

2、方法级别的缓存
package cache;

import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Component;
/**
 * 通过指定方法缓存
 * @author xieyongxue
 *
 */
@Component("personService")
public class PersonServiceImpl implements UserService{
    @Cacheable(value="user1")
    public User getUserByNameAndAge(String name, int age) {
        // TODO Auto-generated method stub
        System.out.println("------正在执行getUserByNameAndAge()方法-------");
        return new User(name, age);
    }
    @Cacheable(value="user2")
    public User getAnothreUser(String name, int age) {
        // TODO Auto-generated method stub
        System.out.println("------正在执行getAnothreUser()方法-------");
        return new User(name, age);
    }
    @Cacheable(value="user2")
    public User getAnotherUser(String name, int age){
        System.out.println("------正在执行getAnotherUser()方法-------");
        return new User(name, age);
    }
    //key:通过SpEL表达式显示指定缓存的KEY,这里指定以name作为参数作为缓存的key,这样只要调用的方法
    //具有相同的name参数,Spring缓存机制就会生效。
    @Cacheable(value="user3",key="#name")
    public User getKeyValue(String name,int age){
        System.out.println("------正在执行getKeyValue---");
        return new User(name, age);
    }
    @Cacheable(value="user3",key="#name")
    public User getKeyValues(String name, int age) {
        // TODO Auto-generated method stub
        System.out.println("------正在执行只getKeyValues---");
        return new User(name, age);
    }

    @Cacheable(value="user4",condition="#age<100")
    public User getCondition(String name, int age) {
        // TODO Auto-generated method stub
        System.out.println("--------正在执行getCondition--------");
        return new User(name, age);
    }
    @Cacheable(value="user4",condition="#age<100")
    public User getConditions(String name, int age) {
        // TODO Auto-generated method stub
        System.out.println("--------正在执行getConditions--------");
        return new User(name, age);
    }
    @CacheEvict(value="user4")
    public void evictUser(String name,int age){
        System.out.println("---正在清空--"+name+","+age+"对应的缓存--");
    }
    @CacheEvict(value="user3",allEntries=true)
    public void evictAll(){
        System.out.println("--正在清空所有缓存---");
    }
}

package spring;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.FileSystemXmlApplicationContext;
import cache.User;
import cache.UserService;
public class CacheTest2 {
    public static void main(String[] args) {
        ApplicationContext fsc=new FileSystemXmlApplicationContext("classpath:applicationContext.xml");
        UserService us=fsc.getBean("personService",UserService.class);
        //第一次调用us对象的方法时会执行该方法,并缓存方法的结果
        User u1=us.getUserByNameAndAge("xieyongxue",24);
        //由于getAnothreUser()方法使用另一个缓存区,因此无法使用getUserByNameAndAge()缓存区中的数据
        User u2=us.getAnothreUser("xieyongxue", 24);
        //第三次调用us对象方法会执行该方法,并缓存结构
        User u3=us.getAnotherUser("xieyongxue", 24);
        //us.getAnotherUser("xieyongxue", 24)已经执行过一次,故下面代码使用缓存
        User u4=us.getAnotherUser("xieyongxue", 24);
        System.out.println(u1==u2);//输出false
        System.out.println(u1==u3);//输出false
        System.out.println(u2==u3);//输出true
        System.out.println(u3==u4);//输出true
        System.out.println("##########以下为测试指定参数作为缓存的KEY################");
        /**
         * 以下为测试指定参数作为缓存的KEY
         */
        User u5=us.getKeyValue("xieyongxue", 24);
        User u6=us.getKeyValues("xieyongxue", 23);
        System.out.println(u5==u6);//输出true
        /**
         * 下面为指定condition条件作为true时缓存,spring的缓存机制才会执行缓存
         */
        System.out.println("当condition指定的条件为true时,spring的缓存机制才会执行缓存");
        User u7=us.getCondition("xieyongxue", 10);
        User u8=us.getConditions("xieyongxue",10);
        User u9=us.getConditions("xieyongxue",10);
        System.out.println(u7==u8);//输出true
        System.out.println(u7==u9);//输出true
        System.out.println(u8==u9);//输出true
        /**
         * 清除缓存
         */
        System.out.println("############清除缓存##########");
        us.evictUser("xieyongxue", 10);
        //由于清除了指定的
        User u10=us.getCondition("xieyongxue",10);
        User u11=us.getConditions("xieyongxue", 10);
        System.out.println(u7==u10);//输出false
        System.out.println(u7==u11);//输出false
    }
}
发布了91 篇原创文章 · 获赞 7 · 访问量 6万+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章