update 使用Spring Redis Data自动配置项优化Redisson 4.X配置流程

update 优化Redisson自定义配置,Json序列化依赖升级至Jackson3.X
This commit is contained in:
秋辞未寒
2026-01-29 17:13:19 +08:00
parent 3797d9b8ed
commit 7c3a5c4a1d
6 changed files with 110 additions and 278 deletions

View File

@@ -107,29 +107,28 @@ spring.data:
timeout: 10s timeout: 10s
# 是否开启ssl # 是否开启ssl
ssl.enabled: false ssl.enabled: false
# redisson 配置 官方文档https://redisson.pro/docs/integration-with-spring/#spring-boot-starter
# redisson 配置 redisson:
redisson: # redis key前缀
# redis key前缀 keyPrefix:
keyPrefix: # 线程池数量
# 线程池数量 threads: 4
threads: 4 # Netty线程池数量
# Netty线程池数量 nettyThreads: 8
nettyThreads: 8 # 单节点配置
# 单节点配置 singleServerConfig:
singleServerConfig: # 客户端名称 不能用中文
# 客户端名称 不能用中文 clientName: RuoYi-Vue-Plus
clientName: RuoYi-Vue-Plus # 最小空闲连接数
# 最小空闲连接数 connectionMinimumIdleSize: 8
connectionMinimumIdleSize: 8 # 连接池大小
# 连接池大小 connectionPoolSize: 32
connectionPoolSize: 32 # 连接空闲超时,单位:毫秒
# 连接空闲超时,单位:毫秒 idleConnectionTimeout: 10000
idleConnectionTimeout: 10000 # 命令等待超时,单位:毫秒
# 命令等待超时,单位:毫秒 timeout: 3000
timeout: 3000 # 发布和订阅连接池大小
# 发布和订阅连接池大小 subscriptionConnectionPoolSize: 50
subscriptionConnectionPoolSize: 50
--- # mail 邮件发送 --- # mail 邮件发送
mail: mail:

View File

@@ -110,29 +110,28 @@ spring.data:
timeout: 10s timeout: 10s
# 是否开启ssl # 是否开启ssl
ssl.enabled: false ssl.enabled: false
# redisson 配置 官方文档https://redisson.pro/docs/integration-with-spring/#spring-boot-starter
# redisson 配置 redisson:
redisson: # redis key前缀
# redis key前缀 keyPrefix:
keyPrefix: # 线程池数量
# 线程池数量 threads: 16
threads: 16 # Netty线程池数量
# Netty线程池数量 nettyThreads: 32
nettyThreads: 32 # 单节点配置
# 单节点配置 singleServerConfig:
singleServerConfig: # 客户端名称 不能用中文
# 客户端名称 不能用中文 clientName: RuoYi-Vue-Plus
clientName: RuoYi-Vue-Plus # 最小空闲连接数
# 最小空闲连接数 connectionMinimumIdleSize: 32
connectionMinimumIdleSize: 32 # 连接池大小
# 连接池大小 connectionPoolSize: 64
connectionPoolSize: 64 # 连接空闲超时,单位:毫秒
# 连接空闲超时,单位:毫秒 idleConnectionTimeout: 10000
idleConnectionTimeout: 10000 # 命令等待超时,单位:毫秒
# 命令等待超时,单位:毫秒 timeout: 3000
timeout: 3000 # 发布和订阅连接池大小
# 发布和订阅连接池大小 subscriptionConnectionPoolSize: 50
subscriptionConnectionPoolSize: 50
--- # mail 邮件发送 --- # mail 邮件发送
mail: mail:

View File

@@ -45,8 +45,8 @@
</dependency> </dependency>
<dependency> <dependency>
<groupId>com.fasterxml.jackson.datatype</groupId> <groupId>tools.jackson.core</groupId>
<artifactId>jackson-datatype-jsr310</artifactId> <artifactId>jackson-databind</artifactId>
</dependency> </dependency>
<!-- redis序列化替代方案 比json快无数的跨语言二进制序列化 --> <!-- redis序列化替代方案 比json快无数的跨语言二进制序列化 -->

View File

