【Spring Boot 源碼解讀】之 【為何引入了 Jedis 依賴最后用的還是 Lettuce 客戶端?】


1、Spring Boot 2.x 的兩種 Redis 客戶端

首先,我們都知道,從 Spring Boot 2.x 開始 Lettuce 已取代 Jedis 成為首選 Redis 的客戶端。當然 Spring Boot 2.x 仍然支持 Jedis,並且你可以任意切換客戶端。至於為什么會使用 Lettuce 替換 Jedis,大家可自行上網搜索。

2、我就是要使用 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

3、源碼分析

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 的依賴 。

4、如何正確使用 Jedis 客戶端

從上面的源碼分析看到,我們有兩種方案,不過前提都是先引入 Jedis 依賴。

第一:直接去掉 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>

第二:在啟動類的 @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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM