avatar

SpringBoot与缓存

JSR107缓存规范

JSR107是Java为我们提供的一套缓存规范,它是面向接口编程的,我们想要实现什么功能就去继承相应的接口,并重写对应的方法。不过由于整合的难度较大,现在市面上较少使用,不过后面推出的一些缓存实现机制都是在它基础层面上进行扩展的。所以我们可以了解一下它的基础概念,后面的使用中很少会使用到它了,一般会使用SpringBoot的缓存抽象
java Caching定义了5个核心接口。分别是CachingProvider,CacheManager,Cache,Entry,Expiry.

  • CachingProvider定义了创建、配置、获取、管理和控制多个CacheManager。一个应用可以在运行期访问多个CachingProvider。
  • CacheManager定义了创建、配置、获取、管理和控制多个唯一命名的Cache,这些Cache存在于CacheManager的上下文中。一个CacheManager仅被一个CachingProvider所拥有。
  • Cache是一个类似Map的数据结构并临时存储以Key为索引的值。一个Cache仅被一个CacheManager所拥有。
  • Entry是一个存储在Cache中的key-value对。
  • Expiry 每一个存储在Cache中的条目有一个定义的有效期。一旦超过这个时间,条目为过期的状态。一旦过期,条目将不可访问、更新和删除。缓存有效期可以通过ExpiryPolicy设置

如果我们想要使用JSR107规范,那么需要导入javax.cache下的cache-api

<dependency>
<groupId>javax.cache</groupId>
<artifactId>cache-api</artifactId>
</dependency>

因为JSR107是一个缓存规范,不是一种实现,全是一些接口,我们需要自己去实现JSR,市面上应用JSR的也不是很多,所以目前我们很少有使用JSR的而多数是使用Spring的缓存抽象。

Spring缓存抽象

Spring从3.1开始定义了org.springframework.cache.Cache和org.springframework.cache.CacheManager接口来统一不同的缓存技术,并且支持JCache(JSR-107)注解简化我们的开发

  • Cache接口为缓存的组件规范定义,包含缓存的各种操作集合
  • Cache接口下Spring提供了各种xxxCache的实现;如RedisCache,EhCacheCache,ConcurrentMapCache等
  • 每次调用需要缓存功能的方法时,Spring会检查指定参数的指定的目标方法是否被调用过;如果有直接从缓存中获取调用后的结果,如果没有就调用方法并缓存结果后返回给用户。下次调用直接从缓存中获取。
  • 使用Spring缓存抽象时我们需要关注以下两点
    • 确定方法需要被缓存以及他们的缓存策略
    • 从缓存中读取的数据是之前缓存存储的数据

几个重要概念&缓存注解

  • 概念和注解

    注解/概念 作用
    Cache 缓存接口,定义缓存操作。实现有:RedisCache、EhCacheCache、ConcurrentMapCache
    CacheManager 缓存管理器,管理各种缓存(Cache)组件
    @Cacheable 主要针对方法配置,能够根据方法的请求参数对其结果进行缓存
    @CacheEvict 清空缓存
    @CachePut 保证方法被调用,又希望结果被缓存
    @EnableCaching 开启基于注解的缓存
    keyGenerator 缓存数据时key的生成策略
    serialize 缓存数据时value序列化策略
  • @Cacheable/@CachePut/@CacheEvict主要参数