@@ -1,28 +1,25 @@
package org.dromara.common.redis.config; package org.dromara.common.redis.config;
import cn.hutool.core.util.ObjectUtil;
import com.fasterxml.jackson.annotation.JsonAutoDetect; import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor; import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.json.JsonMapper;
import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.dromara.common.core.utils.SpringUtils; import org.dromara.common.core.utils.SpringUtils;
import org.dromara.common.redis.config.properties.RedissonProperties;
import org.dromara.common.redis.handler.KeyPrefixHandler; import org.dromara.common.redis.handler.KeyPrefixHandler;
import org.dromara.common.redis.handler.RedisExceptionHandler; import org.dromara.common.redis.handler.RedisExceptionHandler;
import org.redisson.client.codec.StringCodec; import org.redisson.client.codec.StringCodec;
import org.redisson.codec.CompositeCodec; import org.redisson.codec.CompositeCodec;
import org.redisson.codec.TypedJsonJacksonCodec; import org.redisson.codec.TypedJsonJackson3Codec;
import org.redisson.spring.starter.RedissonAutoConfigurationCustomizer; import org.redisson.spring.starter.RedissonAutoConfigurationCustomizer;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.core.task.VirtualThreadTaskExecutor; import org.springframework.core.task.VirtualThreadTaskExecutor;
import tools.jackson.databind.DefaultTyping;
import tools.jackson.databind.ext.javatime.deser.LocalDateTimeDeserializer;
import tools.jackson.databind.ext.javatime.ser.LocalDateTimeSerializer;
import tools.jackson.databind.json.JsonMapper;
import tools.jackson.databind.jsontype.DefaultBaseTypeLimitingValidator;
import tools.jackson.databind.module.SimpleModule;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter; import java.time.format.DateTimeFormatter;
@@ -35,68 +32,45 @@ import java.util.TimeZone;
*/ */
@Slf4j @Slf4j
@AutoConfiguration @AutoConfiguration
@EnableConfigurationProperties(RedissonProperties.class)
public class RedisConfig { public class RedisConfig {
@Autowired /**
private RedissonProperties redissonProperties; * redisson 自定义配置 (更多相关配置项目请参阅 RedissonProperties/RedissonAutoConfigurationV4 和 DataRedisProperties
*
* @see org.redisson.spring.starter.RedissonProperties
* @see org.redisson.spring.starter.RedissonAutoConfigurationV4
* @see org.springframework.boot.data.redis.autoconfigure.DataRedisProperties
*
* @param keyPrefix redis缓存key前缀
*/
@Bean @Bean
public RedissonAutoConfigurationCustomizer redissonCustomizer() { public RedissonAutoConfigurationCustomizer redissonCustomizer(@Value("${spring.data.redis.redisson.keyPrefix}") String keyPrefix) {
return config -> { return config -> {
JavaTimeModule javaTimeModule = new JavaTimeModule(); SimpleModule simpleModule = new SimpleModule();
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
javaTimeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(formatter)); simpleModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(formatter));
javaTimeModule.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(formatter)); simpleModule.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(formatter));
JsonMapper om = new JsonMapper(); JsonMapper jsonMapper = JsonMapper.builder()
om.registerModule(javaTimeModule); .addModules(simpleModule)
om.setTimeZone(TimeZone.getDefault()); .defaultTimeZone(TimeZone.getDefault())
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); .changeDefaultVisibility(visibilityChecker -> visibilityChecker.withVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY))
// 指定序列化输入的类型类必须是非final修饰的。序列化时将对象全类名一起保存下来 // 指定序列化输入的类型类必须是非final修饰的。序列化时将对象全类名一起保存下来
om.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL); // LaissezFaireSubTypeValidator 在 Jackson 3.X 中被禁止外部引用,此处使用 DefaultBaseTypeLimitingValidator 替代,两者在行为上可能会有所差异,但一般情况下无影响
.activateDefaultTyping(new DefaultBaseTypeLimitingValidator(), DefaultTyping.NON_FINAL)
.build();
// org.apache.fory.logging.LoggerFactory 包别引入错了 // org.apache.fory.logging.LoggerFactory 包别引入错了
// LoggerFactory.useSlf4jLogging(true); // LoggerFactory.useSlf4jLogging(true);
// ForyCodec foryCodec = new ForyCodec(); // ForyCodec foryCodec = new ForyCodec();
// CompositeCodec codec = new CompositeCodec(StringCodec.INSTANCE, foryCodec, foryCodec); // CompositeCodec codec = new CompositeCodec(StringCodec.INSTANCE, foryCodec, foryCodec);
TypedJsonJacksonCodec jsonCodec = new TypedJsonJacksonCodec(Object.class, om); TypedJsonJackson3Codec jsonCodec = new TypedJsonJackson3Codec(Object.class, jsonMapper);
// 组合序列化 key 使用 String 内容使用通用 json 格式 // 组合序列化 key 使用 String 内容使用通用 json 格式
CompositeCodec codec = new CompositeCodec(StringCodec.INSTANCE, jsonCodec, jsonCodec); CompositeCodec codec = new CompositeCodec(StringCodec.INSTANCE, jsonCodec, jsonCodec);
config.setThreads(redissonProperties.getThreads()) config.setCodec(codec);
.setNettyThreads(redissonProperties.getNettyThreads())
// 缓存 Lua 脚本 减少网络传输(redisson 大部分的功能都是基于 Lua 脚本实现)
.setUseScriptCache(true)
//设置redis key前缀
.setNameMapper(new KeyPrefixHandler(redissonProperties.getKeyPrefix()))
.setCodec(codec);
if (SpringUtils.isVirtual()) { if (SpringUtils.isVirtual()) {
config.setNettyExecutor(new VirtualThreadTaskExecutor("redisson-")); config.setNettyExecutor(new VirtualThreadTaskExecutor("redisson-"));
} }
RedissonProperties.SingleServerConfig singleServerConfig = redissonProperties.getSingleServerConfig(); config.setNameMapper(new KeyPrefixHandler(keyPrefix));
if (ObjectUtil.isNotNull(singleServerConfig)) {
// 使用单机模式
config.useSingleServer()
.setTimeout(singleServerConfig.getTimeout())
.setClientName(singleServerConfig.getClientName())
.setIdleConnectionTimeout(singleServerConfig.getIdleConnectionTimeout())
.setSubscriptionConnectionPoolSize(singleServerConfig.getSubscriptionConnectionPoolSize())
.setConnectionMinimumIdleSize(singleServerConfig.getConnectionMinimumIdleSize())
.setConnectionPoolSize(singleServerConfig.getConnectionPoolSize());
}
// 集群配置方式 参考下方注释
RedissonProperties.ClusterServersConfig clusterServersConfig = redissonProperties.getClusterServersConfig();
if (ObjectUtil.isNotNull(clusterServersConfig)) {
config.useClusterServers()
.setTimeout(clusterServersConfig.getTimeout())
.setClientName(clusterServersConfig.getClientName())
.setIdleConnectionTimeout(clusterServersConfig.getIdleConnectionTimeout())
.setSubscriptionConnectionPoolSize(clusterServersConfig.getSubscriptionConnectionPoolSize())
.setMasterConnectionMinimumIdleSize(clusterServersConfig.getMasterConnectionMinimumIdleSize())
.setMasterConnectionPoolSize(clusterServersConfig.getMasterConnectionPoolSize())
.setSlaveConnectionMinimumIdleSize(clusterServersConfig.getSlaveConnectionMinimumIdleSize())
.setSlaveConnectionPoolSize(clusterServersConfig.getSlaveConnectionPoolSize())
.setReadMode(clusterServersConfig.getReadMode())
.setSubscriptionMode(clusterServersConfig.getSubscriptionMode());
}
log.info("初始化 redis 配置"); log.info("初始化 redis 配置");
}; };
} }
@@ -126,34 +100,34 @@ public class RedisConfig {
* timeout: 10s * timeout: 10s
* # 是否开启ssl * # 是否开启ssl
* ssl.enabled: false * ssl.enabled: false
* * # redisson配置
* redisson: * redisson:
* # 线程池数量 * # 线程池数量
* threads: 16 * threads: 16
* # Netty线程池数量 * # Netty线程池数量
* nettyThreads: 32 * nettyThreads: 32
* # 集群配置 * # 集群配置
* clusterServersConfig: * clusterServersConfig:
* # 客户端名称 * # 客户端名称
* clientName: ${ruoyi.name} * clientName: ${ruoyi.name}
* # master最小空闲连接数 * # master最小空闲连接数
* masterConnectionMinimumIdleSize: 32 * masterConnectionMinimumIdleSize: 32
* # master连接池大小 * # master连接池大小
* masterConnectionPoolSize: 64 * masterConnectionPoolSize: 64
* # slave最小空闲连接数 * # slave最小空闲连接数
* slaveConnectionMinimumIdleSize: 32 * slaveConnectionMinimumIdleSize: 32
* # slave连接池大小 * # slave连接池大小
* slaveConnectionPoolSize: 64 * slaveConnectionPoolSize: 64
* # 连接空闲超时,单位:毫秒 * # 连接空闲超时,单位:毫秒
* idleConnectionTimeout: 10000 * idleConnectionTimeout: 10000
* # 命令等待超时,单位:毫秒 * # 命令等待超时,单位:毫秒
* timeout: 3000 * timeout: 3000
* # 发布和订阅连接池大小 * # 发布和订阅连接池大小
* subscriptionConnectionPoolSize: 50 * subscriptionConnectionPoolSize: 50
* # 读取模式 * # 读取模式
* readMode: "SLAVE" * readMode: "SLAVE"
* # 订阅模式 * # 订阅模式
* subscriptionMode: "MASTER" * subscriptionMode: "MASTER"
*/ */
} }

