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

参考

祝大家玩得开心!

相关推荐
JH307342 分钟前
SpringBoot 优雅处理金额格式化:拦截器+自定义注解方案
java·spring boot·spring
qq_12498707534 小时前
基于SSM的动物保护系统的设计与实现(源码+论文+部署+安装)
java·数据库·spring boot·毕业设计·ssm·计算机毕业设计
Coder_Boy_4 小时前
基于SpringAI的在线考试系统-考试系统开发流程案例
java·数据库·人工智能·spring boot·后端
2301_818732064 小时前
前端调用控制层接口,进不去,报错415,类型不匹配
java·spring boot·spring·tomcat·intellij-idea
此生只爱蛋4 小时前
【Redis】主从复制
数据库·redis
汤姆yu8 小时前
基于springboot的尿毒症健康管理系统
java·spring boot·后端
暮色妖娆丶8 小时前
Spring 源码分析 单例 Bean 的创建过程
spring boot·后端·spring
biyezuopinvip9 小时前
基于Spring Boot的企业网盘的设计与实现(任务书)
java·spring boot·后端·vue·ssm·任务书·企业网盘的设计与实现
惊讶的猫9 小时前
redis分片集群
数据库·redis·缓存·分片集群·海量数据存储·高并发写
JavaGuide9 小时前
一款悄然崛起的国产规则引擎,让业务编排效率提升 10 倍!
java·spring boot