RedisTemplate 构建的源码分析
创建一个RedisStandaloneConfiguration---->在LettuceConnectionConfiguration中创建一个LettuceConnectionFactory---->在RedisAutoConfiguration中创建一个RedisTemplate
RedisStandaloneConfiguration
从RedisProperties中,取出hostname、port、username、password、database,构建一个RedisStandaloneConfiguration
// org.springframework.boot.autoconfigure.data.redis.RedisConnectionConfiguration#getStandaloneConfig
protected final RedisStandaloneConfiguration getStandaloneConfig() {
if (this.standaloneConfiguration != null) {
return this.standaloneConfiguration;
}
RedisStandaloneConfiguration config = new RedisStandaloneConfiguration();
if (StringUtils.hasText(this.properties.getUrl())) {
ConnectionInfo connectionInfo = parseUrl(this.properties.getUrl());
config.setHostName(connectionInfo.getHostName());
config.setPort(connectionInfo.getPort());
config.setUsername(connectionInfo.getUsername());
config.setPassword(RedisPassword.of(connectionInfo.getPassword()));
}
else {
config.setHostName(this.properties.getHost());
config.setPort(this.properties.getPort());
config.setUsername(this.properties.getUsername());
config.setPassword(RedisPassword.of(this.properties.getPassword()));
}
config.setDatabase(this.properties.getDatabase());
return config;
}
LettuceConnectionFactory
基于LettuceClientConfiguration 和 RedisStandaloneConfiguration,构建一个LettuceConnectionFactory
@Bean
@ConditionalOnMissingBean(RedisConnectionFactory.class)
LettuceConnectionFactory redisConnectionFactory(
ObjectProvider<LettuceClientConfigurationBuilderCustomizer> builderCustomizers,
ClientResources clientResources) {
LettuceClientConfiguration clientConfig = getLettuceClientConfiguration(builderCustomizers, clientResources,
getProperties().getLettuce().getPool());
return createLettuceConnectionFactory(clientConfig);
}
private LettuceConnectionFactory createLettuceConnectionFactory(LettuceClientConfiguration clientConfiguration) {
if (getSentinelConfig() != null) {
return new LettuceConnectionFactory(getSentinelConfig(), clientConfiguration);
}
if (getClusterConfiguration() != null) {
return new LettuceConnectionFactory(getClusterConfiguration(), clientConfiguration);
}
return new LettuceConnectionFactory(getStandaloneConfig(), clientConfiguration);
}
其中,LettuceClientConfiguration 中,包含了池的一些信息,比如MaxTotal、MinIdle、MaxWait等
动态切换库的最佳实践
需求如下:
用户信息 和 业务信息,缓存在不同的 redis db中。我们需要从不同的redis db,获取到不同的信息。
需要注意的是:假如A线程,连接db1获取用户信息;与此同时,B线程可能会连接db2获取业务信息,因此,不能简单粗暴的切换。
实际实现如下:
创建一个RedisStandaloneConfiguration---->然后,创建一个LettuceClientConfiguration,然后结合RedisStandaloneConfiguration 和 LettuceClientConfiguration,创建一个LettuceConnectionFactory---->最后,基于LettuceConnectionFactory创建一个RedisTemplate
redis.user
package com.jshxhb.framework.redis.config;
import org.springframework.boot.autoconfigure.data.redis.RedisProperties;
import org.springframework.boot.context.properties.ConfigurationProperties;
import java.time.Duration;
@ConfigurationProperties(value = "redis.user")
public class RedisUserDbProperties {
private int database = 0;
/**
* Connection URL. Overrides host, port, and password. User is ignored. Example:
* redis://user:password@example.com:6379
*/
private String url;
/**
* Redis server host.
*/
private String host = "localhost";
/**
* Login username of the redis server.
*/
private String username;
/**
* Login password of the redis server.
*/
private String password;
/**
* Redis server port.
*/
private int port = 6379;
/**
* Whether to enable SSL support.
*/
private boolean ssl;
/**
* Read timeout.
*/
private Duration timeout;
/**
* Connection timeout.
*/
private Duration connectTimeout;
/**
* Client name to be set on connections with CLIENT SETNAME.
*/
private String clientName;
/**
* Type of client to use. By default, auto-detected according to the classpath.
*/
private RedisProperties.ClientType clientType;
private RedisProperties.Sentinel sentinel;
private RedisProperties.Cluster cluster;
private final RedisProperties.Jedis jedis = new RedisProperties.Jedis();
private final RedisProperties.Lettuce lettuce = new RedisProperties.Lettuce();
public int getDatabase() {
return database;
}
public void setDatabase(int database) {
this.database = database;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public String getHost() {
return host;
}
public void setHost(String host) {
this.host = host;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public int getPort() {
return port;
}
public void setPort(int port) {
this.port = port;
}
public boolean isSsl() {
return ssl;
}
public void setSsl(boolean ssl) {
this.ssl = ssl;
}
public Duration getTimeout() {
return timeout;
}
public void setTimeout(Duration timeout) {
this.timeout = timeout;
}
public Duration getConnectTimeout() {
return connectTimeout;
}
public void setConnectTimeout(Duration connectTimeout) {
this.connectTimeout = connectTimeout;
}
public String getClientName() {
return clientName;
}
public void setClientName(String clientName) {
this.clientName = clientName;
}
public RedisProperties.ClientType getClientType() {
return clientType;
}
public void setClientType(RedisProperties.ClientType clientType) {
this.clientType = clientType;
}
public RedisProperties.Sentinel getSentinel() {
return sentinel;
}
public void setSentinel(RedisProperties.Sentinel sentinel) {
this.sentinel = sentinel;
}
public RedisProperties.Cluster getCluster() {
return cluster;
}
public void setCluster(RedisProperties.Cluster cluster) {
this.cluster = cluster;
}
public RedisProperties.Jedis getJedis() {
return jedis;
}
public RedisProperties.Lettuce getLettuce() {
return lettuce;
}
}
package com.jshxhb.framework.redis.config;
import com.alibaba.fastjson2.support.spring.data.redis.GenericFastJsonRedisSerializer;
import io.lettuce.core.resource.ClientResources;
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.data.redis.LettuceClientConfigurationBuilderCustomizer;
import org.springframework.boot.autoconfigure.data.redis.RedisProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.RedisPassword;
import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
import org.springframework.data.redis.connection.lettuce.LettuceClientConfiguration;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.connection.lettuce.LettucePoolingClientConfiguration;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.util.StringUtils;
@EnableConfigurationProperties(value = {RedisUserDbProperties.class})
@ConditionalOnProperty(name = "redis.user.enable" ,havingValue = "true", matchIfMissing = false)
@Configuration
public class UserRedisConfiguration {
@Bean("userRedisConnectionFactory")
public LettuceConnectionFactory userRedisConnectionFactory(ObjectProvider<RedisUserDbProperties> redisUserDbPropertiesProvider) {
RedisUserDbProperties redisUserDbProperties = redisUserDbPropertiesProvider.getIfAvailable();
RedisStandaloneConfiguration config = new RedisStandaloneConfiguration();
config.setHostName(redisUserDbProperties.getHost());
config.setPort(redisUserDbProperties.getPort());
config.setUsername(redisUserDbProperties.getUsername());
config.setPassword(RedisPassword.of(redisUserDbProperties.getPassword()));
config.setDatabase(redisUserDbProperties.getDatabase());
// 获取LettuceClientConfiguration
LettuceClientConfiguration lettuceClientConfiguration = lettuceClientConfiguration(redisUserDbProperties);
// 构建LettuceConnectionFactory
return new LettuceConnectionFactory(config, lettuceClientConfiguration);
}
public LettuceClientConfiguration lettuceClientConfiguration(RedisUserDbProperties redisUserDbProperties) {
RedisProperties.Pool pool = redisUserDbProperties.getLettuce().getPool();
GenericObjectPoolConfig<?> config = new GenericObjectPoolConfig<>();
config.setMaxTotal(pool.getMaxActive());
config.setMaxIdle(pool.getMaxIdle());
config.setMinIdle(pool.getMinIdle());
if (pool.getTimeBetweenEvictionRuns() != null) {
config.setTimeBetweenEvictionRuns(pool.getTimeBetweenEvictionRuns());
}
if (pool.getMaxWait() != null) {
config.setMaxWait(pool.getMaxWait());
}
//
LettucePoolingClientConfiguration.LettucePoolingClientConfigurationBuilder builder = LettucePoolingClientConfiguration.builder().poolConfig(config);
if (redisUserDbProperties.isSsl()) {
builder.useSsl();
}
if (redisUserDbProperties.getTimeout() != null) {
builder.commandTimeout(redisUserDbProperties.getTimeout());
}
if (redisUserDbProperties.getLettuce() != null) {
RedisProperties.Lettuce lettuce = redisUserDbProperties.getLettuce();
if (lettuce.getShutdownTimeout() != null && !lettuce.getShutdownTimeout().isZero()) {
builder.shutdownTimeout(redisUserDbProperties.getLettuce().getShutdownTimeout());
}
}
if (StringUtils.hasText(redisUserDbProperties.getClientName())) {
builder.clientName(redisUserDbProperties.getClientName());
}
return builder.build();
}
@Bean("userRedisTemplate")
public RedisTemplate<Object, Object> redisTemplate(@Qualifier("userRedisConnectionFactory") RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<Object, Object> template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory);
// 指定序列化
GenericFastJsonRedisSerializer genericFastJsonRedisSerializer = new GenericFastJsonRedisSerializer();
// keySerializer & valueSerializer
template.setKeySerializer(new StringRedisSerializer());
template.setValueSerializer(genericFastJsonRedisSerializer);
// hash keySerializer & valueSerializer
template.setHashKeySerializer(new StringRedisSerializer());
template.setHashValueSerializer(genericFastJsonRedisSerializer);
template.afterPropertiesSet();
return template;
}
}
redis.iot
package com.jshxhb.framework.redis.config;
import org.springframework.boot.autoconfigure.data.redis.RedisProperties;
import org.springframework.boot.context.properties.ConfigurationProperties;
import java.time.Duration;
@ConfigurationProperties(value = "redis.iot")
public class RedisIotDbProperties {
private int database = 0;
/**
* Connection URL. Overrides host, port, and password. User is ignored. Example:
* redis://user:password@example.com:6379
*/
private String url;
/**
* Redis server host.
*/
private String host = "localhost";
/**
* Login username of the redis server.
*/
private String username;
/**
* Login password of the redis server.
*/
private String password;
/**
* Redis server port.
*/
private int port = 6379;
/**
* Whether to enable SSL support.
*/
private boolean ssl;
/**
* Read timeout.
*/
private Duration timeout;
/**
* Connection timeout.
*/
private Duration connectTimeout;
/**
* Client name to be set on connections with CLIENT SETNAME.
*/
private String clientName;
/**
* Type of client to use. By default, auto-detected according to the classpath.
*/
private RedisProperties.ClientType clientType;
private RedisProperties.Sentinel sentinel;
private RedisProperties.Cluster cluster;
private final RedisProperties.Jedis jedis = new RedisProperties.Jedis();
private final RedisProperties.Lettuce lettuce = new RedisProperties.Lettuce();
public int getDatabase() {
return database;
}
public void setDatabase(int database) {
this.database = database;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public String getHost() {
return host;
}
public void setHost(String host) {
this.host = host;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public int getPort() {
return port;
}
public void setPort(int port) {
this.port = port;
}
public boolean isSsl() {
return ssl;
}
public void setSsl(boolean ssl) {
this.ssl = ssl;
}
public Duration getTimeout() {
return timeout;
}
public void setTimeout(Duration timeout) {
this.timeout = timeout;
}
public Duration getConnectTimeout() {
return connectTimeout;
}
public void setConnectTimeout(Duration connectTimeout) {
this.connectTimeout = connectTimeout;
}
public String getClientName() {
return clientName;
}
public void setClientName(String clientName) {
this.clientName = clientName;
}
public RedisProperties.ClientType getClientType() {
return clientType;
}
public void setClientType(RedisProperties.ClientType clientType) {
this.clientType = clientType;
}
public RedisProperties.Sentinel getSentinel() {
return sentinel;
}
public void setSentinel(RedisProperties.Sentinel sentinel) {
this.sentinel = sentinel;
}
public RedisProperties.Cluster getCluster() {
return cluster;
}
public void setCluster(RedisProperties.Cluster cluster) {
this.cluster = cluster;
}
public RedisProperties.Jedis getJedis() {
return jedis;
}
public RedisProperties.Lettuce getLettuce() {
return lettuce;
}
}
package com.jshxhb.framework.redis.config;
import com.alibaba.fastjson2.support.spring.data.redis.GenericFastJsonRedisSerializer;
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.data.redis.RedisProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.RedisPassword;
import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
import org.springframework.data.redis.connection.lettuce.LettuceClientConfiguration;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.connection.lettuce.LettucePoolingClientConfiguration;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.util.StringUtils;
@EnableConfigurationProperties(value = {RedisIotDbProperties.class})
@ConditionalOnProperty(name = "redis.iot.enable" ,havingValue = "true", matchIfMissing = false)
@Configuration
public class IotRedisConfiguration {
@Primary
@Bean("iotRedisConnectionFactory")
public LettuceConnectionFactory userRedisConnectionFactory(ObjectProvider<RedisIotDbProperties> redisIotDbPropertiesProvider) {
RedisIotDbProperties redisIotDbProperties = redisIotDbPropertiesProvider.getIfAvailable();
RedisStandaloneConfiguration config = new RedisStandaloneConfiguration();
config.setHostName(redisIotDbProperties.getHost());
config.setPort(redisIotDbProperties.getPort());
config.setUsername(redisIotDbProperties.getUsername());
config.setPassword(RedisPassword.of(redisIotDbProperties.getPassword()));
config.setDatabase(redisIotDbProperties.getDatabase());
// 获取LettuceClientConfiguration
LettuceClientConfiguration lettuceClientConfiguration = lettuceClientConfiguration(redisIotDbProperties);
// 构建LettuceConnectionFactory
return new LettuceConnectionFactory(config, lettuceClientConfiguration);
}
public LettuceClientConfiguration lettuceClientConfiguration(RedisIotDbProperties redisIotDbProperties) {
RedisProperties.Pool pool = redisIotDbProperties.getLettuce().getPool();
GenericObjectPoolConfig<?> config = new GenericObjectPoolConfig<>();
config.setMaxTotal(pool.getMaxActive());
config.setMaxIdle(pool.getMaxIdle());
config.setMinIdle(pool.getMinIdle());
if (pool.getTimeBetweenEvictionRuns() != null) {
config.setTimeBetweenEvictionRuns(pool.getTimeBetweenEvictionRuns());
}
if (pool.getMaxWait() != null) {
config.setMaxWait(pool.getMaxWait());
}
//
LettucePoolingClientConfiguration.LettucePoolingClientConfigurationBuilder builder = LettucePoolingClientConfiguration.builder().poolConfig(config);
if (redisIotDbProperties.isSsl()) {
builder.useSsl();
}
if (redisIotDbProperties.getTimeout() != null) {
builder.commandTimeout(redisIotDbProperties.getTimeout());
}
if (redisIotDbProperties.getLettuce() != null) {
RedisProperties.Lettuce lettuce = redisIotDbProperties.getLettuce();
if (lettuce.getShutdownTimeout() != null && !lettuce.getShutdownTimeout().isZero()) {
builder.shutdownTimeout(redisIotDbProperties.getLettuce().getShutdownTimeout());
}
}
if (StringUtils.hasText(redisIotDbProperties.getClientName())) {
builder.clientName(redisIotDbProperties.getClientName());
}
return builder.build();
}
@Bean("iotRedisTemplate")
public RedisTemplate<Object, Object> redisTemplate(@Qualifier("iotRedisConnectionFactory") RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<Object, Object> template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory);
// 指定序列化
GenericFastJsonRedisSerializer genericFastJsonRedisSerializer = new GenericFastJsonRedisSerializer();
// keySerializer & valueSerializer
template.setKeySerializer(new StringRedisSerializer());
template.setValueSerializer(genericFastJsonRedisSerializer);
// hash keySerializer & valueSerializer
template.setHashKeySerializer(new StringRedisSerializer());
template.setHashValueSerializer(genericFastJsonRedisSerializer);
template.afterPropertiesSet();
return template;
}
}
配置文件如下:
redis:
user:
enable: true
host: 218.94.128.34
port: 6379
database: 3
password: long123456
# 15000毫秒
timeout: 15000
lettuce:
pool:
min-idle: 0
max-idle: 8
max-active: 8
max-wait: -1
iot:
enable: true
host: 218.94.128.34
port: 6379
database: 0
password: long123456
# 15000毫秒
timeout: 15000
lettuce:
pool:
min-idle: 0
max-idle: 8
max-active: 8
max-wait: -1
工具类
package com.jshxhb.framework.redis.helper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
@Component
public class RedisCacheManager {
@Autowired
@Qualifier("userRedisTemplate")
private RedisTemplate userRedisTemplate;
@Autowired
@Qualifier("iotRedisTemplate")
private RedisTemplate iotRedisTemplate;
private RedisCacheHelper userRedisCacheHelper;
private RedisCacheHelper iotRedisCacheHelper;
@PostConstruct
public void initHelper() {
userRedisCacheHelper = new RedisCacheHelper(userRedisTemplate);
iotRedisCacheHelper = new RedisCacheHelper(iotRedisTemplate);
}
public RedisCacheHelper getUserRedisCacheHelper() {
return userRedisCacheHelper;
}
public RedisCacheHelper getIotRedisCacheHelper() {
return iotRedisCacheHelper;
}
}
package com.jshxhb.framework.redis.helper;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONReader;
import com.alibaba.fastjson2.TypeReference;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
public class RedisCacheHelper {
private RedisTemplate redisTemplate;
public RedisCacheHelper(RedisTemplate redisTemplate) {
this.redisTemplate = redisTemplate;
}
/**
* 缓存基本的对象,Integer、String、实体类等
*
* @param key 缓存的键值
* @param value 缓存的值
*/
public <V> void setCacheObject(final String key, final V value) {
redisTemplate.opsForValue().set(key, value);
}
/**
* 缓存基本的对象,Integer、String、实体类等
*
* @param key 缓存的键值
* @param value 缓存的值
* @param timeout 时间
* @param timeUnit 时间颗粒度
*/
public <V> void setCacheObject(final String key, final V value, final Integer timeout, final TimeUnit timeUnit) {
redisTemplate.opsForValue().set(key, value, timeout, timeUnit);
}
/**
* 设置有效时间
*
* @param key Redis键
* @param timeout 超时时间
* @return true=设置成功;false=设置失败
*/
public boolean expire(final String key, final long timeout) {
return expire(key, timeout, TimeUnit.SECONDS);
}
/**
* 设置有效时间
*
* @param key Redis键
* @param timeout 超时时间
* @param unit 时间单位
* @return true=设置成功;false=设置失败
*/
public boolean expire(final String key, final long timeout, final TimeUnit unit) {
return redisTemplate.expire(key, timeout, unit);
}
/**
* 判断 key是否存在
*
* @param key 键
* @return true 存在 false不存在
*/
public Boolean hasKey(String key) {
return redisTemplate.hasKey(key);
}
/**
* 获得缓存的基本对象。
*
* @param key 缓存键值
* @return 缓存键值对应的数据
*/
public <V> V getCacheObject(final String key) {
ValueOperations<String, V> operation = redisTemplate.opsForValue();
return operation.get(key);
}
/**
* 获得缓存的基本对象。
*
* @param key 缓存键值
* @return 缓存键值对应的数据
*/
public <V,E> List<E> getCacheList(final String key, Class<E> clazz) {
ValueOperations<String, V> operation = redisTemplate.opsForValue();
V origin = operation.get(key);
if (Objects.isNull(origin)) {
return null;
} else {
if (origin instanceof JSONArray) {
JSONArray jsonArray = (JSONArray) origin;
return jsonArray.toJavaList(clazz, JSONReader.Feature.SupportAutoType);
} else {
return null;
}
}
}
}
测试
@Autowired
private RedisCacheManager redisCacheManager;
@Override
public PageList<BoxBO> pageSelect(BoxBO boxBO) {
Object boxPOList = redisCacheManager.getUserRedisCacheHelper().getCacheObject("boxPOList");
LOGGER.info("redis boxPOList:{}", JSON.toJSONString(boxPOList));
Object sysShowHide = redisCacheManager.getIotRedisCacheHelper().getCacheObject("sys_dict:sys_show_hide");
LOGGER.info("redis sys_show_hide:{}", JSON.toJSONString(sysShowHide));
return null;
// BoxPO boxPO = boxBO.bo2Po();
// List<BoxPO> boxPOList = boxMapper.selectByCondition(boxPO);
// LOGGER.info("pageSelect boxPOList:{}", JSON.toJSONString(boxPOList));
//
// return PageRespUtils.getPageList(BoxBO::po2Bo,boxPOList);
}