参数 作用 例子
value 缓存的名称,在Spring配置文件中定义,必须指定至少一个 @Cacheable(value=”mycache”)或者
@Cacheable(value={“cache1”,”cache2”})
key 缓存的 key,可以为空,如果指定要按照 SpEL
表达式编写,如果不指定,则缺省按照方法的
所有参数进行组合
@Cacheable(value=”testcache”,key=”#userName”)
condition 缓存的条件,可以为空,使用 SpEL 编写,返
回 true 或者 false,只有为 true 才进行缓存/清
除缓存
@Cacheable(value=”testcache”,condition=”#userName.length()>2”)
allEntries(@CacheEvict ) 是否清空所有缓存内容,缺省为 false,如果指
定为 true,则方法调用后将立即清空所有缓存
@CachEvict(value=”testcache”,allEntries=true)
beforeInvocation(@CacheEvict) 是否在方法执行前就清空,缺省为 false,如果
指定为 true,则在方法还没有执行的时候就清
空缓存,缺省情况下,如果方法执行抛
出异常,则不会清空缓存
@CachEvict(value=”testcache”,beforeInvocation=true)
  • 缓存SpEL能使用的参数有哪些
名字 位置 描述 实例
methodName root object 当前被调用的方法名 #root.methodName
method root object 当前被调用的方法 #root.method.name
target root object 当前被调用的目标对象 #root.target
targetClass root object 当前被调用的目标对象类 #root.targetClass
args root object 当前被调用的方法的参数列表 #root.args[0]
caches root object 当前方法调用使用的缓存列表(如@Cacheable(value={“cache1”,”cache2”})) #root.caches[0].name
argument name evaluation context 方法参数的名字,可以直接 #参数名,也可以使用#p0活着#a0的形式,0代表参数的索引 #name,#a0,#p0

使用Srping Cache

我们使用Spring初始化器,选择IO下的缓存模块就可以将缓存的starter引入

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
  • 开启基于注解的缓存@EnableCaching

    @SpringBootApplication
    @EnableCaching //在主程序入口处开启
    public class SpringbootCacheApplication {

    public static void main(String[] args) {
    SpringApplication.run(SpringbootCacheApplication.class, args);
    }
    }
  • 标注缓存注解即可

Cacheable注解使用

Cacheable:将方法的运行结果进行缓存,以后再要有相同的数据,直接从缓存中取,不用调用方法
CacheManager管理多个Cache组件的,对缓存的真正CRUD操作再Cache组件中华,每一个缓存组件有自己唯一一个名字;
几个属性:

1) cacheNames/value:指定缓存组件的名字;将方法的返回结果放在放在哪个缓存中,是数组的方式,可以指定多个缓存
2) key:缓存数据使用的key,可以用它来指定。默认是使用方法参数的值,也可以编写SpEL

@Cacheable(cacheNames = "emp",key = "#root.methodName+'['+#id+']'")

3) keyGenerator:key的生成器,可以自己指定key的生成器组件id key/keyGeneratore二选一,只能有一个使用
如果要使用keyGenerator一共有两步,第一步是往容器中创建一个KeyGenertor类,然后再注解中标记他

//1、往IOC容器中添加
@Configuration
public class MyCacheConfig {

@Bean("MyKeyGenerator")
public KeyGenerator keyGenerator()
{
return new KeyGenerator()
{
@Override
public Object generate(Object target, Method method, Object... params) {
return method.getName()+"["+ Arrays.toString(params)+"]";
}
};
}
}
//2、使用的时候需要在注解中指定
@Cacheable(cacheNames = "emp",key = "MyKeyGenerator")//这样就会走我们自己定义的key生成规则

4) cacheManager:指定缓存管理器,或者是cacheResolver缓存解析器,二选一
5) condition: 指定符合条件的情况下才缓存

@Cacheable(cacheNames = "emp",condition = "#id>1 and #root.methodName eq 'aaa'")//这样是id大于1并且方法名等于aaa才会进行缓存,可以写很多条件

6) unless:否定缓存,当unless为true则不缓存,可以获取到结果进行判断 unless = “#result == null”,与condition用法相反。
7) sync: 是否启用异步模式,是否在缓存结果的时候异步处理,如果是异步unless不生效。

@Cacheable(cacheNames = "emp")//需要指定一下.
public Employee getEmp(Integer id)
{
System.out.println("查询"+id+"员工");
Employee emp = employeeMapper.getEmpById(id);
return emp;
}
Cacheable运行原理

1、从我们缓存的自动配置类入手,因为SpringBoot一切的都是从自动配置开始的。

@Import({ CacheConfigurationImportSelector.class, CacheManagerEntityManagerFactoryDependsOnPostProcessor.class })
public class CacheAutoConfiguration {
}

2、CacheConfigurationImportSelector类会往容器中导入一些缓存用到的组件

