Spring-Boot-ReactiveRedisTemplate自动配置定义和序列化方式选择

背景

Spring-Boot的Redis自动配置 类,RedisReactiveAutoConfigurationRedisAutoConfiguration,组件ReactiveRedisTemplate<Object, Object>RedisTemplate<Object, Object>默认使用JDK序列化方式 ,在现实业务场景中很难使用,其存储的值可读性差且又长 。我觉得不是很合理,意味着使用它们的用户都需要自己重新自定义。ReactiveStringRedisTemplateStringRedisTemplate使用String序列化方式,是合理的。

环境版本

  • spring-boot-starter-parent-2.7.16
  • jdk-11

Redis自动配置定义

结合RedisReactiveAutoConfigurationRedisAutoConfiguration源代码,最简化地重新自定义,序列化方式使用StringJSON

java 复制代码
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()");

        // 对象序列化/反序列化
        // 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;
    }

}
yaml 复制代码
# Appendices
# Appendix A: Common Application Properties

# .A.1. Core Properties
spring:
  application:
    name: redis-spring-boot-starter-example
#  profiles:
#    active: prod,test,dev
# 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>

序列化方式使用StringJSON

RedisSerializer.java()

new JdkSerializationRedisSerializer(null)

不推荐,值可读性差且又长!

shell 复制代码
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()

推荐

类型信息

shell 复制代码
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))

不推荐,有坑!

类型信息丢失,读取时对象类型转换失败

shell 复制代码
localhost:6379> GET "user:user-info::123456"
"{\"id\":123456,\"phone\":\"13666555888\",\"nickName\":\"\xe6\x9d\x8e\xe5\x9b\x9b\"}"
shell 复制代码
java.lang.ClassCastException: class java.util.LinkedHashMap cannot be cast to class com.spring.boot.redis.example.model.UserDto

FastJson

GenericFastJsonRedisSerializer

new GenericFastJsonRedisSerializer()

推荐

类型信息

shell 复制代码
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))

不推荐,有坑!

类型信息丢失,读取时对象类型转换失败

shell 复制代码
localhost:6379> GET "user:user-info::123456"
"{\"id\":123456,\"nickName\":\"\xe6\x9d\x8e\xe5\x9b\x9b\",\"phone\":\"13666555888\"}"
shell 复制代码
java.lang.ClassCastException: class com.alibaba.fastjson.JSONObject cannot be cast to class com.spring.boot.redis.example.model.UserDto

参考

祝大家玩得开心!

相关推荐
用户83249514173214 分钟前
Spring Boot 实现 Redis 多数据库切换(多数据源配置)
redis
seventeennnnn2 小时前
谢飞机的Java高级开发面试:从Spring Boot到分布式架构的蜕变之旅
spring boot·微服务架构·java面试·分布式系统·电商支付
超级小忍3 小时前
服务端向客户端主动推送数据的几种方法(Spring Boot 环境)
java·spring boot·后端
时间会给答案scidag4 小时前
报错 400 和405解决方案
vue.js·spring boot
Wyc724095 小时前
SpringBoot
java·spring boot·spring
傲祥Ax5 小时前
Redis总结
数据库·redis·redis重点总结
ladymorgana6 小时前
【Spring Boot】HikariCP 连接池 YAML 配置详解
spring boot·后端·mysql·连接池·hikaricp
GJCTYU8 小时前
spring中@Transactional注解和事务的实战理解附代码
数据库·spring boot·后端·spring·oracle·mybatis
风象南9 小时前
SpringBoot敏感配置项加密与解密实战
java·spring boot·后端
写不出来就跑路10 小时前
暑期实习感悟与经验分享:从校园到职场的成长之路
java·开发语言·经验分享·spring boot