Spring Boot 源码学习系列
上篇博文,Huazie 带大家从源码角度分析了 Spring Boot 内置的有关 Redis 的自动配置类【RedisAutoConfiguration】,其中有关 LettuceConnectionConfiguration 和 JedisConnectionConfiguration 这两个用于配置 Redis 连接的具体实现还未介绍。本篇就以我们常用的 Jedis 实现 为例,带大家详细分析一下 JedisConnectionConfiguration 配置类。
在开始本篇的内容介绍之前,我们先来看看往期的系列文章【有需要的朋友,欢迎关注系列专栏】:
Spring Boot 源码学习 |
Spring Boot 项目介绍 |
Spring Boot 核心运行原理介绍 |
【Spring Boot 源码学习】@EnableAutoConfiguration 注解 |
【Spring Boot 源码学习】@SpringBootApplication 注解 |
【Spring Boot 源码学习】走近 AutoConfigurationImportSelector |
【Spring Boot 源码学习】自动装配流程源码解析(上) |
【Spring Boot 源码学习】自动装配流程源码解析(下) |
【Spring Boot 源码学习】深入 FilteringSpringBootCondition |
【Spring Boot 源码学习】OnClassCondition 详解 |
【Spring Boot 源码学习】OnBeanCondition 详解 |
【Spring Boot 源码学习】OnWebApplicationCondition 详解 |
【Spring Boot 源码学习】@Conditional 条件注解 |
【Spring Boot 源码学习】HttpEncodingAutoConfiguration 详解 |
【Spring Boot 源码学习】RedisAutoConfiguration 详解 |
RedisConnectionFactory 是 Spring Data Redis 中的一个接口,它提供了创建和管理 Redis 连接的方法。使用 RedisConnectionFactory 可以获取到 Redis 连接对象,然后通过该对象对 Redis 进行存储、查询、删除等操作。
我们来看看 RedisConnectionFactory 的相关的源码:
// 线程安全的 Redis 连接工厂 public interface RedisConnectionFactory extends PersistenceExceptionTranslator { RedisConnection getConnection(); RedisClusterConnection getClusterConnection(); boolean getConvertPipelineAndTxResults(); RedisSentinelConnection getSentinelConnection(); }
我们简单分析一下 Redis 连接工厂中的方法:
以常用的 Jedis 实现为例,我们介绍一下 Redis 连接工厂的 Jedis 实现,即 JedisConnectionFactory。由于该类这是 Spring Data Redis 中的代码,本篇不详细展开了,感兴趣的朋友可以自行翻阅 Spring 源码进行查看。
那 JedisConnectionFactory 主要有哪些内容呢 ?
创建 Jedis 连接 :通过调用 createXXX() 方法,可以创建一个 Jedis 连接对象,用于与 Redis 服务器进行通信。当然,在获取连接之前,我们必须先初始化该连接工厂。
管理连接池 :它内部维护了一个连接池,用于管理和复用 Jedis 连接。当需要创建一个新的 Jedis 连接时,首先会检查连接池中是否有可用的连接,如果有则直接使用,否则创建一个新的连接。这样可以提高性能,减少频繁创建和关闭连接带来的开销。
protected Jedis fetchJedisConnector() { try { if (getUsePool() && pool != null) { return pool.getResource(); } Jedis jedis = createJedis(); // force initialization (see Jedis issue #82) jedis.connect(); return jedis; } catch (Exception ex) { throw new RedisConnectionFailureException("Cannot get Jedis connection", ex); } }
配置连接参数 :允许用户自定义连接参数,例如 超时时间、最大连接数等。这些参数可以在创建连接时通过构造函数传入,也可以在创建连接后,通过 JedisPoolConfig 或者下面的三种连接类型的配置类进行修改。
支持多种连接类型 :包括 单机连接、哨兵连接 和 集群连接。这些连接类型的配置如下:
单机连接,我们需要使用到 RedisStandaloneConfiguration ,可见如下示例:
@Configuration public class RedisConfig { @Bean public JedisConnectionFactory jedisConnectionFactory() { RedisStandaloneConfiguration configuration = new RedisStandaloneConfiguration("localhost", 6379); JedisConnectionFactory jedisConnectionFactory = new JedisConnectionFactory(configuration); return jedisConnectionFactory; } }
集群连接,我们需要使用到 RedisClusterConfiguration ,示例如下:
@Configuration public class RedisConfig { @Bean public JedisConnectionFactory jedisConnectionFactory() { Setnodes = new HashSet<>(); nodes.add(new Host("127.0.0.1", 20011)); nodes.add(new Host("127.0.0.1", 20012)); nodes.add(new Host("127.0.0.1", 20013)); RedisClusterConfiguration clusterConfiguration = new RedisClusterConfiguration(); clusterConfiguration.setClusterNodes(nodes); JedisConnectionFactory jedisConnectionFactory = new JedisConnectionFactory(clusterConfiguration); return jedisConnectionFactory; } }
哨兵连接,我们需要使用到 RedisSentinelConfiguration ,参考如下:
@Configuration public class RedisConfig { @Bean public JedisConnectionFactory jedisConnectionFactory() { Setsentinels = new HashSet<>(); sentinels.add(new Host("127.0.0.1", 30001)); sentinels.add(new Host("127.0.0.1", 30002)); sentinels.add(new Host("127.0.0.1", 30003)); RedisSentinelConfiguration sentinelConfiguration = new RedisSentinelConfiguration(); sentinelConfiguration.setMasterName("mymaster"); sentinelConfiguration.setSentinels(sentinels); JedisConnectionFactory jedisConnectionFactory = new JedisConnectionFactory(sentinelConfiguration); return jedisConnectionFactory; } }
那么 Spring Data Redis 的 JedisConnectionFactory 的自动配置在 Spring Boot 是如何实现的呢?
Spring Boot 是通过内置的 JedisConnectionConfiguration 配置类来完成这一功能。下面我们具体分析一下:
注意: 以下涉及 Spring Boot 源码 均来自版本 2.7.9,其他版本有所出入,可自行查看源码。
翻看 JedisConnectionConfiguration 的源码,我们发现它继承了 RedisConnectionConfiguration 类,该类的部分源码如下:
abstract class RedisConnectionConfiguration { private static final boolean COMMONS_POOL2_AVAILABLE = ClassUtils.isPresent("org.apache.commons.pool2.ObjectPool", RedisConnectionConfiguration.class.getClassLoader()); // 。。。 protected final RedisStandaloneConfiguration getStandaloneConfig() { // 。。。 } protected final RedisSentinelConfiguration getSentinelConfig() { // 。。。 } protected final RedisClusterConfiguration getClusterConfiguration() { // 。。。 } protected final RedisProperties getProperties() { // 。。。 } protected boolean isPoolEnabled(Pool pool) { Boolean enabled = pool.getEnabled(); return (enabled != null) ? enabled : COMMONS_POOL2_AVAILABLE; } private ListcreateSentinels(RedisProperties.Sentinel sentinel) { // 。。。 } protected ConnectionInfo parseUrl(String url) { // 。。。 } static class ConnectionInfo { private final URI uri; private final boolean useSsl; private final String username; private final String password; // 。。。 } }
简单阅读上述的源码,我们可以很快总结一下:
其中内部静态类 ConnectionInfo,用于存储解析后的连接信息,包括:
上篇博文中,我们已经知道了 JedisConnectionConfiguration 是在 RedisAutoConfiguration 中通过 @Import({ LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class }) 导入的。
我们在 META-INF/spring-autoconfigure-metadata.properties 文件中,发现了有关 JedisConnectionConfiguration 的相关配置:
org.springframework.boot.autoconfigure.data.redis.JedisConnectionConfiguration= org.springframework.boot.autoconfigure.data.redis.JedisConnectionConfiguration.ConditionalOnClass=org.apache.commons.pool2.impl.GenericObjectPool,redis.clients.jedis.Jedis,org.springframework.data.redis.connection.jedis.JedisConnection
显然这里涉及到了 ConditionalOnClass 注解,我们翻看 JedisConnectionConfiguration 配置类的源码,如下:
@Configuration(proxyBeanMethods = false) @ConditionalOnClass({ GenericObjectPool.class, JedisConnection.class, Jedis.class }) @ConditionalOnMissingBean(RedisConnectionFactory.class) @ConditionalOnProperty(name = "spring.redis.client-type", havingValue = "jedis", matchIfMissing = true) class JedisConnectionConfiguration extends RedisConnectionConfiguration { // 。。。 @Bean JedisConnectionFactory redisConnectionFactory( ObjectProviderbuilderCustomizers) { return createJedisConnectionFactory(builderCustomizers); } // 。。。 }
我们先来看看上述 JedisConnectionConfiguration 配置类涉及到的注解,如下:
通过翻看 JedisConnectionConfiguration 的源码,我们可以看到 redisConnectionFactory 方法是被 @Bean 注解标注的,意味着该方法创建的 Jedis 连接工厂将成为 Spring 管理的 Bean 对象。
该方法接受一个入参 ObjectProvider
进入 redisConnectionFactory 方法,我们看到它直接调用了 createJedisConnectionFactory 方法并返回一个 JedisConnectionFactory 对象。
我们继续查看 createJedisConnectionFactory 方法:
private JedisConnectionFactory createJedisConnectionFactory( ObjectProviderbuilderCustomizers) { JedisClientConfiguration clientConfiguration = getJedisClientConfiguration(builderCustomizers); if (getSentinelConfig() != null) { return new JedisConnectionFactory(getSentinelConfig(), clientConfiguration); } if (getClusterConfiguration() != null) { return new JedisConnectionFactory(getClusterConfiguration(), clientConfiguration); } return new JedisConnectionFactory(getStandaloneConfig(), clientConfiguration); }
我们详细来分析一下上述代码:
首先,调用 getJedisClientConfiguration 方法返回一个 JedisClientConfiguration 配置类对象。
继续进入 getJedisClientConfiguration 方法:
private JedisClientConfiguration getJedisClientConfiguration( ObjectProviderbuilderCustomizers) { JedisClientConfigurationBuilder builder = applyProperties(JedisClientConfiguration.builder()); RedisProperties.Pool pool = getProperties().getJedis().getPool(); if (isPoolEnabled(pool)) { applyPooling(pool, builder); } if (StringUtils.hasText(getProperties().getUrl())) { customizeConfigurationFromUrl(builder); } builderCustomizers.orderedStream().forEach((customizer) -> customizer.customize(builder)); return builder.build(); }
首先,调用 applyProperties 方法,获取一个 JedisClientConfigurationBuilder 对象,用于构建 JedisClientConfiguration 对象。
private JedisClientConfigurationBuilder applyProperties(JedisClientConfigurationBuilder builder) { PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull(); map.from(getProperties().isSsl()).whenTrue().toCall(builder::useSsl); map.from(getProperties().getTimeout()).to(builder::readTimeout); map.from(getProperties().getConnectTimeout()).to(builder::connectTimeout); map.from(getProperties().getClientName()).whenHasText().to(builder::clientName); return builder; }
该方法的主要目的是根据属性配置来定制 builder 对象。
首先,创建一个 PropertyMapper 对象 map,并调用其 alwaysApplyingWhenNonNull() 方法,以便在非空情况下始终应用映射规则。
接下来,使用 map.from() 方法设置映射规则。这里分别设置了以下映射规则:
最后,返回经过配置的 builder 对象。
接着,从 RedisProperties 中获取 Jedis 连接池的配置信息。
enabled : 是否启用连接池。如果可用,则自动启用。在 Jedis 中,哨兵模式下的连接池是隐式启用的,此设置仅适用于单节点设置。
maxIdle : 池中空闲连接的最大数量。使用负值表示无限数量的空闲连接。
minIdle : 池中保持最小空闲连接的目标数量。此设置仅在空闲连接和驱逐运行之间的时间都为正时才有效。
maxActive : 给定时间内,连接池可以分配的最大连接数。使用负值表示无限制。
maxWait : 当连接池耗尽时,连接分配应阻塞的最长时间。使用负值表示无限期阻塞。
timeBetweenEvictionRuns : 空闲对象驱逐线程的运行时间间隔。当值为正时,空闲对象驱逐线程开始运行,否则不执行空闲对象驱逐。
然后,判断连接池是否启用 ?
如果启用,则调用 applyPooling 方法,将连接池配置应用到 builder 对象上。
private void applyPooling(RedisProperties.Pool pool, JedisClientConfiguration.JedisClientConfigurationBuilder builder) { builder.usePooling().poolConfig(jedisPoolConfig(pool)); } private JedisPoolConfig jedisPoolConfig(RedisProperties.Pool pool) { JedisPoolConfig config = new JedisPoolConfig(); 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()); } return config; }
判断属性中的 spring.redis.url 是否包含非空的文本内容?
private void customizeConfigurationFromUrl(JedisClientConfiguration.JedisClientConfigurationBuilder builder) { ConnectionInfo connectionInfo = parseUrl(getProperties().getUrl()); if (connectionInfo.isUseSsl()) { builder.useSsl(); } }
遍历 builderCustomizers 中的所有自定义器,并对每个自定义器调用其 customize 方法,传入 builder 作为参数,用于进一步定制 Jedis 客户端的配置。
接着,获取哨兵模式配置,并判断是否为空,如果不为空,则直接根据哨兵模式的配置创建并返回一个连接工厂实例。
然后,获取集群模式配置,并判断是否为空,如果不为空,则直接根据集群模式的配置创建并返回一个连接工厂实例。
最后,获取单机模式配置,根据单机模式的配置创建并返回一个连接工厂实例。
本篇我们深入分析了 JedisConnectionConfiguration 配置类的相关内容,该类用于配置 Redis 连接的 Jedis 实现。