static class CacheConfigurationImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
CacheType[] types = CacheType.values();
String[] imports = new String[types.length];
for (int i = 0; i < types.length; i++) {
imports[i] = CacheConfigurations.getConfigurationClass(types[i]);
}
return imports;
}
}

这里导入了很多缓存的配置信息

//org.springframework.boot.autoconfigure.cache.GenericCacheConfiguration
//"org.springframework.boot.autoconfigure.cache.JCacheCacheConfiguration"
//"org.springframework.boot.autoconfigure.cache.EhCacheCacheConfiguration"
//"org.springframework.boot.autoconfigure.cache.HazelcastCacheConfiguration"
//"org.springframework.boot.autoconfigure.cache.InfinispanCacheConfiguration"
//"org.springframework.boot.autoconfigure.cache.RedisCacheConfiguration"
//"org.springframework.boot.autoconfigure.cache.CouchbaseCacheConfiguration"
//"org.springframework.boot.autoconfigure.cache.CaffeineCacheConfiguration"
//"org.springframework.boot.autoconfigure.cache.SimpleCacheConfiguration"
//"org.springframework.boot.autoconfigure.cache.NoOpCacheConfiguration"

3、我们在配置文件中把debug配置为true,会发现SimpleCacheConfiguration生效了。给容器中注册了一个CacheManager:ConcurrentMapCacheManager

@Configuration(proxyBeanMethods = false)
@ConditionalOnMissingBean(CacheManager.class)
@Conditional(CacheCondition.class)
class SimpleCacheConfiguration {
//往容器中注册了一个ConcurrentMapCacheManager,缓存管理
@Bean
ConcurrentMapCacheManager cacheManager(CacheProperties cacheProperties,
CacheManagerCustomizers cacheManagerCustomizers) {
ConcurrentMapCacheManager cacheManager = new ConcurrentMapCacheManager();
List<String> cacheNames = cacheProperties.getCacheNames();
if (!cacheNames.isEmpty()) {
cacheManager.setCacheNames(cacheNames);
}
return cacheManagerCustomizers.customize(cacheManager);
}

}

4、 ConcurrentMapCacheManager的作用是,可以获取和创建ConcurrentMapCache类型的缓存组件

public class ConcurrentMapCacheManager implements CacheManager, BeanClassLoaderAware {
protected Cache createConcurrentMapCache(String name) {
SerializationDelegate actualSerialization = (isStoreByValue() ? this.serialization : null);
return new ConcurrentMapCache(name, new ConcurrentHashMap<>(256), //创建缓存组件
isAllowNullValues(), actualSerialization);

}
}

5、ConcurrentMapCache的作用就是把数据存放在ConcurrentMap中,

public class ConcurrentMapCache extends AbstractValueAdaptingCache {
private final ConcurrentMap<Object, Object> store; //存放数据
...
@Override
@Nullable
protected Object lookup(Object key) {
return this.store.get(key);
}
@Override
public void put(Object key, @Nullable Object value) {
this.store.put(key, toStoreValue(value));
}
...
}

6、缓存运行流程:

  • 在调用方法之前会进入ConcurrentMapCacheManager的getcache方法中,先去查询Cache(缓存组件),按照cacheNames指定的名字获取,第一次获取的时候如果没有cache组件会自动创建一个。
    @Override
    @Nullable
    public Cache getCache(String name) {//会去找cacheable的name
    Cache cache = this.cacheMap.get(name);//第一次是没有的
    if (cache == null && this.dynamic) {
    synchronized (this.cacheMap) {//这里创建缓存的时候是线程安全的
    cache = this.cacheMap.get(name);
    if (cache == null) {//第一次获取缓存会创建一个
    cache = createConcurrentMapCache(name);
    this.cacheMap.put(name, cache);
    }
    }
    }
    return cache;
    }
  • 去cache中查找缓存的内存,使用一个key:默认就是方法的参数,默认使用keyGenerator生成的,默认使用SimpleKeyGenerator生成key
    //  去Cache中查找
    @Override
    @Nullable
    protected Object lookup(Object key) {//这里的key就是默认为方法的参数
    return this.store.get(key);
    }
    //那么这个key是如何生成的呢,SimpleKeyGenerator的生成策略
    public class SimpleKeyGenerator implements KeyGenerator {
    @Override
    public Object generate(Object target, Method method, Object... params) {
    return generateKey(params);
    }
    /**
    * Generate a key based on the specified parameters.
    */
    public static Object generateKey(Object... params) {
    if (params.length == 0) {//如果参数是0,使用空值
    return SimpleKey.EMPTY;
    }
    if (params.length == 1) {
    Object param = params[0];
    if (param != null && !param.getClass().isArray()) {
    return param;
    }
    }
    return new SimpleKey(params);//根据参数来进行设置
    }
    }
  • 没有查找到缓存,就调用目标方法
  • 将目标方法返回的结果放进缓存中,调用ConcurrentMapCacheManager的put方法放到内存中。
  • @Cacheable标注的方法执行之前先来检查缓存中是不是有这个数据,默认按照参数的值作为key去查询缓存,如果没有就运行方法,并将结果放入缓存。
  • 第二次执行的之后就直接从缓存中进行查找。

