RedisAutoConfiguration、LettuceConnectionConfiguration源码解析
一、pom.xml依赖,本文基于2.5.1版本进行源码分析
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-data-redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<version>2.3.12.RELEASE</version>
</dependency>
给一个常用的maven依赖查找官网地址:
Maven Repository: Search/Browse/Explore (mvnrepository.com)
二、springboot启动自动装配:springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration
/** * {@link EnableAutoConfiguration Auto-configuration} for Spring Data's Redis support.*/ @Configuration(proxyBeanMethods = false) 配置Lite轻量模式启动,多实例bean @ConditionalOnClass(RedisOperations.class) 简单理解Spring工程中引用了该类的包 才会构建这个bean @EnableConfigurationProperties(RedisProperties.class) redis配置文件加载 @Import({ LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class }) 2种客户端连接 在SpringBoot2.0之后默认Lettuce客户端连接Redis服务器 public class RedisAutoConfiguration { ....... }
/** * Configuration properties for Redis. * */ @ConfigurationProperties(prefix = "spring.redis") public class RedisProperties {....}
其中类部分属性如下:redis配置类中还详细讲了集群、池、哨兵模式、客户端等内容的配置,这里不一一列举了
其他注解简单理解:
@ConditionalOnBean(仅仅在当前上下文中存在某个对象时,才会实例化一个Bean)
@ConditionalOnClass(某个class位于类路径上,才会实例化一个Bean)
@ConditionalOnExpression(当表达式为true的时候,才会实例化一个Bean)
@ConditionalOnMissingBean(仅仅在当前上下文中不存在某个对象时,才会实例化一个Bean)
@ConditionalOnMissingClass(某个class类路径上不存在的时候,才会实例化一个Bean)
@ConditionalOnNotWebApplication(不是web应用)
Jedis和Lettuce的区别
jedis和Lettuce都是Redis的客户端,它们都可以连接Redis服务器,但是在SpringBoot2.0之后默认都是使用的Lettuce这个客户端连接Redis服务器。因为当使用Jedis客户端连接Redis服务器的时候,每个线程都要拿自己创建的Jedis实例去连接Redis客户端,当有很多个线程的时候,不仅开销大需要反复的创建关闭一个Jedis连接,而且也是线程不安全的,一个线程通过Jedis实例更改Redis服务器中的数据之后会影响另一个线程;
但是如果使用Lettuce这个客户端连接Redis服务器的时候,就不会出现上面的情况,Lettuce底层使用的是Netty,当有多个线程都需要连接Redis服务器的时候,可以保证只创建一个Lettuce连接,使所有的线程共享这一个Lettuce连接,这样可以减少创建关闭一个Lettuce连接时候的开销;而且这种方式也是线程安全的,不会出现一个线程通过Lettuce更改Redis服务器中的数据之后而影响另一个线程的情况;
springboot版本大于2.0,所以我们项目中看到jedis客户端连接是报红的,需要单独的引入jedis包才能使用jedis客户端连接redis服务器,这里我们就主要分析下lettuce方式连接服务器。
三、LettuceConnectionConfiguration源码解析
1、构造函数:
// 参数1来自于 application.properties 中以 spring.redis 为前缀的属性
// 参数2来自于我们注入Spring容器的RedisSentinelConfiguration实例
// 参数3来自于我们注入Spring容器的RedisClusterConfiguration实例
LettuceConnectionConfiguration(RedisProperties properties, ObjectProvider<RedisSentinelConfiguration> sentinelConfigurationProvider, ObjectProvider<RedisClusterConfiguration> clusterConfigurationProvider) { super(properties, sentinelConfigurationProvider, clusterConfigurationProvider); }
针对objectprovider参数可以阅读下这边文章:
(37条消息) Spring ObjectProvider使用说明_OkidoGreen的博客-CSDN博客
什么时候使用ObjectProvider接口?
如果待注入参数的Bean为空或有多个时,便是ObjectProvider发挥作用的时候了。
- 如果注入实例为空时,使用ObjectProvider则避免了强依赖导致的依赖对象不存在异常;
- 如果有多个实例,ObjectProvider的方法会根据Bean实现的Ordered接口或@Order注解指定的先后顺序获取一个Bean。从而了提供了一个更加宽松的依赖注入方式。
2、LettuceConnectionFactory实例注入容器
@Bean @ConditionalOnMissingBean(RedisConnectionFactory.class) LettuceConnectionFactory redisConnectionFactory( ObjectProvider<LettuceClientConfigurationBuilderCustomizer> builderCustomizers, ClientResources clientResources) {
// 获取客户端client配置 LettuceClientConfiguration clientConfig = getLettuceClientConfiguration(builderCustomizers, clientResources, getProperties().getLettuce().getPool()); return createLettuceConnectionFactory(clientConfig); }
// 获取连接工厂实例 优先级:哨兵模式 > 集群模式 > 单机模式 private LettuceConnectionFactory createLettuceConnectionFactory(LettuceClientConfiguration clientConfiguration) { if (getSentinelConfig() != null) { // 哨兵模式 return new LettuceConnectionFactory(getSentinelConfig(), clientConfiguration); } if (getClusterConfiguration() != null) { // 集群模式 return new LettuceConnectionFactory(getClusterConfiguration(), clientConfiguration); } return new LettuceConnectionFactory(getStandaloneConfig(), clientConfiguration); }
这段源码可以很明显的知道当3种配置同时存在时候,实际中只会采用一种模式创建工厂连接实例
3、获取客户端配置
private void customizeConfigurationFromUrl(LettuceClientConfiguration.LettuceClientConfigurationBuilder builder) { ConnectionInfo connectionInfo = parseUrl(getProperties().getUrl()); if (connectionInfo.isUseSsl()) { builder.useSsl(); // 解析出来用户 密码基本在这里没有用到 } }
// 合法的参数,形如字符串 redis://user:password@example.com:6379 protected ConnectionInfo parseUrl(String url) { try { URI uri = new URI(url); // 协议名,以 redis:// 或者 rediss:// 开头 String scheme = uri.getScheme(); if (!"redis".equals(scheme) && !"rediss".equals(scheme)) { throw new RedisUrlSyntaxException(url); } 如果是 rediss ,则表示使用 SSL 安全协议 boolean useSsl = ("rediss".equals(scheme)); String username = null; String password = null; // 指的是url中双斜杠之后,@之前的内容 if (uri.getUserInfo() != null) { String candidate = uri.getUserInfo(); int index = candidate.indexOf(':'); if (index >= 0) { // redis用户名不是必须传入的 // 如果是 username:pwd 的形式 username = candidate.substring(0, index); password = candidate.substring(index + 1); } else { // 如果是 username 的形式 password = candidate; } } return new ConnectionInfo(uri, useSsl, username, password); } catch (URISyntaxException ex) { throw new RedisUrlSyntaxException(url, ex); } }
4-1 如果服务器为哨兵模式,客户端对应哨兵模式的配置:
protected final RedisSentinelConfiguration getSentinelConfig() { // 如果这个不为空,则说明Spring容器中有RedisSentinelConfiguration类型的Bean // 同时说明,从优先级来看,Java代码注入的RedisSentinelConfiguration类型的Bean > application.properties 中以 spring.redis.sentinel 为前缀的配置 if (this.sentinelConfiguration != null) { return this.sentinelConfiguration; } RedisProperties.Sentinel sentinelProperties = this.properties.getSentinel(); if (sentinelProperties != null) { RedisSentinelConfiguration config = new RedisSentinelConfiguration(); // 哨兵服务器可以监控多组 master-slave,这里指定连接其中某组 master-slave 的名字 // 例如,sentinel.conf 中的配置 sentinel monitor mymaster 172.22.0.3 6379 2 // mymaster就是我们需要的值 config.master(sentinelProperties.getMaster()); // 哨兵服务器的 ip:port 解析成 RedisNode config.setSentinels(createSentinels(sentinelProperties)); config.setUsername(this.properties.getUsername()); // 如果 redis-server 配置了 requirepass 属性,则客户端需要提供密码 if (this.properties.getPassword() != null) { config.setPassword(RedisPassword.of(this.properties.getPassword())); } // 如果 redis-sentinel 配置了 requirepass 属性,则客户端需要提供密码 if (sentinelProperties.getPassword() != null) { config.setSentinelPassword(RedisPassword.of(sentinelProperties.getPassword())); } config.setDatabase(this.properties.getDatabase()); return config; } return null; }
4-2 如果服务器为集群模式,客户端对应集群模式的配置:
protected final RedisClusterConfiguration getClusterConfiguration() { // 如果这个不为空,则说明Spring容器中有RedisClusterConfiguration类型的Bean // 同时说明,从优先级来看,Java代码注入的RedisClusterConfiguration类型的Bean > application.properties 中以 spring.redis.cluster 前缀的配置 if (this.clusterConfiguration != null) { return this.clusterConfiguration; } if (this.properties.getCluster() == null) { return null; } RedisProperties.Cluster clusterProperties = this.properties.getCluster(); // Redis 集群节点配置,形式为 ip:port,多个节点之间用逗号分隔 RedisClusterConfiguration config = new RedisClusterConfiguration(clusterProperties.getNodes()); if (clusterProperties.getMaxRedirects() != null) { config.setMaxRedirects(clusterProperties.getMaxRedirects()); } config.setUsername(this.properties.getUsername()); // Redis 集群节点设置了密码,则客户端需要提供密码 if (this.properties.getPassword() != null) { config.setPassword(RedisPassword.of(this.properties.getPassword())); } return config; }
5、探究LettuceConnectionFactory
有参构造器:单机、哨兵、集群
本文来自博客园,作者:chch213,转载请注明原文链接:https://www.cnblogs.com/chch213/p/16270402.html