SpringBoot2基于Spring Cache实现缓存(包括集成Redis和EhCache)

2022年8月8日10:16:58

SpringBoot基于Spring Cache实现缓存(包括集成Redis和EhCache)

  • 缓存的目的是:通过Cache来缓存不经常改变的数据以提高系统性能和增加系统吞吐量,避免直接访问数据库等低速的存储系统。
  • Spring Cache对Cache进行抽象,提供了@Cacheable、@CachePut、@CacheEvict等注解。
  • 可用于单体应用系统,也可集成Redis等缓存服务器用于大型系统或者分布式系统。

一、使用Spring自带的缓存管理器

  • Spring自带的缓存类型为Simple,这个缓存与Spring Boot应用在同一个Java虚拟机内,适合单体应用系统。
  1. pom文件加入依赖
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-cache</artifactId></dependency>
  1. application.properties中配置属性
spring.cache.type=Simple
  • Simple:基于ConcurrentHashMap实现的缓存,只适合单体应用或者开发环境使用。
  • None:禁止使用缓存。
  • Redis:使用Redis缓存。
    等…
  1. 然后在启动或者配置类上加入@EnableCaching注解来开启缓存注解。
//开启缓存注解@EnableCaching@SpringBootApplicationpublicclassApplication{publicstaticvoidmain(String[] args){
        SpringApplication.run(Application.class, args);}}
  1. 示例一个Service实现类,并进行统一解释。
import com.example.demo.bean.User;import com.example.demo.service.UserService;import lombok.extern.slf4j.Slf4j;import org.springframework.cache.annotation.*;import org.springframework.stereotype.Service;import java.util.HashMap;import java.util.Map;@Service@Slf4j@CacheConfig(cacheNames={"user"})//统一指定value的值,这时可省略value,如果你在你的方法依旧写上了value,那么依然以方法的value值为准。publicclassUserServiceImplimplementsUserService{privatestaticfinal Map<Integer, User> users=newHashMap<>(3);static{
        users.put(1,newUser(1,"bym"));
        users.put(2,newUser(2,"wx"));
        users.put(3,newUser(3,"sqp"));}@Override//@Cacheable(key = "targetClass + methodName +#p0")//@Cacheable(value="users", key="#p0")//@Cacheable(key = "#id")@Cacheablepublic UsergetUser(int id){
        log.info("缓存中没有,从数据中取...");return users.get(id);}/**
     * 在更新数据的同时,缓存也被更新
     * @param user
     * @return
     */@Override@CachePut(cacheNames="space", key="#user.id")public UserupdateUser(User user){
        log.info("更新数据...");
        users.put(user.getId(), user);return user;}//清除一条缓存,key为要清空的数据@CacheEvict(value="space", key="#id")publicvoiddelete(int id){
        users.remove(id);}//方法调用后清空所有缓存@CacheEvict(value="space", allEntries=true)publicvoiddeleteAll(){
        users.clear();}//方法调用前清空所有缓存@CacheEvict(value="space", beforeInvocation=true)publicvoiddeleteAllBefore(){
        users.clear();}/**
     *   @Caching是 @Cacheable、@CachePut、@CacheEvict注解的组合
     *   以下注解的含义:
     *   1.当使用指定名字查询数据库后,数据保存到缓存
     *   2.现在使用id、age就会直接查询缓存,而不是查询数据库
     */}

注意:这里的User实体类要实现Serializable序列化接口。public class User implements Serializable,否则会报异常。
SpringBoot2基于Spring Cache实现缓存(包括集成Redis和EhCache)

@Cacheable

@Cacheable作用在方法上,声明了方法的结果是可缓存的。

  1. 如果缓存存在,则目标方法不会被调用,直接取出缓存。可以为方法声明多个缓存,如果至少有一个缓存有缓存项,则其缓存项将被返回。
  2. 如果缓存不存在,则进入实际业务方法,将业务方法返回的结果缓存下来。

属性

SpringBoot2基于Spring Cache实现缓存(包括集成Redis和EhCache)
打开此注解的源码,如下:

@AliasFor("cacheNames")
String[]value()default{};@AliasFor("value")
String[]cacheNames()default{};

