SpringBoot揭密:spring-boot-starter-data-redis

平航
2023-12-01

一. POM解析

  <dependencies>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter</artifactId>
      <version>2.0.0.RELEASE</version>
      <scope>compile</scope>
    </dependency>
    <dependency>
      <groupId>org.springframework.data</groupId>
      <artifactId>spring-data-redis</artifactId>
      <version>2.0.5.RELEASE</version>
      <scope>compile</scope>
      <exclusions>
        <exclusion>
          <artifactId>jcl-over-slf4j</artifactId>
          <groupId>org.slf4j</groupId>
        </exclusion>
      </exclusions>
    </dependency>
    <dependency>
      <groupId>io.lettuce</groupId>
      <artifactId>lettuce-core</artifactId>
      <version>5.0.2.RELEASE</version>
      <scope>compile</scope>
    </dependency>
  </dependencies>

        可以看到主要引入了三个依赖:

        1.spring-boot-starter:该包引入了starter完成自动装配所必须的class和注解等,包含了自动装配的核心逻辑,springboot中默认可以自动装配的starter也定义在其中的spring-boot-autoconfigure中的spring.factories文件中,本文要讲的RedisAutoConfiguration就定义在其中。有了这个包后,即拥有了自动装配的能力了,例如新建一个springboot项目,pom中只引入spring-boot-starter-data-redis包,其就能自动装配了。

        2.spring-data-redis:spring对redis相关操作的人性化封装,使得redis的操作只需简单的调用接口即可,redis的操作的实现过程则有lettuce或jedis驱动(客户端)实现,spring-data-redis只是在接口层对他们做了统一。spring-data-redis包依赖了jedis和lettuce-core这两个驱动,但是optional为true,即这两个依赖不会传递到spring-boot-starter-data-redis中,但是该starter肯定是要依赖一个驱动的,那么其引入了第三个包,即将lettuce做为其默然实现。

        3.lettuce-core:spring-boot-starter-data-redis选择的默然驱动

二. Lettuce和Jedis的区别

        Lettuce 是一个可伸缩的线程安全的 Redis 客户端,支持同步、异步和响应式模式。多个线程可以共享一个连接实例,而不必担心多线程并发问题。它基于优秀 Netty NIO 框架构建,支持 Redis 的高级功能,如 Sentinel,集群,流水线,自动重新连接和 Redis 数据模型

        Jedis在实现上是直接连接的redis server,如果在多线程环境下是非线程安全的,这个时候只有使用连接池,为每个Jedis实例增加物理连接 Lettuce的连接是基于Netty的,连接实例(StatefulRedisConnection)可以在多个线程间并发访问,应为StatefulRedisConnection是线程安全的,所以一个连接实例(StatefulRedisConnection)就可以满足多线程环境下的并发访问,当然这个也是可伸缩的设计,一个连接实例不够的情况也可以按需增加连接实例。

        从 Spring Boot 2.x 开始 Lettuce 已取代 Jedis 成为首选 Redis 的客户端。当然 Spring Boot 2.x 仍然支持 Jedis,并且你可以任意切换客户端,只需排除io.lettuce:lettuce-core并添加redis.clients:jedis即可。

三. 自动装配源码解析

        自动装配类RedisAutoConfiguration在spring-boot-autoconfigure中已经提供,故spring-boot-starter-data-redis.jar中没有再去提供该class。

@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;
	}
}

        首先Redis的自动化配置依靠的是RedisAutoConfiguration,其会按照顺序分别引入LettuceConnectionConfiguration和JedisConnectionConfiguration,它们都会判断Spring容器中是否存在ConnectionFactory,不存在则创建。正是这个引入的顺序,导致LettuceConnectionConfiguration要比JedisConnectionConfiguration先执行,所以当LettuceConnectionConfiguration创建了ConnectionFactor后,JedisConnectionConfiguration判断不为空而不继续创建了。所以即使我们引入了Jedis依赖,最后也还是使用Lettuce客户端。

四. 修改RedisTemplate的序列化器

        RedisTemplate间接实现了InitializingBean,故在Bean的生命周期内会被回调其afterPropertiesSet方法,此方法用来初始化key和value的序列化器,默认是JDK序列化器,但该序列化器需要数据对象实现Serializable接口,最后显示声明序列化ID,容易出错。可以修改利用json的序列化与反序列化能力,系列化器修改如下:

@Configuration
public class RedisTemplateConfig {

    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(redisConnectionFactory);
        GenericJackson2JsonRedisSerializer jackson2JsonRedisSerializer = new GenericJackson2JsonRedisSerializer();
        // 设置值(value)的序列化采用FastJsonRedisSerializer。
        redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
        // 设置键(key)的序列化采用StringRedisSerializer。
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setHashKeySerializer(new StringRedisSerializer());
        redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);
        return redisTemplate;
    }

}
 类似资料: