一、什么是Spring Cache

      从spring 3.1版本开始,提供了一种透明的方式来为现有的spring 应用添加cache。在应用层面与后端存储之间,提供了一层抽象,这层抽象目的在于封装各种可插拔的后端存储( ehcache guava redis),最小化因为缓存给现有业务代码带来的侵入。

    核心抽象主要体现在两个接口上
org.springframework.cache.cache

org.springframework.cache.cachemanager

cache代表缓存本身,包含缓存的常用操作:增加、删除、读取等。

CacheManager 是 Spring 各种缓存的抽象接口。抽象的意义在于屏蔽实现细节的差异和提供扩展性,这一层cache的抽象解耦了缓存的使用和缓存的后端存储,这样后续可以方便的更换后端存储。

Spring 支持的常用 CacheManager 如下:

CacheManager

描述

SimpleCacheManager

使用简单的 Collection 来存储缓存

ConcurrentMapCacheManager

使用 java.util.ConcurrentHashMap 来实现缓存

NoOpCacheManager

仅测试用,不会实际存储缓存

EhCacheCacheManger

使用EhCache作为缓存技术。EhCache 是一个纯 Java 的进程内缓存框架,特点快速、精干,是 Hibernate 中默认的 CacheProvider,也是 Java 领域应用最为广泛的缓存

JCacheCacheManager

支持JCache(JSR-107)标准的实现作为缓存技术

CaffeineCacheManager

使用 Caffeine 作为缓存技术。用于取代 Guava 缓存技术。

RedisCacheManager

使用Redis作为缓存技术

HazelcastCacheManager

使用Hazelcast作为缓存技术

CompositeCacheManager

用于组合 CacheManager,可以从多个 CacheManager 中轮询得到相应的缓存

二、核心注解

Spring Cache 提供了 @Cacheable 、@CachePut 、@CacheEvict 、@Caching 等注解

1、 @Cacheable

缓存数据或者获取缓存数据

属性值如下:

属性/方法名

解释

value

缓存名,必填,它指定了你的缓存存放在哪块命名空间

cacheNames

与 value 差不多,二选一即可

key

可选属性,可以使用 SpEL 标签自定义缓存的key

keyGenerator

key的生成器。key/keyGenerator二选一使用

cacheManager

指定缓存管理器

cacheResolver

指定获取解析器

condition

条件符合则缓存

unless

条件符合则不缓存

sync

是否使用异步模式,默认为false

2、@CachePut

修改缓存数据。

属性/方法名

解释

value

缓存名,必填,它指定了你的缓存存放在哪块命名空间

cacheNames

与 value 差不多,二选一即可

key

可选属性,可以使用 SpEL 标签自定义缓存的key

keyGenerator

key的生成器。key/keyGenerator二选一使用

cacheManager

指定缓存管理器

cacheResolver

指定获取解析器

condition

条件符合则缓存

unless

条件符合则不缓存

3、@EnableCaching

开启缓存功能,一般放在启动类上

4、@CacheEvict

清空缓存

属性/方法名

解释

value

缓存名,必填,它指定了你的缓存存放在哪块命名空间

cacheNames

与 value 差不多,二选一即可

key

可选属性,可以使用 SpEL 标签自定义缓存的key

keyGenerator

key的生成器。key/keyGenerator二选一使用

cacheManager

指定缓存管理器

cacheResolver

指定获取解析器

condition

条件符合则缓存

allEntries

是否清空所有缓存,默认为 false。如果指定为 true,则方法调用后将立即清空所有的缓存

beforeInvocation

是否在方法执行前就清空,默认为 false。如果指定为 true,则在方法执行前就会清空缓存

5、@Caching

该注解可以实现同一个方法上同时使用多种注解。

6、@CacheConfig

统一配置@Cacheable中的value值

三、接入Redis简单案例demo

1、添加maven依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
    <version>${version}</version>
</dependency>

2、redis配置