Stringkey()default"";
StringkeyGenerator()default"";//key的生成器。key/keyGenerator二选一使用
StringcacheManager()default"";//指定缓存管理器
StringcacheResolver()default"";//或者指定获取解析器
Stringcondition()default"";//条件符合则缓存
Stringunless()default"";//条件符合则不缓存booleansync()defaultfalse;//是否使用异步模式
  1. cacheNamesvalue是一个意思,都表示指定缓存的命名空间(或名称),注解中必须指定命名空间。(本文指定用cacheNames)
    写法:
@Cacheable@Cacheable("user")@Cacheable({"user","info"})@Cacheable(cacheNames="user",key="xx")@Cacheable(cacheNames={"user","info"},key="xx")
  1. key指缓存的key。
    1)可以为空,为空时,Spring使用KeyGenerator类来根据方法参数生成key。
KeyGenerator

KeyGenerator的规则:
1.如果方法只有一个参数,这个参数就是Key。
2.如果没有参数,则Key是SimpleKey.EMPTY。
3.如果有多个Key,则返回包含多个参数的SimpleKey。
Spring使用SimpleKeyGenerator来实现上述key的生成。

源码:

package org.springframework.cache.interceptor;import java.lang.reflect.Method;publicclassSimpleKeyGeneratorimplementsKeyGenerator{publicSimpleKeyGenerator(){}public Objectgenerate(Object target, Method method, Object... params){returngenerateKey(params);}publicstatic ObjectgenerateKey(Object... params){if(params.length==0){return SimpleKey.EMPTY;}else{if(params.length==1){
                Object param= params[0];if(param!= null&&!param.getClass().isArray()){return param;}}returnnewSimpleKey(params);}}}

我们还可以实现自己的KeyGenerator方法:

@Cacheable(cacheNames="users",keyGenerator="myKg)public UsergetUser(int id){...}

myKg实现了KeyGenerator接口,然后从user中获取到id作为缓存的Key。

@Overridepublic Objectgenerate(Object target, Method method, Object... params){
    User user=(User)params[0];return user.getId();}

通常情况下,直接使用SpEL表达式来指定Key比自定义KeyGenerator更简单。

2)可按照SpEL表达式编写。

SpEL
名称 位置 描述 示例
methodName root对象 当前被调用的方法名 #root.methodname
method root对象 当前被调用的方法 #root.method.name
target root对象 当前被调用的目标对象实例 #root.target
targetClass root对象 当前被调用的目标对象的类 #root.targetClass
args root对象 当前被调用的方法的参数列表 #root.args[0]
caches root对象 当前方法调用使用的缓存列表 #root.caches[0].name
Argument Name 执行上下文 当前被调用的方法的参数,如findArtisan(Artisan artisan),可以通过#artsian.id获得参数 #artsian.id
result 执行上下文 方法执行后的返回值(仅当方法执行后的判断有效,如 unless cacheEvict的beforeInvocation=false) #result

1.当我们要使用root对象的属性作为key时我们也可以将“#root”省略,因为Spring默认使用的就是root对象的属性。 如

@Cacheable(key="targetClass + methodName +#p0")

2.使用方法参数时我们可以直接使用“#参数名”或者“#p参数index”。 如:

@Cacheable(value="users", key="#id")public Usermethod1(int id){...}@Cacheable(value="users", key="#p0")public Usermethod2(int id,String name){...}@Cacheable(value="users", key="#user.id")public Usermethod3(User user){...}

3.SpEL提供了多种运算符

类型 运算符
关系 <,>,<=,>=,==,!=,lt,gt,le,ge,eq,ne
算术 +,- ,* ,/,%,^
逻辑 &&,
条件 ?: (ternary),?: (elvis)
正则表达式 matches
其他类型 ?.,?[…],![…],$[…]

@CachePut

@CachePut作用在方法上,总是会执行方法体,使用方法体返回的结果更新缓存。属性和Cacheable一样。需要注意的是该注解的value 和 key 必须与要更新的缓存相同,也就是与@Cacheable 相同。

@CacheEvict

@CacheEvict作用在方法上,能够根据一定的条件删除缓存项或清空缓存。
这里需要注意两个属性:

属性 解释 示例
allEntries 是否清空所有缓存内容,缺省为 false,如果指定为 true,则方法调用后将立即清空所有缓存 @CachEvict(value=”testcache”,allEntries=true)
beforeInvocation 是否在方法执行前就清空,缺省为 false,如果指定为 true,则在方法还没有执行的时候就清空缓存,缺省情况下,如果方法执行抛出异常,则不会清空缓存 @CachEvict(value=”testcache”,beforeInvocation=true)