CachePut注解使用

CachePut:既调用方法,又更新缓存数据。使用场景就是修改了数据库的某个数据,同时要更新缓存

  • 运行时机:

    • 调用目标方法
    • 将目标方法缓存起来
    • 不同于Cacheable先走注解,再走方法,CachePut是先走方法,返回值直接缓存
  • 注意事项:

    • 一般CachePut是与Cacheable配合使用的,那么就是说我Cacheable缓存的结果想要通过CachePut修改,key值必须相同,且cacheNames也要相同才能找到进行修改。
@CachePut(cacheNames = "emp",key = "#employee.id")//这里想更新原有的Cacheable放进去的缓存,需要设置相同的key和cachename
public Employee updateEmp(Employee employee)
{
employeeMapper.updateEmp(employee);
return employee;
}

CacheEvict注解使用

用于删除缓存中的数据。默认在方法之后执行。

  • CacheEvict特有属性:
    • allEntries = true: 指定清除缓存中所有的数据。
    • beforeInvocation = false 是否在方法之前清除
      @CacheEvict(cacheNames = "emp",key = "#id")//不写key默认是用方法参数
      public void deleteEmp(Integer id)
      {
      employeeMapper.deleteEmp(id);
      }

Caching注解使用

Caching注解包含了三个注解,主要是为了应对比较复杂的情况

public @interface Caching {

Cacheable[] cacheable() default {};

CachePut[] put() default {};

CacheEvict[] evict() default {};

}

示例如下:

//这样使用组合注解的方式进行注解开发
@Caching(
cacheable = {
@Cacheable(value = "emp",key = "#lastName")
},
put = {
@CachePut(value = "emp",key = "#result.id"),
@CachePut(value = "emp",key = "#result.lastName")
}
)
public Employee getEmployeeBylastName(String lastName)
{
return employeeMapper.getEmpByLastName(lastName);
}

CacheConfig注解使用

如果我们的方法都需要缓存注解,那么在类上加一个CacheConfig即可,可以指定一些公共的属性。

@CacheConfig(cacheNames = "emp")//在类上面进行标记以后,下面方法的就不需要标记了。
public class EmployeeService {
}

SpringBoot整合Redis

