Spring 中的缓存技术 (JSR-107)

官方文档

Spring从3.1开始定义了一系列抽象接口来统一不同的缓存技术; 并支持使用**JCache(JSR-107)**注解简化我们进行缓存开发; Spring Cache 只负责维护抽象层, 具体的实现由你的技术选型来决定; 将缓存处理和缓存技术解除耦合;

依赖

Spring cache 抽象由spring-context 相关组件实现; 非Spring Boot 项目可通过引入该模块进行集成;

Spring Boot 项目可引入以下依赖:

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

基于 Redis 的缓存管理

If Redis is available and configured, a RedisCacheManager is auto-configured

否则需要定义 RedisCacheManager, 使用RedisCacheManagerBuilder构造之, 或者其他的

使用 FastJson 序列化

 
@Configuration
@EnableCaching
@ConditionalOnClass(RedisOperations.class)
@EnableConfigurationProperties(RedisProperties.class)
public class RedisConfig extends CachingConfigurerSupport {
  
 
    /**使用  FastJson
     * @return
     */
    @Bean
    public RedisCacheConfiguration redisCacheConfiguration(){
        FastJsonRedisSerializer<Object> fastJsonRedisSerializer = new FastJsonRedisSerializer<>(Object.class);
        RedisCacheConfiguration configuration = RedisCacheConfiguration.defaultCacheConfig();
        configuration = configuration.serializeValuesWith(RedisSerializationContext.SerializationPair
                .fromSerializer(fastJsonRedisSerializer))
                .entryTtl(Duration.ofDays(1));
        return configuration;
    }
 
    @Bean(name = "redisTemplate")
    @ConditionalOnMissingBean(name = "redisTemplate")
    public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        RedisTemplate<Object, Object> template = new RedisTemplate<>();
        FastJsonRedisSerializer<Object> fastJsonRedisSerializer = new FastJsonRedisSerializer<>(Object.class);
        // value值的序列化采用fastJsonRedisSerializer
        template.setValueSerializer(fastJsonRedisSerializer);
        template.setHashValueSerializer(fastJsonRedisSerializer);
        // key的序列化 采用StringRedisSerializer
        StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
        template.setKeySerializer( stringRedisSerializer );
        template.setHashKeySerializer(stringRedisSerializer );
        template.setConnectionFactory(redisConnectionFactory);
        return template;
    }
 
    /**
     * 自定义缓存key生成策略  见下 缓存注解->键默认生成策略 
      <pre>使用方法 @Cacheable(keyGenerator="keyGenerator")</pre>
     * @return
     */
    @Bean
    @Override
    public KeyGenerator keyGenerator() {
        return (target, method, params) -> {
            StringBuilder sb = new StringBuilder();
            sb.append(target.getClass().getName());
            sb.append(method.getName());
            for (Object obj: params) {
                //fastjson
                 sb.append(JSON.toJSONString(obj).hashCode());
            }
            return sb.toString();
        };
    }
 
}

使用 Jackson 序列化

public class RedisConfig extends CachingConfigurerSupport {
    @Bean
    public RedisCacheConfiguration redisCacheConfiguration(){
    	 GenericJackson2JsonRedisSerializer jackson2JsonRedisSerializer = new GenericJackson2JsonRedisSerializer();
          RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig();
          redisCacheConfiguration = redisCacheConfiguration.serializeValuesWith(
                  RedisSerializationContext
                          .SerializationPair
                          .fromSerializer(jackson2JsonRedisSerializer)
          ).entryTtl(Duration.ofDays(1));
          return redisCacheConfiguration;
    }
 
    @Bean(name = "redisTemplate")
    @ConditionalOnMissingBean(name = "redisTemplate")
    public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        RedisTemplate<Object, Object> template = new RedisTemplate<>();
        // Jackson2JsonRedisSerializer
        GenericJackson2JsonRedisSerializer jackson2JsonRedisSerializer = new GenericJackson2JsonRedisSerializer();
        // value值的序列化采用 
        template.setValueSerializer(jackson2JsonRedisSerializer);
        template.setHashValueSerializer(jackson2JsonRedisSerializer);
 
        // key的序列化采用StringRedisSerializer
        StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
        template.setKeySerializer( stringRedisSerializer );
        template.setHashKeySerializer(stringRedisSerializer );
	    