注意:CacheEvict只具备删除缓存功能,不具备加载缓存功能,只有相应的@Cacheable方法被调用后,才会加载最新缓存项。

缓存系统中不经常改变的业务数据一旦发生改变时,通常会更新此业务相关的所有缓存 。

  • 以菜单为例,如果添加菜单项目,应更新菜单相关的所有缓存。
//方法调用后清空所有缓存@CacheEvict(value={"menu","menuTree"}, allEntries=true)publicvoidaddMenu(Menu menu){
        users.clear();}

在更新菜单后,菜单树和菜单缓存都需要清空以便下次获取的时候重新构造。

@Caching

@Caching作用在方法上,可以混合以上三种注解,比如一个修改同时需要失效对应的用户缓存和用户扩展信息缓存。
SpringBoot2基于Spring Cache实现缓存(包括集成Redis和EhCache)

@Caching(evict={@CacheEvict(value="user",key="#user.id"),@CacheEvict(value="userExt",key="#ext.id")})publicvoidupdateUser(User user,UserExt ext){...}

@CacheConfig

到目前为止,所有的Cache注解都需要提供Cache名称,如果每个Service方法上都包含一个Cache名称,可能写起来重复,注解@CacheConfig作用于类上,可以为此类的方法的缓存注解提供默认值,包括缓存的默认名称和KeyGenerator。
SpringBoot2基于Spring Cache实现缓存(包括集成Redis和EhCache)

用法:@CacheConfig("users")等。
这时可省略方法上的value,如果你在你的方法依旧写上了value,那么依然以方法的value值为准。

二、Spring Cache整合Redis

  • 对于分布式应用,通常都会将缓存放在一台或者多台专门的缓存服务器上。使用Redis作为缓存是一种常用的选择。
  1. pom文件加入依赖
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-cache</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency><!-- redis依赖commons-pool 这个依赖一定要添加 --><dependency><groupId>org.apache.commons</groupId><artifactId>commons-pool2</artifactId></dependency>
  1. application.properties中配置属性
spring.cache.type=Redis
spring.redis.host=192.168.2.185
spring.redis.port=6379
spring.redis.lettuce.pool.max-active=8
spring.redis.lettuce.pool.max-idle=8
spring.redis.lettuce.pool.min-idle=0

其注解使用方式与Simple方式一致。

  • 通过上面两步的配置,SpringBoot的CacheManager就会使用RedisCache。
定制缓存:对于RedisCacheManager来说,还可以定制缓存项的存活时间,还有修改缓存序列化策略。
  • 添加一个配置类:
@Configuration@AutoConfigureAfter(RedisAutoConfiguration.class)publicclassCacheConfigextendsCachingConfigurerSupport{//缓存管理器@Beanpublic RedisCacheManagerredisCacheManager(RedisConnectionFactory factory){
        Jackson2JsonRedisSerializer serializer=newJackson2JsonRedisSerializer(Object.class);
        ObjectMapper mapper=newObjectMapper();
        mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);// 生成一个默认配置,通过config对象即可对缓存进行自定义配置
        RedisCacheConfiguration config= RedisCacheConfiguration.defaultCacheConfig()//此配置可以使redis的值不为乱码。.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(serializer));// 设置缓存的默认过期时间,也是使用Duration设置//        config = config.entryTtl(Duration.ofMinutes(10))//                .disableCachingNullValues();     // 不缓存空值// 设置一个初始化的缓存空间set集合
        Set<String> cacheNames=newHashSet<>();
        cacheNames.add("space");
        cacheNames.add("user");// 对每个缓存空间应用不同的配置
        Map<String, RedisCacheConfiguration> configMap=newHashMap<>();// 通过Duration可以自己实现以什么时间为单位
        configMap.put("space", config.entryTtl(Duration.ofMinutes(1)));
        configMap.put("user", config.entryTtl(Duration.ofSeconds(10)));return RedisCacheManager.builder(RedisCacheWriter.nonLockingRedisCacheWriter(factory)).initialCacheNames(cacheNames)// 注意这两句的调用顺序,一定要先调用该方法设置初始化的缓存名,再初始化相关的配置.withInitialCacheConfigurations(configMap).cacheDefaults(config).build();}//    修改redis默认的缓存序列化策略(jdk序列化),//	  这里我们注入了一个RedisTemplate 设置了里面的序列化,然后呢把他注入到redisCacheManger里就可以了。//    @Bean//    public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory connectionFactory) {//        RedisTemplate<Object, Object> template = new RedisTemplate<>();//        template.setConnectionFactory(connectionFactory);//        //使用Jackson2JsonRedisSerializer来序列化和反序列化redis的value值(默认使用JDK的序列化方式)//        Jackson2JsonRedisSerializer serializer = new Jackson2JsonRedisSerializer(Object.class);//        ObjectMapper mapper = new ObjectMapper();//        mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);//        mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);//        serializer.setObjectMapper(mapper);//        template.setValueSerializer(serializer);//        //使用StringRedisSerializer来序列化和反序列化redis的key值//        template.setKeySerializer(new StringRedisSerializer());//        template.afterPropertiesSet();//        return template;//    }}
  • 配置上面一步前:SpringBoot2基于Spring Cache实现缓存(包括集成Redis和EhCache)
    SpringBoot2基于Spring Cache实现缓存(包括集成Redis和EhCache)
  • 配置成功后没有乱码问题
    SpringBoot2基于Spring Cache实现缓存(包括集成Redis和EhCache)

