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

参考

祝大家玩得开心!

相关推荐
计算机-秋大田1 小时前
基于微信小程序的乡村研学游平台设计与实现,LW+源码+讲解
java·spring boot·微信小程序·小程序·vue
LuckyLay1 小时前
Spring学习笔记_36——@RequestMapping
java·spring boot·笔记·spring·mapping
哭哭啼2 小时前
Redis环境部署(主从模式、哨兵模式、集群模式)
数据库·redis·缓存
程序员小明z2 小时前
基于Java的药店管理系统
java·开发语言·spring boot·毕业设计·毕设
夜色呦2 小时前
现代电商解决方案:Spring Boot框架实践
数据库·spring boot·后端
爱敲代码的小冰3 小时前
spring boot 请求
java·spring boot·后端
明志致远淡泊宁静3 小时前
记录一次服务器redis被入侵
运维·服务器·redis
WuMingf_3 小时前
redis
数据库·redis
李少兄5 小时前
解决 Spring Boot 中 `Ambiguous mapping. Cannot map ‘xxxController‘ method` 错误
java·spring boot·后端
荆州克莱5 小时前
Big Data for AI实践:面向AI大模型开发和应用的大规模数据处理套件
spring boot·spring·spring cloud·css3·技术