View File

@@ -1,135 +0,0 @@
package org.dromara.common.redis.config.properties;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.redisson.config.ReadMode;
import org.redisson.config.SubscriptionMode;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* Redisson 配置属性
*
* @author Lion Li
*/
@Data
@ConfigurationProperties(prefix = "redisson")
public class RedissonProperties {
/**
* redis缓存key前缀
*/
private String keyPrefix;
/**
* 线程池数量,默认值 = 当前处理核数量 * 2
*/
private int threads;
/**
* Netty线程池数量,默认值 = 当前处理核数量 * 2
*/
private int nettyThreads;
/**
* 单机服务配置
*/
private SingleServerConfig singleServerConfig;
/**
* 集群服务配置
*/
private ClusterServersConfig clusterServersConfig;
@Data
@NoArgsConstructor
public static class SingleServerConfig {
/**
* 客户端名称
*/
private String clientName;
/**
* 最小空闲连接数
*/
private int connectionMinimumIdleSize;
/**
* 连接池大小
*/
private int connectionPoolSize;
/**
* 连接空闲超时,单位:毫秒
*/
private int idleConnectionTimeout;
/**
* 命令等待超时,单位:毫秒
*/
private int timeout;
/**
* 发布和订阅连接池大小
*/
private int subscriptionConnectionPoolSize;
}
@Data
@NoArgsConstructor
public static class ClusterServersConfig {
/**
* 客户端名称
*/
private String clientName;
/**
* master最小空闲连接数
*/
private int masterConnectionMinimumIdleSize;
/**
* master连接池大小
*/
private int masterConnectionPoolSize;
/**
* slave最小空闲连接数
*/
private int slaveConnectionMinimumIdleSize;
/**
* slave连接池大小
*/
private int slaveConnectionPoolSize;
/**
* 连接空闲超时,单位:毫秒
*/
private int idleConnectionTimeout;
/**
* 命令等待超时,单位:毫秒
*/
private int timeout;
/**
* 发布和订阅连接池大小
*/
private int subscriptionConnectionPoolSize;
/**
* 读取模式
*/
private ReadMode readMode;
/**
* 订阅模式
*/
private SubscriptionMode subscriptionMode;
}
}