二、Spring Cache整合EhCache

  • EhCache 是一个纯Java的进程内缓存框架,具有快速、精干等特点,是Hibernate中默认CacheProvider
  • Ehcache是一种广泛使用的开源Java分布式缓存。主要面向通用缓存,Java EE和轻量级容器。它具有内存和磁盘存储,缓存加载器,缓存扩展,缓存异常处理程序,一个gzip缓存servlet过滤器,支持REST和SOAP api等特点。
主要特性:
  1. 快速
  2. 简单
  3. 多种缓存策略
  4. 缓存数据有两级:内存和磁盘,因此无需担心容量问题
  5. 缓存数据会在虚拟机重启的过程中写入磁盘
  6. 可以通过RMI、可插入API等方式进行分布式缓存
  7. 具有缓存和缓存管理器的侦听接口
  8. 支持多缓存管理器实例,以及一个实例的多个缓存区域
  9. 提供Hibernate的缓存实现

ehcache 对分布式支持不够好,多个节点不能同步,通常和redis一块使用

和Redis比较:
  • ehcache直接在jvm虚拟机中缓存,速度快,效率高;但是缓存共享麻烦,集群分布式应用不方便。

  • redis是通过socket访问到缓存服务,效率比ecache低,比数据库要快很多,
    处理集群和分布式缓存方便,有成熟的方案。如果是单个应用或者对缓存访问要求很高的应用,用ehcache。如果是大型系统,存在缓存共享、分布式部署、缓存内容很大的,建议用redis。

ehcache也有缓存共享方案,不过是通过RMI或者Jgroup多播方式进行广播缓存通知更新,缓存共享复杂,维护不方便;简单的共享可以,但是涉及到缓存恢复,大数据缓存,则不合适。

ehcache3.x与2.x的差距还是非常大的,主要区别在于3.x后使用了java的缓存规范JSR107!!!

EhCache3.x
  1. pom文件加入依赖
<!-- JSR107 API --><dependency><groupId>javax.cache</groupId><artifactId>cache-api</artifactId></dependency><dependency><groupId>org.ehcache</groupId><artifactId>ehcache</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-cache</artifactId></dependency>
  1. yml配置
    需要说明的是默认路径为config: classpath:/ehcache.xml如果在这个目录下这个配置可以不用写,但ehcache.xml必须有。
spring:cache:type: jcachejcache:config: classpath:/cache/ehcache.xml
  1. 配置文件
    在resources的cache目录下新建ehcache.xml
<?xml version="1.0" encoding="UTF-8"?><configxmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'xmlns='http://www.ehcache.org/v3'xmlns:jsr107='http://www.ehcache.org/v3/jsr107'xsi:schemaLocation="
        http://www.ehcache.org/v3 http://www.ehcache.org/schema/ehcache-core-3.0.xsd
        http://www.ehcache.org/v3/jsr107 http://www.ehcache.org/schema/ehcache-107-ext-3.0.xsd"><cache-templatename="heap-cache"><resources><heapunit="entries">2000</heap><offheapunit
  • 作者:鞑子感到头秃
  • 原文链接:https://blog.csdn.net/weixin_41143087/article/details/93977266
    更新时间:2022年8月8日10:16:58 ,共 10635 字。