您现在的位置是:首页 >技术交流 >SpringBoot与RedisCacheManager整合网站首页技术交流
SpringBoot与RedisCacheManager整合
本文根据众多互联网博客内容整理后形成,引用内容的版权归原始作者所有,仅限于学习研究使用
SpringBoot 缓存管理器CacheManager
从3.1开始Spring定义了org.springframework.cache.Cache
和org.springframework.cache.CacheManager
接口来统一不同的缓存技术;并支持使用JCache(JSR-107)
注解简化开发.
- Cache接口为缓存的组件规范定义,包含缓存的各种操作集合;
- Cache接口下Spring提供了各种xxxCache的实现;如
RedisCache
,EhCacheCache
,ConcurrentMapCache
等;
快速开始
1、导入Maven
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-cache</artifactId> </dependency> <!-- redis --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-pool2</artifactId> </dependency> |
因为需要用RedisCacheManager
所以导入Redis依赖
2、配置Redis并开启缓存支持
import com.fasterxml.jackson.annotation.JsonAutoDetect; import com.fasterxml.jackson.annotation.PropertyAccessor; import com.fasterxml.jackson.databind.ObjectMapper; import org.springframework.cache.CacheManager; import org.springframework.cache.annotation.CachingConfigurerSupport; import org.springframework.cache.annotation.EnableCaching; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.cache.RedisCacheConfiguration; import org.springframework.data.redis.cache.RedisCacheManager; import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer; import org.springframework.data.redis.serializer.RedisSerializationContext; import org.springframework.data.redis.serializer.RedisSerializer; import org.springframework.data.redis.serializer.StringRedisSerializer; import javax.annotation.Resource; import java.time.Duration; /** * * @author rstyro * @time 2018-07-31 * */ @Configuration @EnableCaching // 开启缓存支持 public class RedisConfig extends CachingConfigurerSupport { @Resource private LettuceConnectionFactory lettuceConnectionFactory; /** * 配置CacheManager * @return */ @Bean public CacheManager cacheManager() { RedisSerializer<String> redisSerializer = new StringRedisSerializer(); Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class); //解决查询缓存转换异常的问题 ObjectMapper om = new ObjectMapper(); om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL); jackson2JsonRedisSerializer.setObjectMapper(om); // 配置序列化(解决乱码的问题) RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig() // .entryTtl(Duration.ZERO) .entryTtl(Duration.ofSeconds(20)) //设置缓存失效时间 .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer)) .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer)) .disableCachingNullValues(); RedisCacheManager cacheManager = RedisCacheManager.builder(lettuceConnectionFactory) .cacheDefaults(config) .build(); return cacheManager; } /** * RedisTemplate配置 */ @Bean public RedisTemplate<String, Object> redisTemplate(LettuceConnectionFactory lettuceConnectionFactory) { // 设置序列化 Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<Object>( Object.class); ObjectMapper om = new ObjectMapper(); om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL); jackson2JsonRedisSerializer.setObjectMapper(om); // 配置redisTemplate RedisTemplate<String, Object> redisTemplate = new RedisTemplate<String, Object>(); redisTemplate.setConnectionFactory(lettuceConnectionFactory); RedisSerializer<?> stringSerializer = new StringRedisSerializer(); redisTemplate.setKeySerializer(stringSerializer);// key序列化 redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);// value序列化 redisTemplate.setHashKeySerializer(stringSerializer);// Hash key序列化 redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);// Hash value序列化 redisTemplate.afterPropertiesSet(); return redisTemplate; } } |
缓存工作原理
当我们引入缓存的时候,SpringBoot的缓存自动配置CacheAutoConfiguration
就会生效
-
在
CacheAutoConfiguration
中的CacheConfigurationImportSelector
会导入很多缓存组件配置类 -
通过debug看源码,知道imports 的内容如下
static class CacheConfigurationImportSelector implements ImportSelector { CacheConfigurationImportSelector() { } 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; } } /** imports 如下 0 = "org.springframework.boot.autoconfigure.cache.GenericCacheConfiguration" 1 = "org.springframework.boot.autoconfigure.cache.JCacheCacheConfiguration" 2 = "org.springframework.boot.autoconfigure.cache.EhCacheCacheConfiguration" 3 = "org.springframework.boot.autoconfigure.cache.HazelcastCacheConfiguration" 4 = "org.springframework.boot.autoconfigure.cache.InfinispanCacheConfiguration" 5 = "org.springframework.boot.autoconfigure.cache.CouchbaseCacheConfiguration" 6 = "org.springframework.boot.autoconfigure.cache.RedisCacheConfiguration" 7 = "org.springframework.boot.autoconfigure.cache.CaffeineCacheConfiguration" 8 = "org.springframework.boot.autoconfigure.cache.SimpleCacheConfiguration" 9 = "org.springframework.boot.autoconfigure.cache.NoOpCacheConfiguration" */
-
如果没有引
Redis
, 则SimpleCacheConfiguration
这个就是默认的配置类 -
在
SimpleCacheConfiguration
注入了一个ConcurrentMapCacheManager
-
ConcurrentMapCacheManager
实现了CacheManager
接口 -
ConcurrentMapCacheManager
通过ConcurrentHashMap
把数据缓存起来 -
在
CacheManager
有一个Cache getCache(String var1)
方法换取缓存 -
流程大概就这里,感兴趣的同学可以打断点阅读源码
代码示例地址
- Github: https://github.com/rstyro/Springboot/tree/master/SpringBoot-RedisCacheManager
- Gitee : spring-boot: springboot 和一些主流框架的整合的各个基本demo - Gitee.com
例子2
1、向pom文件中添加依赖
<!--springboot中的redis依赖--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency>
2、application.yml添加配置
spring: redis: host: localhost # Redis服务器地址 database: 0 # Redis数据库索引(默认为0) port: 6379 # Redis服务器连接端口 password: ld123456 # Redis服务器连接密码(默认为空)
3、RedisConfig配置类
package com.donleo.mybatis.config;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import java.util.Arrays;
/**
* @author liangd
* date 2020-12-04 16:15
* code Redis配置类
*/
@Configuration
@EnableCaching
public class RedisConfig extends CachingConfigurerSupport {
/**
* 配置redisTemplate 代替默认配置
* @param redisConnectionFactory RedisConnectionFactory
* @return RedisTemplate
*/
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory);
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;
}
/**
*
* 配置 cacheManager 代替默认的cacheManager (缓存管理器)
* @param factory RedisConnectionFactory
* @return CacheManager
*/
@Bean
public CacheManager cacheManager(RedisConnectionFactory factory) {
RedisSerializer<String> redisSerializer = new StringRedisSerializer();
Jackson2JsonRedisSerializer serializer = new Jackson2JsonRedisSerializer(Object.class);
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
serializer.setObjectMapper(objectMapper);
// 配置序列化
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer))
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(serializer));
RedisCacheManager cacheManager = RedisCacheManager.builder(factory)
.cacheDefaults(config)
.build();
return cacheManager;
}
/**
* 配置KeyGenerator
* @return
*/
@Bean
@Override
public KeyGenerator keyGenerator() {
//lambda 表达式
return (target, method, params) -> method.getName() + Arrays.asList(params);
}
/*
// 1. 不需要参数,返回值为 5
() -> 5
// 2. 接收一个参数(数字类型),返回其2倍的值
x -> 2 * x
// 3. 接受2个参数(数字),并返回他们的差值
(x, y) -> x – y
// 4. 接收2个int型整数,返回他们的和
(int x, int y) -> x + y
// 5. 接受一个 string 对象,并在控制台打印,不返回任何值(看起来像是返回void)
(String s) -> System.out.print(s)
*/
/* @Bean
@Override
public KeyGenerator keyGenerator() {
return (target, method, params)->{
return method.getName()+ Arrays.asList(params);
};
}*/
}
KeyGenerator
/**
* 生产key的策略
* @return
*/
@Bean
public KeyGenerator wiselyKeyGenerator() {
return new KeyGenerator() {
@Override
public Object generate(Object target, Method method, Object... params) {
StringBuilder sb = new StringBuilder();
sb.append(target.getClass().getName());
sb.append(method.getName());
for (Object obj : params) {
sb.append(obj.toString());
}
return sb.toString();
}
};
}
例子3
针对基于Redis API的RedisTemplate进行了自定义序列化机制的改进,从而实现了相对熟悉的JSON格式缓存数据,但是这种自定义的RedisTemplate对于基于注解的Redis缓存实现来说,是没有作用的,读者可以自行演示。接下来,针对基于注解的Redis缓存机制和自定义序列化方式的实现进行讲解。
1.Redis注解默认序列化机制
打开Spring Boot整合Redis组件提供的缓存自动配置类RedisCacheConfiguration(org.springframework.boot.autoconfigure.cache包下的),查看该类的源码信息,其核心代码如下。
@Configuration
class RedisCacheConfiguration {
@Bean
public RedisCacheManager cacheManager(RedisConnectionFactory
redisConnectionFactory,ResourceLoader resourceLoader) {
RedisCacheManagerBuilder builder =
RedisCacheManager.builder(redisConnectionFactory)
.cacheDefaults(this.determineConfiguration(resourceLoader.getClassLoader()));
List<String> cacheNames = this.cacheProperties.getCacheNames();
if(!cacheNames.isEmpty()) {
builder.initialCacheNames(new LinkedHashSet(cacheNames));
}
return (RedisCacheManager)this.customizerInvoker.customize(builder.build());
}
private org.springframework.data.redis.cache.RedisCacheConfiguration
determineConfiguration(ClassLoader classLoader){
if(this.redisCacheConfiguration != null) {
return this.redisCacheConfiguration;
} else {
Redis redisProperties = this.cacheProperties.getRedis();
org.springframework.data.redis.cache.RedisCacheConfiguration config =
org.springframework.data.redis.cache.RedisCacheConfiguration.defaultCacheConfig();
config = config.serializeValuesWith(SerializationPair.fromSerializer(
new JdkSerializationRedisSerializer(classLoader)));
...
return config;
}
}
}
从上述核心源码中可以看出,同RedisTemplate核心源码类似,RedisCacheConfiguration内部同样通过Redis连接工厂RedisConnectionFactory定义了一个缓存管理器RedisCacheManager;同时定制RedisCacheManager时,也默认使用了JdkSerializationRedisSerializer序列化方式。
如果想要使用自定义序列化方式的RedisCacheManager进行数据缓存操作,可以参考上述核心代码创建一个名为cacheManager的Bean组件,并在该组件中设置对应的序列化方式即可。
需要注意的是,本书使用的是Spring Boot 2.X版本进行使用讲解,这与Spring Boot 1.X版本中的一些实现原理并不相同。例如上述RedisCacheManager的构建方式,在Spring Boot 1.X版本中的源码如下。
@Bean
public RedisCacheManager cacheManager(RedisTemplate<Object, Object> redisTemplate) {
RedisCacheManager cacheManager = new RedisCacheManager(redisTemplate);
cacheManager.setUsePrefix(true);
List<String> cacheNames = this.cacheProperties.getCacheNames();
if(!cacheNames.isEmpty()) {
cacheManager.setCacheNames(cacheNames);
}
return (RedisCacheManager)this.customizerInvoker.customize(cacheManager);
}
从这两个版本的源码对比可以看出,Spring Boot 1.X版本中,RedisCacheManager是在RedisTemplate的基础上述进行构建的,而Spring Boot 2.X版本中,RedisCacheManager是单独进行构建的。因此,在Spring Boot 2.X版本中,对RedisTemplate进行自定义序列化机制构建后,仍然无法对RedisCacheManager内部默认序列化机制进行覆盖(这也就解释了基于注解的Redis缓存实现仍然会使用JDK默认序列化机制的原因),想要基于注解的Redis缓存实现也使用自定义序列化机制,需要自定义RedisCacheManager。
2.自定义RedisCacheManager
在chapter06项目的Redis配置类RedisConfig中,按照上一步分析的定制方法自定义名为cacheManager的Bean组件,示例代码如下:
@Bean
public RedisCacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
// 分别创建String和JSON格式序列化对象,对缓存数据key和value进行转换
RedisSerializer<String> strSerializer = new StringRedisSerializer();
Jackson2JsonRedisSerializer jacksonSeial =
new Jackson2JsonRedisSerializer(Object.class);
// 解决查询缓存转换异常的问题
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jacksonSeial.setObjectMapper(om);
// 定制缓存数据序列化方式及时效
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofDays(1))
.serializeKeysWith(RedisSerializationContext.SerializationPair
.fromSerializer(strSerializer))
.serializeValuesWith(RedisSerializationContext.SerializationPair
.fromSerializer(jacksonSeial))
.disableCachingNullValues();
RedisCacheManager cacheManager = RedisCacheManager
.builder(redisConnectionFactory).cacheDefaults(config).build();
return cacheManager;
}
上述代码中,在RedisConfig配置类中使用@Bean注解注入了一个默认名称为方法名的cacheManager组件。在定义的Bean组件中,通过RedisCacheConfiguration对缓存数据的key和value分别进行了序列化方式的定制,其中缓存数据的key定制为StringRedisSerializer(即String格式),而value定制为了Jackson2JsonRedisSerializer(即JSON格式),同时还使用entryTtl(Duration.ofDays(1))方法将缓存数据有效期设置为1天。