        template.setConnectionFactory(redisConnectionFactory);
        return template;
    }
 
    @Bean
    @Override
    public KeyGenerator keyGenerator() {
        return (target, method, params) -> {
            StringBuilder sb = new StringBuilder();
            sb.append(target.getClass().getName());
            sb.append(method.getName());
            for (Object obj : params) {
                sb.append(JacksonUtils.beanToJson(params).hashCode());
            }
            return sb.toString();
        };
    }
}

GenericJackson2JsonRedisSerializer 相对于 Jackson2JsonRedisSerializer 它序列化的json会携带泛型信息类似 {"@class":"cn.simae.cloud.admin.modules.bs.dto.UserDTO" .. , 下次反序列化的时候就不会不知类型转成linkmap 而报错.

CacheManager

If you have not defined a bean of type CacheManager or a CacheResolver named cacheResolver (see CachingConfigurer),

Spring Boot tries to detect the following providers (in the indicated order):

Generic JCache (JSR-107) (EhCache 3, Hazelcast, Infinispan, and others) EhCache 2.x Hazelcast Infinispan Couchbase Redis Caffeine Simple

注解

@CacheConfig(cacheNames = "role")
public interface JwtPermissionService {
	
    @Cacheable(key = "'loadPermissionByUser:' + #p0.username")
    public Collection<GrantedAuthority> mapToGrantedAuthorities(ApplicationUser user);
    
}

@EnableCaching

该注解是启用Spring cache 的开关; 必须开启才能使用Spring cache相关功能;

@CacheConfig

作用于接口或类上, 对(缓存名称, key生成器, 缓存管理器, 缓存处理器)提交默认配置; 其他属性可参考Cacheable;

@Cacheable

可以标记在一个方法上, 也可以标记在一个类上; @Cacheable 提供两个参数来指定缓存名: value, cacheNames, 二者选其一即可;

  • 标记在一个类上时则表示该类所有的方法都是支持缓存的

  • 标记在一个方法上时表示该方法是支持缓存的

对于一个支持缓存的方法,Spring会在其被调用后将其返回值缓存起来, 以保证下次利用同样的参数来执行该方法时可以直接从缓存中获取结果, 而不需要再次执行该方法; Spring在缓存方法的返回值时是以键值对进行缓存的, 值就是方法的返回结果,

至于键的话, Spring又支持两种策略, 默认策略和自定义策略;

键生成策略

  1. 取参数作为键
@Cacheable(key = "#p0")
DeptDTO findById(Long id);
 
  1. 键生成策略

默认是@Cacheable(keyGenerator = "keyGenerator") 通过KeyGenerator生成的, 其默认生成策略如下: 如果方法没有参数, 则使用0作为key; 如果只有一个参数的话则使用该参数作为key; 如果参数多于一个的话则使用所有参数的hashCode作为key;

键自定义生成策略 @Cacheable(value = "reportData",keyGenerator = "cacheKeyGenerator")

@Bean
public KeyGenerator cacheKeyGenerator() {
    return (target, method, params) -> {
        StringBuilder sb = new StringBuilder();
        sb.append(target.getClass().getName());
        sb.append(method.getName());
        sb.append(":");
        sb.append(DigestUtil.md5(JSONUtil.toJsonStr(params)));
        return sb.toString();
    };
}
  • @CachePut 该注解容易跟@Cacheable混淆; 两者都可以执行缓存的”放入”操作, 不同于@Cacheable,@CachePut每次都将执行方法并将返回值K-V放入缓存, 如果该K存在则进行更新; 其他属性可参考Cacheable;

  • @CachEvict 主要针对方法配置,能够根据一定的条件对特定的缓存进行清空; 该注解有两个特别的属性:

    allEntries 是否清空所有缓存内容, 缺省为 false, 如果指定为 true, 则方法调用后将立即清空所有缓存; 注意不能跟key参数同时使用;

    beforeInvocation 是否在方法执行前就清空, 缺省为 false, 如果指定为 true, 则在方法还没有执行的时候就清空缓存, 缺省情况下, 如果方法执行抛出异常, 则不会清空缓存;

  • @Caching

该注解是个组合注解;有时候我们需要在一个方法上同时使用多个相同注解但是java是不支持一个注解在同一个方法上多次使用; 这时就可以使用该注解进行组合;