在实际开发中一般使用缓存中间件的方式进行开发
关于redis的使用可以查看官网:http://www.redis.cn/commands.html

  • 如果想要使用redis,需要引入redis的starter

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

    这样就将redis的starter引入进来了。

  • 在配置文件中对redis进行配置

    spring:
    redis:
    host: 192.168.1.8

    这是Redis中常见的配置。

    #redis
    # Redis服务器地址
    spring.redis.host=10.11.12.237
    # Redis服务器连接端口
    spring.redis.port=6379
    # Redis数据库索引(默认为0)
    spring.redis.database=0
    # Redis服务器连接密码(默认为空)
    spring.redis.password=
    # 连接超时时间(毫秒)
    spring.redis.timeout=10000

    # 以下连接池已在SpringBoot2.0不推荐使用
    #spring.redis.pool.max-active=8
    #spring.redis.pool.max-wait=-1
    #spring.redis.pool.max-idle=8
    #spring.redis.pool.min-idle=0

    # Jedis
    #spring.redis.jredis.max-active=8
    #spring.redis.jredis.max-wait=10000
    #spring.redis.jredis.max-idle=8
    #spring.redis.jredis.min-idle=0

    # Lettuce
    # 连接池最大连接数(使用负值表示没有限制)
    spring.redis.lettuce.pool.max-active=8
    # 连接池最大阻塞等待时间(使用负值表示没有限制)
    spring.redis.lettuce.pool.max-wait=10000
    # 连接池中的最大空闲连接
    spring.redis.lettuce.pool.max-idle=8
    # 连接池中的最小空闲连接
    spring.redis.lettuce.pool.min-idle=0
    # 关闭超时时间
    spring.redis.lettuce.shutdown-timeout=100
  • 测试redis是否配置正确

    @Autowired
    StringRedisTemplate stringRedisTemplate;//操作字符串
    @Autowired
    RedisTemplate redisTemplate;//操作对象
    /*
    Redis常见的五大数据结构
    String(字符串),List(列表),Set(集合),Hash(散列),Zset(有序集合)
    stringRedisTemplate.opsForValue();[操作字符串]
    */
    @Test
    public void redistest()
    {
    stringRedisTemplate.opsForValue().set("msg","你好");
    }

    使用客户端进行查看,就可以查看到相应的信息,这里的使用都对应这redis中的操作可以去官网进行查看

  • 对象的序列化问题
    因为在序列化的时候RedisTemplate默认使用的序列化器是JDK的序列化器,这样会导致乱码。
    1、第一种方法就是就是序列化成json然后用字符串的方式进行保存
    2、就是实现自己的序列化器替换掉Redis客户端使用的默认序列化器。这种是直接使用RedisTemplate时生效。

    @Configuration
    public class MyRedisConfig {

    @Bean
    public RedisTemplate<Object, Employee> EmpredisTemplate(RedisConnectionFactory redisConnectionFactory)
    throws UnknownHostException {
    RedisTemplate<Object, Employee> template = new RedisTemplate<>();
    template.setConnectionFactory(redisConnectionFactory);
    template.setDefaultSerializer(new Jackson2JsonRedisSerializer<Employee>(Employee.class));
    return template;
    }
    }//实现自己额序列化器

    @Autowired
    RedisTemplate<Object,Employee> EmpredisTemplate ;
    @Test
    public void redistest()
    {
    Employee emp = employeeMapper.getEmpById(1);
    EmpredisTemplate.opsForValue().set("emp",emp);//测试类
    }

    这样就可以实现json的写入了。
    3、自定义RedisCacheManager,使用GenericJackson2JsonRedisSerializer类对value进行序列化,这种序列化起作用的入口是直接使用CacheManager获取Cache,利用Cache操作Redis时才生效。

    Cache cache = redisCacheManager.getCache("");
    cache.put();
    @Configuration
    public class MyRedisConfig {
    @Bean
    RedisCacheManager cacheManager(RedisConnectionFactory factory){
    //创建默认RedisCacheWriter
    RedisCacheWriter cacheWriter = RedisCacheWriter.nonLockingRedisCacheWriter(factory);

    //创建默认RedisCacheConfiguration并使用GenericJackson2JsonRedisSerializer构造的 SerializationPair对value进行转换
    //创建GenericJackson2JsonRedisSerializer的json序列化器
    GenericJackson2JsonRedisSerializer jsonRedisSerializer = new GenericJackson2JsonRedisSerializer();
    //使用json序列化器构造出对转换Object类型的SerializationPair序列化对
    RedisSerializationContext.SerializationPair<Object> serializationPair = RedisSerializationContext.SerializationPair.fromSerializer(jsonRedisSerializer);
    //将可以把Object转换为json的SerializationPair传入RedisCacheConfiguration
    //使得RedisCacheConfiguration在转换value时使用定制序列化器
    RedisCacheConfiguration cacheConfiguration=RedisCacheConfiguration.defaultCacheConfig().serializeValuesWith(serializationPair);

    RedisCacheManager cacheManager = new RedisCacheManager(cacheWriter,cacheConfiguration);
    return cacheManager;
    }
    }

