springboot2.0整合redis(使用Jedis)及自动装配部分源码解析
1.springboot 目前的推荐版是2.1.2.RELEASE,我们就以当前最新的推荐版为例;2.关于redis在linux下安装,可以参考《linux下安装redis》《linux下redis集群搭建》3.spring-data-redis 目前spring的官网的版本是2.1.5,详细查看https://spring.io/projects/spring-data-redis#...
1.springboot 目前的推荐版是2.1.2.RELEASE,我们就以当前最新的推荐版为例;
2.关于redis在linux下安装,可以参考《linux下安装redis》 《linux下redis集群搭建》
3.spring-data-redis 目前spring的官网的版本是2.1.5,详细查看https://spring.io/projects/spring-data-redis#overview
关于官网描述的特性,主要的几点如下:
1.支持jedis和lettuce,不支持JRedis和SRP
特别介绍下在2.x版本中,默认是使用lettuce;1.x版本的时候,默认使用的就是Jedis;关于两个的区别:
- # Jedis和Lettuce都是Redis Client
- # Jedis 是直连模式,在多个线程间共享一个 Jedis 实例时是线程不安全的。
- # 如果想要在多线程环境下使用 Jedis,需要使用连接池。
- # 每个线程都去拿Jedis 实例,当连接数量增多时,物理连接成本就较高了。
- # Lettuce 是基于 netty 的,netty 是一个多线程、事件驱动的 I/O 框架,连接实例可以在多个线程间共享,通过异步的方式可以让我们更好的利用系统资源,而不用浪费线程等待网络或磁盘I/O。
2.异常的处理转换
3.RedisTemplate提供的是redis操作的高级抽象
4.Pubsub 支持
5.redis的哨兵模式以及集群模式的支持
6.JDK, String, JSON and Spring Object/XML的序列化映射
……………………后面的就不翻译了,英语也不太好,翻译的也不太好………………
4.springboot2.x整合redis;主要是使用Jedis的方式,毕竟1.x版本的时候,默认使用的就是Jedis;
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.2.RELEASE</version>
<relativePath /> <!-- lookup parent from repository -->
</parent>
<properties>
<java.version>1.8</java.version>
<!-- 默认为2.9.1版本,运行的时候,会报错,指定低一个版本的 -->
<jedis.version>2.9.0</jedis.version>
</properties>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<exclusions>
<!-- 默认指定了 lettuce,我们需要排除,并且引入jedis的客户端-->
<exclusion>
<groupId>io.lettuce</groupId>
<artifactId>lettuce-core</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
yml文件的配置
spring:
redis:
database: 0
host: localhost
port: 6379
timeout:
nano: 2
seconds: 2
jedis:
pool:
max-active: 8
max-wait: -1
max-idle: 8
min-idle: 0
查看redis的配置类org.springframework.boot.autoconfigure.data.redis.RedisProperties,与springboot1.x不同的是
timeout属性由之前的int类型,换成了java8的类型;(下部分,会用源码的形式,查看timeout的类型的改变)
Jedis与Lettuce的连接属性进行了区别;
如果想使用Lettuce,进行整合,只需要把引入spring-boot-starter-data-redis,默认就是Lettuce,只需要把连接池属性改成Lettuce;
如果是集群搭建,配置文件如下
spring:
redis:
cluster:
nodes:
- 192.168.112.129:7000
- 192.168.112.129:7001
- 192.168.112.129:7002
- 192.168.112.129:7003
- 192.168.112.129:7004
- 192.168.112.129:7005
max-redirects: 3
jedis:
pool:
max-active: 8
max-wait: -1
max-idle: 8
min-idle: 0
5.测试,以及测试结果就不演示了,很简单的案例
在测试过程中,使用redis的客户端(界面化工具),直接获取数据,会遇到序列化的问题;在org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration自动装配类中,实例化了redisTemplate和stringRedisTemplate;
@Configuration
@ConditionalOnClass(RedisOperations.class)
@EnableConfigurationProperties(RedisProperties.class)
@Import({ LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class })
public class RedisAutoConfiguration {
@Bean
@ConditionalOnMissingBean(name = "redisTemplate")
public RedisTemplate<Object, Object> redisTemplate(
RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
RedisTemplate<Object, Object> template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
@Bean
@ConditionalOnMissingBean
public StringRedisTemplate stringRedisTemplate(
RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
StringRedisTemplate template = new StringRedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
}
我们简单查看源码,就可以看到序列化的问题;
先看RedisTemplate<Object, Object> template = new RedisTemplate<>();的实例化,RedisTemplate构造方法下的,重写父级的public void afterPropertiesSet()方法,直接查看父级的org.springframework.data.redis.core.RedisAccessor类,可以看到afterPropertiesSet()方法,实现于spring的org.springframework.beans.factory.InitializingBean接口,关于InitializingBean接口作用,以及什么时机执行,大家自行去了解
/*
* (non-Javadoc)
* @see org.springframework.data.redis.core.RedisAccessor#afterPropertiesSet()
*/
@Override
public void afterPropertiesSet() {
super.afterPropertiesSet();
boolean defaultUsed = false;
if (defaultSerializer == null) {
defaultSerializer = new JdkSerializationRedisSerializer(
classLoader != null ? classLoader : this.getClass().getClassLoader());
}
if (enableDefaultSerializer) {
if (keySerializer == null) {
keySerializer = defaultSerializer;
defaultUsed = true;
}
if (valueSerializer == null) {
valueSerializer = defaultSerializer;
defaultUsed = true;
}
if (hashKeySerializer == null) {
hashKeySerializer = defaultSerializer;
defaultUsed = true;
}
if (hashValueSerializer == null) {
hashValueSerializer = defaultSerializer;
defaultUsed = true;
}
}
if (enableDefaultSerializer && defaultUsed) {
Assert.notNull(defaultSerializer, "default serializer null and not all serializers initialized");
}
if (scriptExecutor == null) {
this.scriptExecutor = new DefaultScriptExecutor<>(this);
}
initialized = true;
}
看代码,就可以看到,在没有指定keySerializer,valueSerializer的情况下,默认使用的是defaultSerializer;
我们在看StringRedisTemplate template = new StringRedisTemplate();构造方法,相信大家也都能看懂,调用了set的方法,把序列化的类,直接使用string的形式
public StringRedisTemplate() {
setKeySerializer(RedisSerializer.string());
setValueSerializer(RedisSerializer.string());
setHashKeySerializer(RedisSerializer.string());
setHashValueSerializer(RedisSerializer.string());
}
所有说,我们对序列化以及反序列化的时候,一般情况下,json串-->POJO对象转换的时候,自己手动转换,可以直接使用StringRedisTemplate;而需要自动转换的时候,可以自定义一个RedisTemplate的实例,使用Jackson2JsonRedisSerializer的形式,序列化valueSerializer,代码如下:
我把key序列了字符换,val用的jackson2JsonRedisSerializer序列化,大家可以根据实际情况来操作;
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory);
Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<Object>(
Object.class);
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om);
template.setValueSerializer(jackson2JsonRedisSerializer);
template.setKeySerializer(RedisSerializer.string());
// template.setValueSerializer(RedisSerializer.string());
// template.setHashKeySerializer(RedisSerializer.string());
// template.setHashValueSerializer(RedisSerializer.string());
return template;
}
}
测试代码如下,比较简单,大家自行测试即可
@RunWith(SpringRunner.class)
@SpringBootTest
public class BadgerRedisSingleApplicationTests {
@Autowired
RedisTemplate<String, Object> redisTemplate;
@Autowired
StringRedisTemplate stringRedisTemplate;
@Test
public void testRedisTemplate() {
final String key = "obj";
List<String> strs = Arrays.asList("d", "b", "a", "c", "a");
ValueOperations<String, Object> opsForValue = redisTemplate.opsForValue();
opsForValue.set(key, strs);
Object str = opsForValue.get(key);
System.out.println(str);
}
@Test
public void testString() {
final String key = "test";
ValueOperations<String, String> opsForValue = stringRedisTemplate.opsForValue();
opsForValue.set(key, "test");
String str = opsForValue.get(key);
System.out.println(str);
}
}
至此,redis的整合就完成了,下面对源码进行讲解下,不感兴趣的同学,可以略过了………………
6.自动装配的源码解析
我们先看org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration自动装配类
@Configuration
@ConditionalOnClass(RedisOperations.class)
@EnableConfigurationProperties(RedisProperties.class)
@Import({ LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class })
public class RedisAutoConfiguration {
里面的内容,上面已经介绍过了,我们直接看Import导入的JedisConnectionConfiguration的实例,我们使用的Jedis,排除了Lettuce,所以只有JedisConnectionConfiguration可以实例化成功
@Import({ LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class })
JedisConnectionConfiguration(RedisProperties properties,
ObjectProvider<RedisSentinelConfiguration> sentinelConfiguration,
ObjectProvider<RedisClusterConfiguration> clusterConfiguration,
ObjectProvider<JedisClientConfigurationBuilderCustomizer> builderCustomizers) {
super(properties, sentinelConfiguration, clusterConfiguration);
this.properties = properties;
this.builderCustomizers = builderCustomizers;
}
@Bean
@ConditionalOnMissingBean(RedisConnectionFactory.class)
public JedisConnectionFactory redisConnectionFactory() throws UnknownHostException {
return createJedisConnectionFactory();
}
我们先看构造方法,实例的时候自动注入了以下属性,并把properties和builderCustomizers存起来了
RedisProperties properties,:这个就yml配置文件里的属性的类
ObjectProvider<RedisSentinelConfiguration> sentinelConfiguration:这个是哨兵模式的配置
ObjectProvider<RedisClusterConfiguration> clusterConfiguration:这个是集群模式的配置
ObjectProvider<JedisClientConfigurationBuilderCustomizer> builderCustomizers:这个是普通模式的构建器
再看redisConnectionFactory()方法,也是这个类中最重要的部分,为RedisAutoConfiguration自动装配的时候,提供的RedisConnectionFactory 这个连接工厂;
private JedisConnectionFactory createJedisConnectionFactory() {
//获取jedis客户端的配置
JedisClientConfiguration clientConfiguration = getJedisClientConfiguration();
//创建哨兵模式的连接工厂(yml只配置了普通模式,此处不判断不生效)
if (getSentinelConfig() != null) {
return new JedisConnectionFactory(getSentinelConfig(), clientConfiguration);
}
//创建集群模式的连接工厂(yml只配置了普通模式,此处不判断不生效)
if (getClusterConfiguration() != null) {
return new JedisConnectionFactory(getClusterConfiguration(),
clientConfiguration);
}
//创建普通模式的连接工厂
return new JedisConnectionFactory(getStandaloneConfig(), clientConfiguration);
}
我们先看第一行的方法:getJedisClientConfiguration() 获取jedis客户端的配置
private JedisClientConfiguration getJedisClientConfiguration() {
//创建默认的JedisClientConfigurationBuilder对象,并设置useSsl,readTimeout,connectTimeout属性
JedisClientConfigurationBuilder builder = applyProperties(JedisClientConfiguration.builder());
//获取yml文件配置的pool,并配置JedisPoolConfig 设置到poolConfig属性中
RedisProperties.Pool pool = this.properties.getJedis().getPool();
if (pool != null) {
applyPooling(pool, builder);
}
//处理url
if (StringUtils.hasText(this.properties.getUrl())) {
customizeConfigurationFromUrl(builder);
}
//执行定制器
customize(builder);
return builder.build();
}
先看第一个JedisClientConfiguration.builder()方法,构建JedisClientConfigurationBuilder对象
class DefaultJedisClientConfigurationBuilder implements JedisClientConfigurationBuilder,
JedisPoolingClientConfigurationBuilder, JedisSslClientConfigurationBuilder {
private boolean useSsl;
private @Nullable SSLSocketFactory sslSocketFactory;
private @Nullable SSLParameters sslParameters;
private @Nullable HostnameVerifier hostnameVerifier;
private boolean usePooling;
private GenericObjectPoolConfig poolConfig = new JedisPoolConfig();
private @Nullable String clientName;
private Duration readTimeout = Duration.ofMillis(Protocol.DEFAULT_TIMEOUT);
private Duration connectTimeout = Duration.ofMillis(Protocol.DEFAULT_TIMEOUT);
private DefaultJedisClientConfigurationBuilder() {}
上述yml配置中spring.timeout.nano =2 以及spring.timeout.seconds =2 ;在applyProperties中,设置了readTimeout 和connectTimeout属性,这个属性默认的就是Duration.ofMillis(Protocol.DEFAULT_TIMEOUT);后面的配置,大家就自己往下看;
我们再看创建JedisConnectionFactory的最后一步,在createJedisConnectionFactory()方法中,创建完JedisClientConfiguration,开始创建new JedisConnectionFactory(getStandaloneConfig(), clientConfiguration);
我们直接看JedisConnectionFactory中的获取连接的方法 Jedis jedis = fetchJedisConnector();
public RedisConnection getConnection() {
if (isRedisClusterAware()) {
return getClusterConnection();
}
Jedis jedis = fetchJedisConnector();
String clientName = clientConfiguration.getClientName().orElse(null);
JedisConnection connection = (getUsePool() ? new JedisConnection(jedis, pool, getDatabase(), clientName)
: new JedisConnection(jedis, null, getDatabase(), clientName));
connection.setConvertPipelineAndTxResults(convertPipelineAndTxResults);
return postProcessConnection(connection);
}
protected Jedis fetchJedisConnector() {
try {
if (getUsePool() && pool != null) {
return pool.getResource();
}
Jedis jedis = createJedis();
// force initialization (see Jedis issue #82)
jedis.connect();
potentiallySetClientName(jedis);
return jedis;
} catch (Exception ex) {
throw new RedisConnectionFailureException("Cannot get Jedis connection", ex);
}
}
private Jedis createJedis() {
if (providedShardInfo) {
return new Jedis(getShardInfo());
}
Jedis jedis = new Jedis(getHostName(), getPort(), getConnectTimeout(), getReadTimeout(), isUseSsl(),
clientConfiguration.getSslSocketFactory().orElse(null), //
clientConfiguration.getSslParameters().orElse(null), //
clientConfiguration.getHostnameVerifier().orElse(null));
Client client = jedis.getClient();
getRedisPassword().map(String::new).ifPresent(client::setPassword);
client.setDb(getDatabase());
return jedis;
}
后面的就可以不看了,拿到Jedis实例后,就可以做相关操作了;在创建实例的时候,传入的getConnectTimeout(),getReadTimeout()参数中,就是上述readTimeout属性Duration的toMillis()方法;获取的结果跟springboot1.x版本中的int类型的,是一致的
private int getConnectTimeout() {
return Math.toIntExact(clientConfiguration.getConnectTimeout().toMillis());
}
好了,redis在springboot2.0中,自动装配就是这样的,有兴趣的,还可以继续深入的看下去
更多推荐
所有评论(0)