一、概述
Spring Cache 并不是一种Cache实现的技术,Spring Cache是一种缓存实现的通用技术,是基于
Spring提供的Cache框架,让开发者更容易将缓存的实现快速的键入自己的项目中,简化了代码中对缓存的操作。
Spring从3.1开始定义了 org.springframework.cache.Cache 和 org.springframework.cache.CacheManager接口来统一不同的缓存技术并支持使用
JCache(JSR-107) 注解形式简化开发
2、Spring Cache 核心接口
1)org.springframework.cache.Cache
Cache接口定义了缓存的组件规范,包含缓存的各种集合操作;Spring提供了Cache接
口的各种实现类(XXXCache),如:RedisCache、ConcurrentMapCache等
Cache接口常用方法介绍:
//获取缓存中间件名称,如:redis
String getName();
//获取缓存中所有条目
Object getNativeCache();
//根据key从缓存中取数据
@Nullable
Cache.ValueWrapper get(Object var1);
//根据key从缓存中取数据,并将value转换为指定类型
@Nullable
<T> T get(Object var1, @Nullable Class<T> var2);
//根据key从缓存中取数据,若key的值存在,则直接返回,若key的值不存在则从线程Callable中获取结果并将结果保存到缓存中,最后将结果返回
@Nullable
<T> T get(Object var1, Callable<T> var2);
void put(Object var1, @Nullable Object var2);//向缓存中添加数据
//若缓存中key的值不存在则把key-value 保存到缓存中,否则返回缓存中key的值
@Nullable
default Cache.ValueWrapper putIfAbsent(Object key, @Nullable Object value) {
Cache.ValueWrapper existingValue = this.get(key);
if (existingValue == null) {
this.put(key, value);
}
return existingValue;
}
//从缓存中删除键对应的值
void evict(Object var1);
//清空缓存中所有数据
void clear();
2)org.springframework.cache.CacheManager
缓存管理器,管理各种Cache缓存
CacheManager 接口常用方法介绍:
public interface CacheManager {
//获取指定的Cache
@Nullable
Cache getCache(String var1);
//获取所有的Cache名称
Collection<String> getCacheNames();
}
二、Spring Cache 使用步骤
以Spring Cache 整合Redis为例记录一次Spring Cache 使用步骤:
1、引入依赖
1)引入Spring Cache依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
<version>2.2.6.RELEASE</version>
</dependency>
2)因为我们使用的缓存是Redis,所以还需要引入Spring boot 整合redis的依赖
<!-- 引入spring-redis 一来-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<exclusions>
<!--
排除 spring data redis 中的默认的lettuce(spring data redis依赖在2.0
版本后默认的客户端是lettuce),
因为 lettuce 客户端有个bug会导致链接不释放,从而导致内存溢出
把redis客户端替换成jedis来解决这个bug
-->
<exclusion>
<groupId>io.lettuce</groupId>
<artifactId>lettuce-core</artifactId>
</exclusion>
</exclusions>
</dependency>
<!--导入jedis -->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</dependency>
2、编写配置
1) 编写Spring Cache 配置之前,我们要先了解Spring Cache 自动配置了哪些东西?
Spring Cache自动配置类是 CacheAutoConfiguration,CacheAutoConfiguration中
通过子类 CacheConfigurationImportSelector 加载多种配置类,如下图所示:
我们这次使用的是Redis对应的配置文件RedisCacheConfiguration,RedisCacheConfiguration主要作用是实例化Redis的缓存管理器 RedisCacheManager,RedisCacheConfiguration结构如下:
@AutoConfigureAfter({RedisAutoConfiguration.class})
@ConditionalOnBean({RedisConnectionFactory.class})
@ConditionalOnMissingBean({CacheManager.class})
@Conditional({CacheCondition.class})
class RedisCacheConfiguration {
RedisCacheConfiguration() {
}
@Bean
RedisCacheManager cacheManager(CacheProperties cacheProperties, CacheManagerCustomizers cacheManagerCustomizers, ObjectProvider<org.springframework.data.redis.cache.RedisCacheConfiguration> redisCacheConfiguration, ObjectProvider<RedisCacheManagerBuilderCustomizer> redisCacheManagerBuilderCustomizers, RedisConnectionFactory redisConnectionFactory, ResourceLoader resourceLoader) {
RedisCacheManagerBuilder builder = RedisCacheManager.builder(redisConnectionFactory).cacheDefaults(this.determineConfiguration(cacheProperties, redisCacheConfiguration, resourceLoader.getClassLoader()));
List<String> cacheNames = cacheProperties.getCacheNames();
if (!cacheNames.isEmpty()) {
builder.initialCacheNames(new LinkedHashSet(cacheNames));
}
redisCacheManagerBuilderCustomizers.orderedStream().forEach((customizer) -> {
customizer.customize(builder);
});
return (RedisCacheManager)cacheManagerCustomizers.customize(builder.build());
}
private org.springframework.data.redis.cache.RedisCacheConfiguration determineConfiguration(CacheProperties cacheProperties, ObjectProvider<org.springframework.data.redis.cache.RedisCacheConfiguration> redisCacheConfiguration, ClassLoader classLoader) {
return (org.springframework.data.redis.cache.RedisCacheConfiguration)redisCacheConfiguration.getIfAvailable(() -> {
return this.createConfiguration(cacheProperties, classLoader);
});
}
private org.springframework.data.redis.cache.RedisCacheConfiguration createConfiguration(CacheProperties cacheProperties, ClassLoader classLoader) {
Redis redisProperties = cacheProperties.getRedis();
org.springframework.data.redis.cache.RedisCacheConfiguration config = org.springframework.data.redis.cache.RedisCacheConfiguration.defaultCacheConfig();
config = config.serializeValuesWith(SerializationPair.fromSerializer(new JdkSerializationRedisSerializer(classLoader)));
if (redisProperties.getTimeToLive() != null) {
config = config.entryTtl(redisProperties.getTimeToLive());
}
if (redisProperties.getKeyPrefix() != null) {
config = config.prefixKeysWith(redisProperties.getKeyPrefix());
}
if (!redisProperties.isCacheNullValues()) {
config = config.disableCachingNullValues();
}
if (!redisProperties.isUseKeyPrefix()) {
config = config.disableKeyPrefix();
}
return config;
}
}
实例化RedisCacheConfiguration 需要传入参数 CacheProperties,CacheProperties是一
个配置类,在CacheProperties 中针对不同的缓存中间件配置配置文件都有一个子类与
其对应;另外由CacheProperties类可以发现SpringCache 支持 .proerties 类型的配置文件
,且配置项的名称以 “spring.cache” 开头,CacheProperties类属性如下图所示:
2)spring cache 整合Redis 需要我们手动配置的内容?
创建 .properties类型的配置文件(如:application.properties)
在配置文件一般只需要配置缓存类型,我们使用的缓存中间件是什么这里就配置什么
(如:redis),spring cache 配置大致如下:
3、使用Spring Cache 简化缓存操作
1)在启动类上添加注解 @EnableCaching 表示开始Spring Cache,如下图所示:
2)在需要使用缓存的方法上添加相应的注解,这里最常用的是注解@Cacheable;
注解 @Cacheable执行流程如下:
每次调用需要缓存功能的方法时,Spring会检查指定参数指定的目标方
法是否已经被调用过(即缓存中是否已经存在该目标方法的执行结果);
若缓存中目标方法执行结果已存在,直接获取缓存中执行后的结果;若缓存
中不存在数据,则执行目标方法,将执行结果返回给客户,并将执行结果保
存到缓存中。
注解@Cacheable使用代码如下:
@Cacheable
public Map<String, List<Catalog2VO>> getCatalogJsonFromCache() {
//1、查询所有的三级分类数据
List<CategoryEntity> selectList = this.baseMapper.selectList(null);
//2、将所有的三级分类,按父分类parentId分组
Map<Long, List<CategoryEntity>> categoryMap = selectList.stream().collect(Collectors.groupingBy(key -> key.getParentCid()));
//3、查询1级分类
List<CategoryEntity> level1Category = categoryMap.get(0L);
//4、封装数据
Map<String,List<Catalog2VO>> catalog2Map = level1Category.stream().collect(Collectors.toMap(key -> key.getCatId().toString(),level1 -> {
// 、、、、、省略 。。。。。
}));
return catalog2Map;
}
@Cacheable 默认行为:
1)@Cacheable标注的方法执行时,先从缓存中取数据,若缓存中有数据,
则方法就不执行了,直接返回缓存的数据
2)@Cacheable 数据时默认是jdk 序列化格式,jdk序列化是 不能跨平台的
3) 缓存中的key 是自动生成的,
格式:缓存名称::$simpleKey
4)@Cacheable 在缓存中的数据默认过期时间是-1,表示数据永不过期 。
4、@Cacheable 自定义配置
由上边 @Cacheable 默认行为可以发现,直接使用 @Cacheable 很多情况下并不能
满足我们的要求,对于缓存中的数据我们希望有以下的配置:
1)为了数据能够跨平台,我们应该可以指定数据的序列化方式,
如:把数据序列化成json
2)为了数据的查询,我们应该能指定key 值
3)我们应该能指定缓存数据的过期时间
在配置@Cacheable 之前,我们先下@Ccheable的结构
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Cacheable {
@AliasFor("cacheNames")
String[] value() default {};
//存储方法调用结果的缓存的名称(即缓存名称)。
//每一个需要缓存的数据都来指定要存放到哪个名字的缓存(相当于缓存分区,一般建议按:业务类型来分区)
@AliasFor("value")
String[] cacheNames() default {};
//指定缓存的key,若不指定则会通过 keyGenerator() 生成一个key
String key() default "";
//用于生成缓存中的key,默认是按EL正则表达式修改(也可以自己修改,但一般不需要自己修改)
//生成key的EL表达式格式:缓存名称(即value 或 cacheNames 的值)::$simpleKey
String keyGenerator() default "";
//指定使用的目标缓存,值是目标缓存的CacheManager缓存管理器
String cacheManager() default "";
//值指向要使用的org.springframework.cache.interceptor.CacheResolver 的Bean的名称
String cacheResolver() default "";
//想把哪些数据放入缓存(把符合条件饿存缓存)
String condition() default "";
//除非我们指定的条件外其他都可以存缓存
String unless() default "";
//在高并发环境中,若有多个请求访问该方法,此时若 sync= false,则每个方法都会直接访问方法,
//若 sync=true,则只有一个方法访问该方法然后把方法执行结果放回到缓存中,其他请求直接返回缓存
//的结果
boolean sync() default false;
}
(1) 可以通过注解@Cacheable 的value 或 cacheNames 指定缓存的名称
(2)在.properties配置文件中,全局配置缓存的过期时间,如:
spring.cache.redis.time-to-live=3600000
(2)通过key属性指定缓存中的key,
注意:解析时 Cacheable 会把key的值看成一个EL表达式,如:
key = "#root.method.name" , "#root.method.name"是一个EL表达式(不是字 符串),表示 以当前@Cacheable标注的方法名称作为缓存的key;
示例代码如下:
//value : 每一个需要缓存的数据都来指定要存放到哪个名字的缓存(相当于缓存分区,一般建议按:业务类型来分区)
//注意:key 的值是一个表达式,若想指定固定的key的值,可以使用嵌套字符串的格式,如:key = "'catalogJson'"
@Cacheable(value = {"catalogJson"},key = "#root.methodName")
public Map<String, List<Catalog2VO>> getCatalogJsonFromCache() {
//1、查询所有的三级分类数据
List<CategoryEntity> selectList = this.baseMapper.selectList(null);
//2、将所有的三级分类,按父分类parentId分组
Map<Long, List<CategoryEntity>> categoryMap = selectList.stream().collect(Collectors.groupingBy(key -> key.getParentCid()));
//3、查询1级分类
List<CategoryEntity> level1Category = categoryMap.get(0L);
//4、封装数据
Map<String,List<Catalog2VO>> catalog2Map = level1Category.stream().collect(Collectors.toMap(key -> key.getCatId().toString(),level1 -> {
// 、、、、、省略 。。。。。
}));
return catalog2Map;
}
5、给Spring Cache 指定序列化方式和超时时间
在上边Redis 的自动配置类
org.springframework.boot.autoconfigure.cache.RedisCacheConfiguration
类中的 determineConfiguration 方法可以发现,
若org.springframework.data.redis.cache.RedisCacheConfiguration
实例不存在,则会使用默认的RedisCacheConfiguration(spring data) 的Bean
如图所示:
org.springframework.data.redis.cache.RedisCacheConfiguration 类结构如下:
package org.springframework.data.redis.cache;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.util.Optional;
import java.util.function.Consumer;
import org.springframework.cache.interceptor.SimpleKey;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.converter.Converter;
import org.springframework.core.convert.converter.ConverterRegistry;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext.SerializationPair;
import org.springframework.format.support.DefaultFormattingConversionService;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
public class RedisCacheConfiguration {
private final Duration ttl;
private final boolean cacheNullValues;
private final CacheKeyPrefix keyPrefix;
private final boolean usePrefix;
private final SerializationPair<String> keySerializationPair;
private final SerializationPair<Object> valueSerializationPair;
private final ConversionService conversionService;
private RedisCacheConfiguration(Duration ttl, Boolean cacheNullValues, Boolean usePrefix, CacheKeyPrefix keyPrefix, SerializationPair<String> keySerializationPair, SerializationPair<?> valueSerializationPair, ConversionService conversionService) {
this.ttl = ttl;
this.cacheNullValues = cacheNullValues;
this.usePrefix = usePrefix;
this.keyPrefix = keyPrefix;
this.keySerializationPair = keySerializationPair;
this.valueSerializationPair = valueSerializationPair;
this.conversionService = conversionService;
}
//。。。。。。。。。。。。。。。省略 。。。。。。。。。。。。。。。
}
在spring data redis 包下RedisCacheConfiguration 中可以设置缓存过期时间、前缀、
是否保存null值、序列化方式等等。
我们只要在项目中实例化一个 RedisCacheConfiguration(spring data) 的Bean,就可以
在自定义的 RedisCacheConfiguration 指定序列化方式,示例代码如下:
@Configuration
public class MyCacheConfig {
/**
* 手动实例化 RedisCacheConfiguration 并指定序列化方式
* @return
*/
@Bean
public RedisCacheConfiguration configuration(){
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig();
//key序列化成String
config = config.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()));
//value 序列化成json
config = config.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()));
return config;
}
}