View File

@@ -1,5 +1,6 @@
package org.dromara.common.redis.handler; package org.dromara.common.redis.handler;
import org.apache.commons.lang3.Strings;
import org.dromara.common.core.utils.StringUtils; import org.dromara.common.core.utils.StringUtils;
import org.redisson.config.NameMapper; import org.redisson.config.NameMapper;
@@ -24,10 +25,7 @@ public class KeyPrefixHandler implements NameMapper {
*/ */
@Override @Override
public String map(String name) { public String map(String name) {
if (StringUtils.isBlank(name)) { if (StringUtils.isNoneBlank(name,keyPrefix) && !Strings.CS.startsWith(name, keyPrefix)) {
return null;
}
if (StringUtils.isNotBlank(keyPrefix) && !name.startsWith(keyPrefix)) {
return keyPrefix + name; return keyPrefix + name;
} }
return name; return name;
@@ -38,10 +36,7 @@ public class KeyPrefixHandler implements NameMapper {
*/ */
@Override @Override
public String unmap(String name) { public String unmap(String name) {
if (StringUtils.isBlank(name)) { if (StringUtils.isNoneBlank(name,keyPrefix) && Strings.CS.startsWith(name, keyPrefix)) {
return null;
}
if (StringUtils.isNotBlank(keyPrefix) && name.startsWith(keyPrefix)) {
return name.substring(keyPrefix.length()); return name.substring(keyPrefix.length());
} }
return name; return name;