spring:
  redis:
    # Redis数据库索引(默认为0)
    database: 0
    host: 127.0.0.1
    password: test
    # Redis服务器连接端口
    port: 6379
    # 连接超时时间(毫秒)
    timeout: 10000
    jedis:
      pool:
        # 连接池最大连接数(使用负值表示没有限制)
        max-active: 8
        # 连接池最大阻塞等待时间(使用负值表示没有限制)
        max-wait: -1
        # 连接池中的最大空闲连接
        max-idle: 8
        # 连接池中的最小空闲连接
        min-idle: 0

3、配置CacheManager以及自定义key生成策略

@EnableConfigurationProperties(CacheProperties.class)
@Configuration(proxyBeanMethods = false)
public class RedisCacheAutoConfiguration extends CachingConfigurerSupport {
    @Resource
    private RedisConnectionFactory factory;
    @Resource
    private CacheProperties cacheProperties;

    /**
     * 自定义生成redis-key
     */
    @Override
    @Primary
    @Bean
    public KeyGenerator keyGenerator() {
        return (target, method, params) -> {
            StringBuilder sb = new StringBuilder(cacheProperties.getCacheKeyPrefix()).append(":");
            sb.append(target.getClass().getName()).append(":");
            sb.append(method.getName()).append(":");
            for (Object param : params) {
                sb.append(param.toString()).append(":");
            }
            return sb.toString();
        };
    }

    @Override
    @Bean
    @Primary
    public CacheResolver cacheResolver() {
        return new SimpleCacheResolver(cacheManager());
    }


    /**
     * 配置CacheManager
     */
    @Primary
    @Bean
    @Override
    public CacheManager cacheManager() {
        RedisCacheConfiguration defaultRedisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig()
                //不缓存null结果
//                .disableCachingNullValues()
                .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer(CommonUtil.objectMapper())))
                //默认缓存30分钟
                .entryTtl(Duration.ofMinutes(30))
                //设置缓存key前缀
                .computePrefixWith(cacheKeyPrefix());
        return new CustomRedisCacheManager(RedisCacheWriter.nonLockingRedisCacheWriter(factory), defaultRedisCacheConfiguration);
    }

    
     /**
     * key添加默认前缀方法
     */
    @Primary
    @Bean
    public CacheKeyPrefix cacheKeyPrefix() {
        return (String cacheName) -> {
            String[] arr = cacheName.split(CustomRedisCacheManager.SEPARATOR);
            StringBuilder sb = new StringBuilder(cacheProperties.getCacheKeyPrefix()).append(":").append(arr[0]).append(":");
            return sb.toString();
        };
    }

}

默认key前缀配置

@Data
@ConfigurationProperties(prefix = "test.cache")
public class CacheProperties {

    /**
     * 缓存key前缀
     */
    private String cacheKeyPrefix;


}

自定义RedisCacheManager用于重写过期时间

/**
 * 继承RedisCacheManager,重写createRedisCache方法,定义缓存过期时间
 */
public class CustomRedisCacheManager extends RedisCacheManager {

    public static final String SEPARATOR = "#";

    public CustomRedisCacheManager(RedisCacheWriter cacheWriter, RedisCacheConfiguration defaultCacheConfiguration) {
        super(cacheWriter, defaultCacheConfiguration);
    }

    @Override
    protected RedisCache createRedisCache(String name, RedisCacheConfiguration cacheConfig) {
        String[] arr = StringUtils.delimitedListToStringArray(name, SEPARATOR);
        name = arr[0];
        if (arr.length > 1) {
            // 缓存过期时间,注意单位是秒
            long ttl = Long.parseLong(arr[1]);
            cacheConfig = cacheConfig.entryTtl(Duration.ofSeconds(ttl));
        }
        return super.createRedisCache(name, cacheConfig);
    }
}

使用案例

//缓存失效时间1800s,key为拼接nick
    @Cacheable(value = "taoOrderAnalysis#1800", key = "#nick")
    public TaoOrderAnalysisDianzhang queryByNick(String nick) {
        TableRouter tableRouter = new TableRouter();
        tableRouter.setDbNo(1);
        tableRouter.setLogicTable("tao_order_analysis_dianzhang");
        return taoOrderAnalysisDianzhangMapper.queryByNick(nick, tableRouter);
    }

在传递的nick为 -fengye- ,定义的cacheKeyPrefix为 test ,那么获取缓存使用key为:test:taoOrderAnalysis:-fengye-