Spring 整合ehcache缓存方法和问题整理

前几天在spring中整合ehcache,整和很简单,就是遇到了一些问题,记录一下以免再次犯错。
这假设已经配置好了spring项目。以下是spring引入cache的主要配置。

spring cache 配置

spring boot 配置

如果使用 spring boot, 开启缓存很方便
pom 引入依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-cache</artifactId>
</dependency>
    <!-- ehcache -->
    <dependency>
      <groupId>net.sf.ehcache</groupId>
      <artifactId>ehcache</artifactId>
    </dependency>

在启动类增加启用缓存注解@EnableCaching

@EnableCaching //启用缓存

配置

#配置ehcache缓存
spring.cache.type=ehcache 
#指定ehcache配置文件
spring.cache.ehcache.config=classpath:ehcache/ehcache.xml

spring mvc 配置

我使用的是spring mvc,配置如下
pom引入依赖

<dependency>
  <groupId>net.sf.ehcache</groupId>
  <artifactId>ehcache</artifactId>
  <version>2.10.6</version>
  <type>pom</type>
</dependency>

<!--Spring对于缓存功能的抽象封装接口-->
<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-context-support</artifactId>
  <version>4.3.15.RELEASE</version>
</dependency>

在配置文件开启缓存,例如在 spring-context.xml中 添加以下内容

    <beans xmlns:aop="http://www.springframework.org/schema/aop"
    xmlns:cache="http://www.springframework.org/schema/cache"
    xsi:schemaLocation="http://www.springframework.org/schema/beans 
    http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
    http://www.springframework.org/schema/cache http://www.springframework.org/schema/cache/spring-cache.xsd">

    <!-- 启用缓存注解功能 -->
    <cache:annotation-driven cache-manager="ehcacheManager"/>

    <!-- cacheManager工厂类,指定ehcache.xml的位置 -->
    <bean id="ehcacheManagerFactory" class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean">
        <property name="configLocation" value="classpath:ehcache/ehcache.xml" />
    </bean>

    <!-- 声明cacheManager -->
    <bean id="ehcacheManager" class="org.springframework.cache.ehcache.EhCacheCacheManager">
        <property name="cacheManager" ref="ehcacheManagerFactory" />
        <property name="transactionAware" value="true"/>
    </bean>

ehcache 配置文件

接下来,我们需要在resources目录下增加ehcache的配置文件,新建文件ehcache/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"/>    
   <defaultCache
            maxEntriesLocalHeap="10000"
            eternal="false"
            overflowToDisk="false" 
            timeToIdleSeconds="120"
            timeToLiveSeconds="120"
            diskSpoolBufferSizeMB="30"
            maxEntriesLocalDisk="10000000"
            diskExpiryThreadIntervalSeconds="120"
            memoryStoreEvictionPolicy="LRU">
        <persistence strategy="localTempSwap"/>
    </defaultCache>

    <cache name="MY_CACHE"
           maxEntriesLocalHeap="50000"
           maxEntriesLocalDisk="10000000"
           eternal="false"
           diskSpoolBufferSizeMB="60"
           timeToIdleSeconds="3600"
           timeToLiveSeconds="3600"
           memoryStoreEvictionPolicy="LRU"
           transactionalMode="off">
        <persistence strategy="localTempSwap"/>
    </cache>   
</ehcache>

缓存测试

新建一个service,并在方法中使用Cacheable 注解,指定key就可以了。

@Service
public class CacheService {
    @Cacheable(value = "MY_CACHE" , key= "#name")
    public String testCache(String name) {
        System.out.println("缓存方法执行!");
        return "Hello " + name;
    }
}

测试缓存方法

    @Test
    public void test () {
        cacheService.testCache("jam");
        cacheService.testCache("jam");
    }

结果发现,使用了缓存,方法只输出一次,如果把@Cacheable注解注释掉,发现方法调用了两次。

要注意的问题

缓存service注入不要与mvc的Controller配置文件一起注入

由于 spring缓存 是使用aop 实现的,所有要确保cache的bean的aop是生效的,我遇到的问题是打包成jar包好,在另一个项目中调用缓存没启用。
排查后发现 新的项目中beans注入了两次,第一次是在注入mcv的Controller中注入,没有aop,然后才是另个一配置文件的beans注入,但是单例模式第二次不生效。
项目中有 spring-mvc.xml 和 spring-context.xml 配置文件,mvc配置如下

    <context:component-scan base-package="com.sf.pass">
        <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
    </context:component-scan>

查找资料后发现 include-fliter 这个是白名单,默认会加载service等一些其他的beans。spring配置中的use-default-filters用来指示是否自动扫描带有@Component、@Repository、@Service和@Controller的类。默认为true,即默认扫描

    <context:component-scan base-package="com.sf.pass" use-default-filters="false">
        <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
    </context:component-scan>

参考 https://blog.51cto.com/9855084/1961155

缓存返回的对象是共享变量,注意并发问题,修改要慎重

调用缓存方法返回的数据是共享的,请尽量不要进行修改。例如缓存方法返回了一个集合,如果对进行进行增删改,那么会直接影响了缓存的内容,这样会导致很多问题。
第一,如果是并发环境,可能会有并发修改异常。
第二,对缓存对象进行修改,会直接影响缓存,导致下次相同参数获取的结果不一致。
第三,修改了缓存的数据,如果缓存过期了,那么重新获取的结果就会不一样了。

例如,我们把之前的方法修改一下,返回一个list

@Cacheable(value = CacheConstants.CACHE_NANE_CVY_CACHE , key= "#name")
public List testCache(String name) {   
return new ArrayList(10);
}

然后使用测试方法,在返回的list进行修改

    @Test
    public void test () {
        for (int i = 0; i < 10; i++) {
            List list  = CacheService.testCache("jam");
            list.add(1);
            System.out.println(list.size());
        }
    }

发现缓存返回的list改变了,size不断增加。

缓存的key拼接

通常我们需要在缓存加上前缀,以便是不同类型的缓存,例如用户和产品都有id,如果想放在同一个缓存里,需要在缓存增加前缀等标识,例如用户key可能是 user_id, 产品key是prod_id,我们可以这样增加前缀 ,其中id是参数

String CACHE_KEY_USER= "'USER_'+#id"

当然key的组合还有很多中方式。可以参考下面的地址:
https://blog.csdn.net/syani/article/details/52239967
https://segmentfault.com/a/1190000016961342

缓存相关的注解

  • @Cacheable triggers cache population
  • @CacheEvict triggers cache eviction
  • @CachePut updates the cache without interfering with the method execution
  • @Caching regroups multiple cache operations to be applied on a method
  • @CacheConfig shares some common cache-related settings at class-level

相关资料

Spring cache 相关的官方文档,有问题建议之间看官方文档
https://docs.spring.io/spring/docs/4.3.x/spring-framework-reference/html/cache.html

欢迎关注我的公众号
只说一点点点点

发表评论

电子邮件地址不会被公开。 必填项已用*标注

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据