更多的关于redis的使用在redis里面进行讲解。

SpringBoot中Redis序列化原理

配置类RedisCacheConfiguration向容器中导入了其定制的RedisCacheManager,在默认的RedisCacheManager的配置中,是使用jdk序列化value值

@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(RedisConnectionFactory.class)
@AutoConfigureAfter(RedisAutoConfiguration.class)
@ConditionalOnBean(RedisConnectionFactory.class)
@ConditionalOnMissingBean(CacheManager.class)
@Conditional(CacheCondition.class)
class RedisCacheConfiguration {

//向容器中导入RedisCacheManager
@Bean
RedisCacheManager cacheManager(CacheProperties cacheProperties, CacheManagerCustomizers cacheManagerCustomizers,
ObjectProvider<org.springframework.data.redis.cache.RedisCacheConfiguration> redisCacheConfiguration,
ObjectProvider<RedisCacheManagerBuilderCustomizer> redisCacheManagerBuilderCustomizers,
RedisConnectionFactory redisConnectionFactory, ResourceLoader resourceLoader) {
//使用determineConfiguration()的返回值生成RedisCacheManagerBuilder
//调用了RedisCacheManagerBuilder的cacheDefaults()方法(见下一代码块)
RedisCacheManagerBuilder builder = RedisCacheManager.builder(redisConnectionFactory).cacheDefaults(
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));
//使用RedisCacheManagerBuilder的build()方法创建RedisCacheManager并进行定制操作
return cacheManagerCustomizers.customize(builder.build());
}


private org.springframework.data.redis.cache.RedisCacheConfiguration determineConfiguration(
CacheProperties cacheProperties,
ObjectProvider<org.springframework.data.redis.cache.RedisCacheConfiguration> redisCacheConfiguration,
ClassLoader classLoader) {
//determineConfiguration()调用了createConfiguration()
return redisCacheConfiguration.getIfAvailable(() -> createConfiguration(cacheProperties, classLoader));
}


//createConfiguration()定义了其序列化value的规则
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();
//使用jdk序列化器对value进行序列化
config = config.serializeValuesWith(
SerializationPair.fromSerializer(new JdkSerializationRedisSerializer(classLoader)));
//设置properties文件中设置的各项属性
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;
}

}

RedisCacheManager的直接构造类,该类保存了配置类RedisCacheConfiguration,该配置在会传递给RedisCacheManager,RedisCacheManager是通过RedisCacheManagerBuilder创建的

public static class RedisCacheManagerBuilder {

private final RedisCacheWriter cacheWriter;
//默认缓存配置使用RedisCacheConfiguration的默认配置
//该默认配置缓存时默认将k按字符串存储,v按jdk序列化数据存储(见下一代码块)
private RedisCacheConfiguration defaultCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig();
private final Map<String, RedisCacheConfiguration> initialCaches = new LinkedHashMap<>();
private boolean enableTransactions;
boolean allowInFlightCacheCreation = true;

private RedisCacheManagerBuilder(RedisCacheWriter cacheWriter) {
this.cacheWriter = cacheWriter;
}


//传入RedisCacheManagerBuilder使用的缓存配置规则RedisCacheConfiguration类
public RedisCacheManagerBuilder cacheDefaults(RedisCacheConfiguration defaultCacheConfiguration) {

Assert.notNull(defaultCacheConfiguration, "DefaultCacheConfiguration must not be null!");

this.defaultCacheConfiguration = defaultCacheConfiguration;

return this;
}


//使用默认defaultCacheConfiguration创建RedisCacheManager
public RedisCacheManager build() {

RedisCacheManager cm = new RedisCacheManager(cacheWriter, defaultCacheConfiguration, initialCaches,
allowInFlightCacheCreation);

cm.setTransactionAware(enableTransactions);

return cm;
}
}

