事情是这样的
项目中使用了springboot + spring data redis,但是公司规定,redis密码一律托管,只能远程获取。
开发环境使用的单实例redis,连接池用的是lettuce,同事的是实现是把Spring Data Redis自动装载的代码copy一份搬到项目里,原因从下面的分析中可以看出,Spring相关配置核心类都是包可见的,在外部根本无法继承和引用。
但是,好事者,也就是在下,觉得这“不够Spring”,于是,深挖了一番,并在一番分析之后,给社区提了一个比较中肯的Issue,并且被采纳。
Spring Data Redis 自动装配机制
在org.springframework.boot.autoconfigure.data.redis
中有RedisAutoConfiguration
, 其通过@Import
依赖于LettuceConnectionConfiguration
1 | java复制代码@Configuration(proxyBeanMethods = false) |
LettuceConnectionConfiguration
继承自RedisConnectionConfiguration
,核心代码如下
1 | java复制代码@Configuration(proxyBeanMethods = false) |
从中可以看出,Spring boot 自动装配Lettuce连接工厂的条件如下
① 存在 RedisClient
, lettuce.io
中自带的redis 客户端类
② 项目中使用配置spring.redis.client-type
为lettuce
③ 项目代码中只要不定义RedisConnectionFactory
, 便会自动按照配置文件创建 LettuceConnectionFactory
其中,包含两处关键,
- 构造函数
LettuceConnectionConfiguration
出现的RedisProperties
和两个ObjectProvider
,并且调用了父类构造函数 redisConnectionFactory
中包含两个重要方法getLettuceClientConfiguration
和createLettuceConnectionFactory
, 其中getLettuceClientConfiguration
主要处理Pool连接池的相关配置,不做赘述,从下面的分析也可以知道,properties
其实就是RedisProperties
,重点看createLettuceConnectionFactory
下面,逐个解析这些关键点。
父类构造函数 RedisConnectionConfiguration
1 | java复制代码protected RedisConnectionConfiguration(RedisProperties properties, |
理解这段代码的关键是ObjectProvider
, 其实你如果细心留意,你会发现,Springboot的代码,特别是构造函数,大量的用到ObjectProvider
ObjectProvider
关于ObjectProvider
, 可以简单聊两句 Spring 4.3的一些改进
当构造方法的参数为单个构造参数时,可以不使用@Autowired进行注解
1 | java复制代码@Service |
比如,上面这段代码是spring 4.3之后的版本,不需要@Autowired
也可以正常运行。
同样是在Spring 4.3版本中,不仅隐式的注入了单构造参数的属性,还引入了
ObjectProvider
接口。
1 | java复制代码//A variant of ObjectFactory designed specifically for injection points, allowing for programmatic optionality and lenient not-unique handling. |
从源码注释中可以得知,ObjectProvider
接口是ObjectFactory
接口的扩展,专门为注入点设计的,可以让注入变得更加宽松和更具有可选项。
其中,由getIfAvailable()
可见,当待注入参数的Bean为空或有多个时,便是ObjectProvider
发挥作用的时候。
- 如果注入实例为空时,使用
ObjectProvider
则避免了强依赖导致的依赖对象不存在异常 - 如果有多个实例,
ObjectProvider
的方法会根据Bean实现的Ordered
接口或@Order
注解指定的先后顺序获取一个Bean
, 从而了提供了一个更加宽松的依赖注入方式
回到,RedisConnectionConfiguration
这个父类构造函数本身,其实就是实现这样的功能:如果用户提供了RedisSentinelConfiguration
和 RedisSentinelConfiguration
, 会在构造函数中加载进来,而RedisProperties
则比较简单,就是redis的相关配置。
RedisProperties
从配置中读取redis的相关配置,最简单的单机redis配置的是简单的属性,sentinel是哨兵相关配置,cluster是集群相关配置,Pool是连接池的相关配置
1 | java复制代码@ConfigurationProperties(prefix = "spring.redis") |
小结一下,目前,我们可以看到RedisAutoConfiguration
依赖于配置类LettuceConnectionConfiguration
, 其构造函数读取了用户定义的redis配置,其中包含 单机配置+集群配置+哨兵配置+连接池配置,其中集群配置和哨兵配置是两个允许用户自定义的Bean。
createLettuceConnectionFactory
LettuceConnectionConfiguration
中实现连接池的方法中调用了createLettuceConnectionFactory
, 其实现如下
1 | java复制代码private LettuceConnectionFactory createLettuceConnectionFactory(LettuceClientConfiguration clientConfiguration) { |
其实就是依次读取哨兵的配置,集群的配置 以及 单机的配置,如果有就创建连接池返回。
其中getSentinelConfig()
和 getClusterConfiguration()
是父类的方法,其实现如下,
1 | java复制代码protected final RedisSentinelConfiguration getSentinelConfig() { |
从中,我们可以知道,其优先读取在构造函数中由ObjectProvider
引入的可能存在的用户自定义配置Bean,如果没有,再通过读取RedisProperties
完成装配。
但是,细心的读者要问了,How about 单机配置?
1 | java复制代码protected final RedisStandaloneConfiguration getStandaloneConfig() { |
是的,你没有看错,单身狗不配……
总结起来就是,在构造函数中获取合适的配置bean,然后在创建连接池的方法里面查找,如果没有就用配置文件构造一个,但是不支持单实例的redis。
提一个issue吧
保护单身狗,人人有责,于是,我以“单身狗保护协会”的名义给SpringBoot社区提了一个issue
然后,大佬回复,可以保护可以支持,很开心。
其中,有提到使用BeanPostProcessor
的方法去改写RedisProperties
的配置,中途我有想到,所以把issue关了,沉吟一阵,觉得不优雅,不开心,又把issue给打开了,很感谢开源团队的支持和理解,备受鼓舞。
本文转载自: 掘金