Spring-Boot-ReactiveRedisTemplate自动配置定义和序列化方式选择
背景
Spring-Boot的Redis自动配置类,RedisReactiveAutoConfiguration
和RedisAutoConfiguration
,组件ReactiveRedisTemplate<Object, Object>
和RedisTemplate<Object, Object>
默认使用JDK序列化方式,在现实业务场景中很难使用,其存储的值可读性差且又长。我觉得不是很合理,意味着使用它们的用户都需要自己重新自定义。ReactiveStringRedisTemplate
和StringRedisTemplate
使用String序列化方式,是合理的。
环境版本
- spring-boot-starter-parent-2.7.16
- jdk-11
Redis自动配置定义
结合RedisReactiveAutoConfiguration
和RedisAutoConfiguration
源代码,最简化地重新自定义,序列化方式使用String
和JSON
。
网络上有很多Redis配置的文章,为何还要再写呢?
看了一些相关文章,都没有充分发挥Spring Boot官方的自动配置模块功能。
我是充分结合官方自动配置声明,以最简单最少的配置,使用好Redis。
package com.spring.boot.redis.example.config;
import com.alibaba.fastjson.support.spring.GenericFastJsonRedisSerializer;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.cache.CacheManagerCustomizers;
import org.springframework.boot.autoconfigure.cache.CacheProperties;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration;
import org.springframework.boot.autoconfigure.data.redis.RedisReactiveAutoConfiguration;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ResourceLoader;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.connection.ReactiveRedisConnectionFactory;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.ReactiveRedisTemplate;
import org.springframework.data.redis.core.ReactiveStringRedisTemplate;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.RedisSerializationContext.SerializationPair;
import org.springframework.data.redis.serializer.RedisSerializer;
/**
* Redis配置
*
* @author guang.yi
* @since 2023/7/30
* @see org.springframework.boot.autoconfigure.data.redis.RedisReactiveAutoConfiguration
* @see org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration
* @see org.springframework.boot.autoconfigure.cache.RedisCacheConfiguration
*/
@Slf4j
@EnableCaching
@Configuration(proxyBeanMethods = false)
@AutoConfigureBefore({RedisAutoConfiguration.class, RedisReactiveAutoConfiguration.class})
public class RedisConfiguration {
public RedisConfiguration() {
log.info("create RedisConfiguration");
}
/**
* {@link org.springframework.boot.autoconfigure.cache.RedisCacheConfiguration#cacheManager(CacheProperties, CacheManagerCustomizers, ObjectProvider, ObjectProvider, RedisConnectionFactory, ResourceLoader)}
* <pre>
* ObjectProvider<org.springframework.data.redis.cache.RedisCacheConfiguration> redisCacheConfiguration
* ObjectProvider<RedisCacheManagerBuilderCustomizer> redisCacheManagerBuilderCustomizers
* 对象提供者,可自定义创建组件
* </pre>
*
* @see org.springframework.boot.autoconfigure.cache.RedisCacheConfiguration#createConfiguration
* @see org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer
* @see com.alibaba.fastjson.support.spring.GenericFastJsonRedisSerializer
* @see org.springframework.data.redis.cache.CacheKeyPrefix#compute
*/
@Bean
@ConditionalOnMissingBean
public RedisCacheConfiguration redisCacheConfiguration(CacheProperties cacheProperties) {
log.info("call redisCacheConfiguration()");
// 对象序列化/反序列化
// keySerializationPair,默认是 RedisSerializer.string()
// valueSerializationPair,默认是 RedisSerializer.java()
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig();
// RedisSerializer.json()
// GenericJackson2JsonRedisSerializer
config = config.serializeValuesWith(SerializationPair.fromSerializer(RedisSerializer.json()));
// // Jackson2JsonRedisSerializer - 不推荐,有坑!
// config = config.serializeValuesWith(SerializationPair.fromSerializer(new Jackson2JsonRedisSerializer<>(Object.class)));
// GenericFastJsonRedisSerializer
// config = config.serializeValuesWith(SerializationPair.fromSerializer(new GenericFastJsonRedisSerializer()));
// // FastJsonRedisSerializer - 不推荐,有坑!
// config = config.serializeValuesWith(SerializationPair.fromSerializer(new FastJsonRedisSerializer<>(Object.class)));
CacheProperties.Redis redisProperties = cacheProperties.getRedis();
if (redisProperties.getTimeToLive() != null) {
config = config.entryTtl(redisProperties.getTimeToLive());
}
if (redisProperties.getKeyPrefix() != null) {
config = config.prefixCacheNameWith(redisProperties.getKeyPrefix());
}
if (!redisProperties.isCacheNullValues()) {
config = config.disableCachingNullValues();
}
if (!redisProperties.isUseKeyPrefix()) {
config = config.disableKeyPrefix();
}
// // CacheKeyPrefix#compute
// config = config.computePrefixWith(cacheName -> cacheName + ':');
return config;
}
// RedisReactiveAutoConfiguration
/**
* {@code @ConditionalOnMissingBean(name = "reactiveRedisTemplate")}
* 表示组件可覆盖
*
* @see org.springframework.boot.autoconfigure.data.redis.RedisReactiveAutoConfiguration#reactiveRedisTemplate(ReactiveRedisConnectionFactory, ResourceLoader)
*/
@Bean
@ConditionalOnMissingBean(name = "reactiveRedisTemplate")
// @ConditionalOnBean(ReactiveRedisConnectionFactory.class)
public ReactiveRedisTemplate<Object, Object> reactiveRedisTemplate(
ReactiveRedisConnectionFactory reactiveRedisConnectionFactory) {
log.info("call reactiveRedisTemplate()");
// 对象序列化/反序列化
// 默认是 RedisSerializer.java()
// RedisSerializer.string()
// StringRedisSerializer.UTF_8
// RedisSerializer.json()
// GenericJackson2JsonRedisSerializer
RedisSerializationContext<Object, Object> serializationContext = RedisSerializationContext
.newSerializationContext(RedisSerializer.string())
.value(RedisSerializer.json())
.build();
return new ReactiveRedisTemplate<>(reactiveRedisConnectionFactory, serializationContext);
}
/**
* {@code @ConditionalOnMissingBean(name = "reactiveStringRedisTemplate")}
* 表示组件可覆盖
*
* @see org.springframework.boot.autoconfigure.data.redis.RedisReactiveAutoConfiguration#reactiveStringRedisTemplate(ReactiveRedisConnectionFactory)
*/
@Bean
@ConditionalOnMissingBean(name = "reactiveStringRedisTemplate")
// @ConditionalOnBean(ReactiveRedisConnectionFactory.class)
public ReactiveStringRedisTemplate reactiveStringRedisTemplate(
ReactiveRedisConnectionFactory reactiveRedisConnectionFactory) {
log.info("call reactiveStringRedisTemplate()");
// 对象序列化/反序列化
// 默认是 RedisSerializer.string()
// RedisSerializationContext.string()
return new ReactiveStringRedisTemplate(reactiveRedisConnectionFactory);
}
// RedisAutoConfiguration
/**
* {@code @ConditionalOnMissingBean(name = "redisTemplate")}
* 表示组件可覆盖
*
* @see org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration#redisTemplate(RedisConnectionFactory)
* @see RedisTemplate#afterPropertiesSet()
*/
@Bean
@ConditionalOnMissingBean(name = "redisTemplate")
// @ConditionalOnSingleCandidate(RedisConnectionFactory.class)
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
log.info("call redisTemplate()");
RedisTemplate<Object, Object> template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory);
// 对象序列化/反序列化
// 默认是 RedisSerializer.java()
template.setDefaultSerializer(RedisSerializer.string());
// template.setKeySerializer(RedisSerializer.string());
template.setValueSerializer(RedisSerializer.json());
// template.setHashKeySerializer(RedisSerializer.string());
// template.setHashValueSerializer(RedisSerializer.string());
return template;
}
/**
* {@code @ConditionalOnMissingBean}
* 表示组件可覆盖
* <pre>
* public StringRedisTemplate() {
* setKeySerializer(RedisSerializer.string());
* setValueSerializer(RedisSerializer.string());
* setHashKeySerializer(RedisSerializer.string());
* setHashValueSerializer(RedisSerializer.string());
* }
* </pre>
*
* @see org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration#stringRedisTemplate(RedisConnectionFactory)
* @see StringRedisTemplate#StringRedisTemplate()
*/
@Bean
@ConditionalOnMissingBean(name = "stringRedisTemplate")
// @ConditionalOnSingleCandidate(RedisConnectionFactory.class)
public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
log.info("call stringRedisTemplate()");
// 对象序列化/反序列化
// 默认是 RedisSerializer.string()
// return new StringRedisTemplate(redisConnectionFactory);
StringRedisTemplate template = new StringRedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
template.setDefaultSerializer(RedisSerializer.string());
return template;
}
}
# Appendices
# Appendix A: Common Application Properties
# .A.1. Core Properties
spring:
application:
name: redis-spring-boot-starter-example
# RedisProperties
redis:
database: 0
host: "localhost"
port: 6379
timeout: 1s
connect-timeout: 300ms
client-name: "user-example"
# client-type: lettuce
# sentinel:
# master: ""
# nodes: "host:port"
# cluster:
# nodes: "host:port"
# max-redirects: 3
# jedis:
# pool:
# enabled: true
# max-idle: 8
# min-idle: 0
# max-active: 8
# max-wait: 300ms
# time-between-eviction-runs: 5m
lettuce:
shutdown-timeout: 100ms
pool:
enabled: true
max-idle: 8
min-idle: 0
max-active: 8
max-wait: -1
time-between-eviction-runs: 5m
# CacheProperties
cache:
type: redis
# cache-names: ["user"]
redis:
time-to-live: 5m
# cache-null-values: true
key-prefix: "user:"
use-key-prefix: true
# enable-statistics: true
Redis序列化方式选择
org.springframework.data.redis.serializer.RedisSerializer<T>
序列化方式使用String
和JSON
RedisSerializer.java()
new JdkSerializationRedisSerializer(null)
不推荐,缓存值可读性差且又长!
localhost:6379> GET "user:user-info::123456"
"\xac\xed\x00\x05sr\x00+com.spring.boot.redis.example.model.UserDto\xb7\x8a\xefY\xa8\xccS`
\x02\x00\x03L\x00\x02idt\x00\x10Ljava/lang/Long;
L\x00\bnickNamet\x00\x12Ljava/lang/String;
L\x00\x05phoneq\x00~\x00\x02xpsr\x00\x0ejava.lang.Long;
\x8b\xe4\x90\xcc\x8f#\xdf\x02\x00\x01J\x00\x05
valuexr\x00\x10java.lang.Number\x86\xac\x95\x1d\x0b\x94\xe0\x8b\x02\x00\x00xp\x00\x00\x00\x00\x00\x01\xe2@t\x00\x06\xe6\x9d\x8e\xe5\x9b\x9bt\x00\x0b13666555888"
RedisSerializer.string()
StringRedisSerializer.UTF_8
new StringRedisSerializer(StandardCharsets.UTF_8)
推荐
Jackson
RedisSerializer.json()
new GenericJackson2JsonRedisSerializer()
推荐
类型信息
localhost:6379> GET "user:user-info::123456"
"{\"@class\":\"com.spring.boot.redis.example.model.UserDto\",\"id\":123456,\"phone\":\"13666555888\",\"nickName\":\"\xe6\x9d\x8e\xe5\x9b\x9b\"}"
Jackson2JsonRedisSerializer
new Jackson2JsonRedisSerializer<>(Object.class))
不推荐,有坑!
类型信息丢失,读取时对象类型转换失败
localhost:6379> GET "user:user-info::123456"
"{\"id\":123456,\"phone\":\"13666555888\",\"nickName\":\"\xe6\x9d\x8e\xe5\x9b\x9b\"}"
java.lang.ClassCastException: class java.util.LinkedHashMap cannot be cast to class com.spring.boot.redis.example.model.UserDto
FastJson
GenericFastJsonRedisSerializer
new GenericFastJsonRedisSerializer()
推荐
类型信息
localhost:6379> GET "user:user-info::123456"
"{\"@type\":\"com.spring.boot.redis.example.model.UserDto\",\"id\":123456L,\"nickName\":\"\xe6\x9d\x8e\xe5\x9b\x9b\",\"phone\":\"13666555888\"}"
FastJsonRedisSerializer
new FastJsonRedisSerializer<>(Object.class))
不推荐,有坑!
类型信息丢失,读取时对象类型转换失败
localhost:6379> GET "user:user-info::123456"
"{\"id\":123456,\"nickName\":\"\xe6\x9d\x8e\xe5\x9b\x9b\",\"phone\":\"13666555888\"}"
java.lang.ClassCastException: class com.alibaba.fastjson.JSONObject cannot be cast to class com.spring.boot.redis.example.model.UserDto
参考
- Spring Data Redis - Learn
- Spring Data Redis - Documentation
- redis-spring-boot-starter-example
祝大家玩得开心!