RedisCacheConfiguration保存了许多缓存规则,这些规则都保存在RedisCacheManagerBuilder的RedisCacheConfiguration defaultCacheConfiguration属性中,并且当RedisCacheManagerBuilder创建RedisCacheManager传递过去

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;

//默认缓存配置
public static RedisCacheConfiguration defaultCacheConfig(@Nullable ClassLoader classLoader) {

DefaultFormattingConversionService conversionService = new DefaultFormattingConversionService();

registerDefaultConverters(conversionService);

return new RedisCacheConfiguration(Duration.ZERO, true, true, CacheKeyPrefix.simple(),
SerializationPair.fromSerializer(RedisSerializer.string()),//key使用字符串
SerializationPair.fromSerializer(RedisSerializer.java(classLoader)), conversionService);
//value按jdk序列化存储
//static RedisSerializer<Object> java(@Nullable ClassLoader classLoader) {
//return new JdkSerializationRedisSerializer(classLoader);
}
}
}

RedisCacheManager在创建RedisCache时将RedisCacheConfiguration传递过去,并在创建RedisCache时通过createRedisCache()起作用

public class RedisCacheManager extends AbstractTransactionSupportingCacheManager {

private final RedisCacheWriter cacheWriter;
private final RedisCacheConfiguration defaultCacheConfig;
private final Map<String, RedisCacheConfiguration> initialCacheConfiguration;
private final boolean allowInFlightCacheCreation;

protected RedisCache createRedisCache(String name, @Nullable RedisCacheConfiguration cacheConfig) {
//如果调用该方法时RedisCacheConfiguration有值则使用定制的,否则则使用默认的RedisCacheConfiguration defaultCacheConfig,即RedisCacheManagerBuilder传递过来的配置
return new RedisCache(name, cacheWriter, cacheConfig != null ? cacheConfig : defaultCacheConfig);
}
}

RedisCache,Redis缓存,具体负责将缓存数据序列化的地方,将RedisCacheConfiguration的序列化对SerializationPair提取出来并使用其定义的序列化方式分别对k和v进行序列化操作

public class RedisCache extends AbstractValueAdaptingCache {

private static final byte[] BINARY_NULL_VALUE = RedisSerializer.java().serialize(NullValue.INSTANCE);

private final String name;
private final RedisCacheWriter cacheWriter;
private final RedisCacheConfiguration cacheConfig;
private final ConversionService conversionService;

public void put(Object key, @Nullable Object value) {

Object cacheValue = preProcessCacheValue(value);

if (!isAllowNullValues() && cacheValue == null) {

throw new IllegalArgumentException(String.format(
"Cache '%s' does not allow 'null' values. Avoid storing null via '@Cacheable(unless=\"#result == null\")' or configure RedisCache to allow 'null' via RedisCacheConfiguration.",
name));
}

//在put k-v时使用cacheConfig中的k-v序列化器分别对k-v进行序列化
cacheWriter.put(name, createAndConvertCacheKey(key), serializeCacheValue(cacheValue), cacheConfig.getTtl());
}

//从cacheConfig(即RedisCacheConfiguration)中获取KeySerializationPair并写入key值
protected byte[] serializeCacheKey(String cacheKey) {
return ByteUtils.getBytes(cacheConfig.getKeySerializationPair().write(cacheKey));
}


//从cacheConfig(即RedisCacheConfiguration)中获取ValueSerializationPair并写入key值
protected byte[] serializeCacheValue(Object value) {

if (isAllowNullValues() && value instanceof NullValue) {
return BINARY_NULL_VALUE;
}

return ByteUtils.getBytes(cacheConfig.getValueSerializationPair().write(value));
}
}

分析到这也就不难理解,要使用json保存序列化数据时,需要自定义RedisCacheManager,在RedisCacheConfiguration中定义序列化转化规则,并向RedisCacheManager传入我们自己定制的RedisCacheConfiguration了,我定制的序列化规则会跟随RedisCacheConfiguration一直传递到RedisCache,并在序列化时发挥作用。

文章作者: zenshin
文章链接: https://zlh.giserhub.com/2020/04/24/springboot/cache/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 zenshin's blog
打赏
  • 微信
    微信
  • 支付宝
    支付宝

评论