[springboot专栏]整合EhCache实现单服务缓存

在Spring框架内我们首选Spring Cache作为缓存框架的门面,之所以说它是门面,是因为它只提供接口层的定义以及AOP注解等,不提供缓存的具体存取操作。缓存的具体存储还需要具体的缓存存储,比如EhCache 、Redis等。Spring Cache与缓存框架的关系有点像SLF4j与logback、log4j的关系。
在这里插入图片描述

  • EhCache 适用于单体应用的缓存,当应用进行分布式部署的时候,各应用的副本之间缓存是不同步的。EhCache 由于没有独立的部署服务,所以它的缓存和应用的内存是耦合在一起的,当缓存数据量比较大的时候要注意系统资源能不能满足应用内存的要求。
  • redis由于是可以独立部署的内存数据库服务,所以它能够满足应用分布式部署的缓存集中存储的要求,也就是分布式部署的应用使用一份缓存,从而缓存自然是同步的。但是对于一些小规模的应用,额外引入了redis服务,增加了运维的成本。

所以,比如我们自己开发一个小博客,自己的服务器又没有很多的资源独立部署redis服务,用EHCache作为缓存是比较好的选择。如果是企业级用户量,使用redis独立部署的服务作为缓存是更好的选择。

一、整合Spring Cache 与Ehcache

通过上一小节的学习,可以使用Spring cache通过注解的方式来操作缓存,一定程度上减少了程序员缓存操作代码编写量。注解添加和移除都很方便,不与业务代码耦合,容易维护。 这一部分内容是没有变化的,所以我们仍然使用Spring Cache。

第一步:pom.xml 添加 Spring cache 和 Ehcache的 jar 依赖:

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

第二步:添加入口启动类 @EnableCaching 注解开启 Caching,实例如下。

@EnableCaching

在Spring Boot中通过@EnableCaching注解自动化配置合适的缓存管理器(CacheManager),Spring Boot根据下面的顺序去侦测缓存提供者,也就是说Spring Cache支持下面的这些缓存框架:

  • Generic
  • JCache (JSR-107) (EhCache 3, Hazelcast, Infinispan, and others)
  • EhCache 2.x(发现ehcache的bean,就使用ehcache作为缓存
  • Hazelcast
  • Infinispan
  • Couchbase
  • Redis
  • Caffeine
  • Simple

第三步:添加ehcache配置

yml配置

需要说明的是config:classpath:/ehcache.xml可以不用写,因为默认就是这个路径。但ehcache.xml必须有。

spring:
  cache:
    type: ehcache
    ehcache:
      config: classpath:/ehcache.xml

在 resources 目录下,添加 ehcache 的配置文件 ehcache.xml ,文件内容如下:

<ehcache>
    <diskStore path="java.io.tmpdir/cache_dongbb"/>
    <defaultCache
            maxElementsInMemory="10000"
            eternal="false"
            timeToIdleSeconds="120"
            timeToLiveSeconds="120"
            overflowToDisk="false"
            diskPersistent="false"
            diskExpiryThreadIntervalSeconds="120"
    />
    <cache name="user_detail"
            maxElementsInMemory="10000"
            eternal="true"
            overflowToDisk="true"
            diskPersistent="true"
            diskExpiryThreadIntervalSeconds="600"/>
</ehcache>
conds="600"/>
</ehcache>

配置含义:

  1. name:缓存名称,与缓存注解的value(cacheNames)属性值相同。
  2. maxElementsInMemory:缓存最大个数。
  3. eternal:缓存对象是否永久有效,一但设置了,timeout将不起作用。
  4. timeToIdleSeconds:设置对象在失效前的允许闲置时间(单位:秒)。仅当eternal=false对象不是永久有效时使用,可选属性,默认值是0,也就是可闲置时间无穷大。
  5. timeToLiveSeconds:设置对象在失效前允许存活时间(单位:秒)。最大时间介于创建时间和失效时间之间。仅当eternal=false对象不是永久有效时使用,默认是0.,也就是对象存活时间无穷大。
  6. overflowToDisk:当内存中对象数量达到maxElementsInMemory时,Ehcache将会对象写到磁盘中。
  7. diskSpoolBufferSizeMB:这个参数设置DiskStore(磁盘缓存)的缓存区大小。默认是30MB。每个Cache都应该有自己的一个缓冲区。
  8. maxElementsOnDisk:硬盘最大缓存个数。
  9. diskPersistent:是否缓存虚拟机重启期数据。
  10. diskExpiryThreadIntervalSeconds:磁盘失效线程运行时间间隔,默认是120秒。
  11. memoryStoreEvictionPolicy:当达到maxElementsInMemory限制时,Ehcache将会根据指定的策略去清理内存。默认策略是LRU(最近最少使用)。你可以设置为FIFO(先进先出)或是LFU(较少使用)。
  12. clearOnFlush:内存数量最大时是否清除。
  13. diskStore 则表示临时缓存的硬盘目录。

“java.io.tmpdir”操作系统缓存的临时目录,不同操作系统的缓存临时目录不一样,在Windows的缓存目录为C:\\Users\\登录用户~1\\AppData\\Local\\Temp\\ ; Linux目录为/tmp

二、缓存的使用方法

缓存的使用方法仍然是Spring Cache的注解,使用方法是一样的,参考本专栏文章《结合redis详述声明式缓存注解的使用-Cacheable、CacheEvict、CachePut、Caching》。

三、缓存使用中的坑

注意:@Cacheable 注解在对象内部调用不会生效。这个坑不是单独针对EhCache的,只要使用Spring Cache都会有这个问题。

@Component
public class ClassA   {
    
    
    @Override
    public void MethodA(String username)  {
    
    
        MethodA1(username);  //缓存失效,@Cacheable 注解在对象内部调用不会生效
    }

    @Cacheable(value = USER_DETAIL,key = "#username")
    public void MethodA1(String username) {
    
    
       //执行方法体
    }
}

原因: Spring 缓存注解是基于Spring AOP切面,必须走代理才能生效,同类调用或者子类调用父类带有缓存注解的方法时属于内部调用,没有走代理,所以注解不生效。
解决办法: 将缓存方法,放在一个单独的类中

@Component
public class ClassA   {
    
    
    @Resource
    ClassB classB;

    @Override
    public void methodA(String username)  {
    
    
        classB.methodA1(username);  //缓存失效,@Cacheable 注解在对象内部调用不会生效
    }
}
@Component
public class ClassB   {
    
    

    @Cacheable(value = USER_DETAIL,key = "#username")
    public void methodA1(String username) {
    
    
       //执行方法体
    }
}

猜你喜欢

转载自blog.csdn.net/hanxiaotongtong/article/details/122893356