【Spring Cloud 源码解读】Spring Boot 如何正确引入 Jedis 和 Lettuce 客户端

1591次阅读  |  发布于4年以前

一、Spring Boot 2.x 的两种 Redis 客户端

首先,我们都知道,从 Spring Boot 2.x 开始 Lettuce 已取代 Jedis 成为首选 Redis 的客户端。当然 Spring Boot 2.x 仍然支持 Jedis,并且你可以任意切换客户端。至于为什么会使用 Lettuce 替换 Jedis,大家可自行上网搜索。

二、我就是要使用 Jedis !

那么如果我们还是要在项目中使用 Jedis 作为 Redis 的客户端呢?是不是引入 Jedis 依赖即可?下面来试试。

引入依赖:

<dependency>
   <groupId>redis.clients</groupId>
   <artifactId>jedis</artifactId>
</dependency>

配置:

可以加上下面的关于连接池配置,或者不加,因为有默认值。

spring.redis.jedis.pool.max-active=10
spring.redis.jedis.pool.min-idle=1
.....

例子代码:

我们在访问接口时,打印 RedisTemplateConnectionFactory 即可知道使用的是哪个客户端。

/**
* @author Howinfun
* @desc
* @date 2020/1/15
*/
@RestController
public class TestController {

   @Autowired
   private RedisTemplate redisTemplate;

   @GetMapping("/test")
   public String test(){
       System.out.println(redisTemplate.getConnectionFactory());
       return "hello";
  }
}

神奇的 Lettuce 客户端:

启动项目,访问接口,可是我们看到,控制台打印出来的竟然还是 LettuceConnectionFactory

org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory@21891c74

三、源码分析

Redis 的自动配置类:

首先看到 RedisAutoConfiguration

@Configuration
@ConditionalOnClass({RedisOperations.class})
@EnableConfigurationProperties({RedisProperties.class})
// 导入Lettuce和Jedis的连接配置类,而下面的创建RedisTemplate的RedisConnectionFactory在这两个配置类里都有生成。
@Import({LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class})
public class RedisAutoConfiguration {
   public 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;
  }
}

Lettuce 的连接配置类:

然后到LettuceConnectionConfiguration

@Configuration
// 当存在RedisClient.class时执行。ps:RedisClient是Lettuce依赖里面的类
@ConditionalOnClass({RedisClient.class})
class LettuceConnectionConfiguration extends RedisConnectionConfiguration {
   // .... 省略

   @Bean
   // 当Spring容器不存在RedisConnectionFactory类型的bean对象时执行
   @ConditionalOnMissingBean({RedisConnectionFactory.class})
   public LettuceConnectionFactory redisConnectionFactory(ClientResources clientResources) throws UnknownHostException {

       LettuceClientConfiguration clientConfig = this.getLettuceClientConfiguration(clientResources, this.properties.getLettuce().getPool());
       // 创建connectinFactory
       return this.createLettuceConnectionFactory(clientConfig);
  }

   // ..... 省略
}

Jedis 的连接配置类:

最后到JedisConnectionConfiguration

@Configuration
// 当同时存在GenericObjectPool.class, JedisConnection.class, Jedis.class时执行。ps:JedisConnection和Jedis都是Jedis依赖里面的类。GenericObjectPool是spring-boot-starter-data-redis里的类。
@ConditionalOnClass({GenericObjectPool.class, JedisConnection.class, Jedis.class})
class JedisConnectionConfiguration extends RedisConnectionConfiguration {

   // .... 省略

   @Bean
   // 当Spring容器不存在RedisConnectionFactory类型的bean对象时执行
   @ConditionalOnMissingBean({RedisConnectionFactory.class})
   public JedisConnectionFactory redisConnectionFactory() throws UnknownHostException {

       // 创建connectinFactory
       return this.createJedisConnectionFactory();
  }

   // .... 省略
}

总结:

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

ps: Spring Boot 2.x 使用 Lettuce 的原理:首先是依靠 @Import 的引入顺序,然后是 spring-boot-starter-data-redis 里有 Lettuce 的依赖,而没有 Jedis 的依赖 。

四、如何正确使用 Jedis 客户端

从上面的源码分析看到,我们有两种方案,不过前提都是先引入 Jedis 依赖。

1、直接去掉 Lettuce 的依赖

使 LettuceConnectionConfiguration 不能生效,因为 @ConditionalOnClass({RedisClient.class}) 的不再成立,导致其不会生效执行。

<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-data-redis</artifactId>
   <exclusions>
       <exclusion>
           <groupId>io.lettuce</groupId>
           <artifactId>lettuce-core</artifactId>
       </exclusion>
   </exclusions>
</dependency>

2、在启动类的 @SpringBootApplication 去掉 RedisAutoConfiguration ,然后自己自定义一个 RedisAutoConfiguration

在自定义的 RedisAutoConfiguration 中修改 @Import 关于两个客户端的 ConnectionConfiguration 的引入顺序即可。但是其实这样做很没必要,还不如直接去掉依赖。

@SpringBootApplication(exclude = RedisAutoConfiguration.class)
@Configuration
@ConditionalOnClass({RedisOperations.class})
@EnableConfigurationProperties({RedisProperties.class})
// 先 Jedis 后 Lettuce
@Import({JedisConnectionConfiguration.class, LettuceConnectionConfiguration.class})
public class MyRedisAutoConfiguration {
   public MyRedisAutoConfiguration() {
  }

   // ...... 省略
}

最后:

我们可以看到控制台打印的终于是 JedisConnectionFactory了。

org.springframework.data.redis.connection.jedis.JedisConnectionFactory@16c9834c

Copyright© 2013-2020

All Rights Reserved 京ICP备2023019179号-8