<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          我給SpringBoot提了個(gè)issue,被采納了…

          共 7510字,需瀏覽 16分鐘

           ·

          2021-09-22 09:48

          事情是這樣的

          項(xiàng)目中使用了springboot + spring data redis,但是公司規(guī)定,redis密碼一律托管,只能遠(yuǎn)程獲取。

          開發(fā)環(huán)境使用的單實(shí)例redis,連接池用的是lettuce,同事的是實(shí)現(xiàn)是把Spring Data Redis自動(dòng)裝載的代碼copy一份搬到項(xiàng)目里,原因從下面的分析中可以看出,Spring相關(guān)配置核心類都是包可見的,在外部根本無法繼承和引用。

          但是,好事者,也就是在下,覺得這“不夠Spring”,于是,深挖了一番,并在一番分析之后,給社區(qū)提了一個(gè)比較中肯的Issue,并且被采納。

          Spring Data Redis 自動(dòng)裝配機(jī)制

          org.springframework.boot.autoconfigure.data.redis中有RedisAutoConfiguration, 其通過@Import依賴于LettuceConnectionConfiguration

          @Configuration(proxyBeanMethods = false)
          @ConditionalOnClass(RedisOperations.class)
          @EnableConfigurationProperties(RedisProperties.class)
          @Import({ LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class })
          public class RedisAutoConfiguration {

          @Bean
          @ConditionalOnMissingBean(name = "redisTemplate")
          @ConditionalOnSingleCandidate(RedisConnectionFactory.class)
          public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
          RedisTemplate<Object, Object> template = new RedisTemplate<>();
          template.setConnectionFactory(redisConnectionFactory);
          return template;
          }

          }
          復(fù)制代碼

          LettuceConnectionConfiguration 繼承自RedisConnectionConfiguration,核心代碼如下

          @Configuration(proxyBeanMethods = false)
          @ConditionalOnClass(RedisClient.class) // -->①
          @ConditionalOnProperty(name = "spring.redis.client-type", havingValue = "lettuce", matchIfMissing = true) // -->②
          class LettuceConnectionConfiguration extends RedisConnectionConfiguration {

          LettuceConnectionConfiguration(RedisProperties properties,
          ObjectProvider<RedisSentinelConfiguration> sentinelConfigurationProvider,
          ObjectProvider<RedisClusterConfiguration> clusterConfigurationProvider) {
          super(properties, sentinelConfigurationProvider, clusterConfigurationProvider);
          }

          @Bean
          @ConditionalOnMissingBean(RedisConnectionFactory.class) // -->③
          LettuceConnectionFactory redisConnectionFactory(
          ObjectProvider<LettuceClientConfigurationBuilderCustomizer> builderCustomizers,
          ClientResources clientResources)
          {
          LettuceClientConfiguration clientConfig = getLettuceClientConfiguration(builderCustomizers, clientResources,
          getProperties().getLettuce().getPool());
          return createLettuceConnectionFactory(clientConfig);
          }
          }
          復(fù)制代碼

          從中可以看出,Spring boot 自動(dòng)裝配Lettuce連接工廠的條件如下

          ① 存在 RedisClientlettuce.io 中自帶的redis 客戶端類

          ② 項(xiàng)目中使用配置spring.redis.client-typelettuce

          ③ 項(xiàng)目代碼中只要不定義RedisConnectionFactory , 便會(huì)自動(dòng)按照配置文件創(chuàng)建 LettuceConnectionFactory


          其中,包含兩處關(guān)鍵,

          • 構(gòu)造函數(shù)LettuceConnectionConfiguration 出現(xiàn)的RedisProperties 和兩個(gè)ObjectProvider,并且調(diào)用了父類構(gòu)造函數(shù)

          • redisConnectionFactory 中包含兩個(gè)重要方法getLettuceClientConfigurationcreateLettuceConnectionFactory, 其中getLettuceClientConfiguration 主要處理Pool連接池的相關(guān)配置,不做贅述,從下面的分析也可以知道,properties其實(shí)就是RedisProperties,重點(diǎn)看createLettuceConnectionFactory

          下面,逐個(gè)解析這些關(guān)鍵點(diǎn)。

          父類構(gòu)造函數(shù) RedisConnectionConfiguration

          protected RedisConnectionConfiguration(RedisProperties properties,
          ObjectProvider<RedisSentinelConfiguration> sentinelConfigurationProvider,
          ObjectProvider<RedisClusterConfiguration> clusterConfigurationProvider)
          {
          this.properties = properties;
          this.sentinelConfiguration = sentinelConfigurationProvider.getIfAvailable();
          this.clusterConfiguration = clusterConfigurationProvider.getIfAvailable();
          }
          復(fù)制代碼

          理解這段代碼的關(guān)鍵是ObjectProvider, 其實(shí)你如果細(xì)心留意,你會(huì)發(fā)現(xiàn),Springboot的代碼,特別是構(gòu)造函數(shù),大量的用到ObjectProvider

          ObjectProvider

          關(guān)于ObjectProvider ,  可以簡(jiǎn)單聊兩句 Spring 4.3的一些改進(jìn)

          當(dāng)構(gòu)造方法的參數(shù)為單個(gè)構(gòu)造參數(shù)時(shí),可以不使用@Autowired進(jìn)行注解

          @Service
          public class FooService {
          private final FooRepository repository;
          public FooService(FooRepository repository) {
          this.repository = repository
          }
          }
          復(fù)制代碼

          比如,上面這段代碼是spring 4.3之后的版本,不需要@Autowired 也可以正常運(yùn)行。

          同樣是在Spring 4.3版本中,不僅隱式的注入了單構(gòu)造參數(shù)的屬性,還引入了ObjectProvider接口。

          //A variant of ObjectFactory designed specifically for injection points, allowing for programmatic optionality and lenient not-unique handling.
          public interface ObjectProvider<T> extends ObjectFactory<T>, Iterable<T> {
          // ...省略了部分代碼
          @Nullable
          T getIfAvailable() throws BeansException;
          }
          復(fù)制代碼

          從源碼注釋中可以得知,ObjectProvider接口是ObjectFactory接口的擴(kuò)展,專門為注入點(diǎn)設(shè)計(jì)的,可以讓注入變得更加寬松和更具有可選項(xiàng)。

          其中,由getIfAvailable()可見,當(dāng)待注入?yún)?shù)的Bean為空或有多個(gè)時(shí),便是ObjectProvider發(fā)揮作用的時(shí)候。

          • 如果注入實(shí)例為空時(shí),使用ObjectProvider則避免了強(qiáng)依賴導(dǎo)致的依賴對(duì)象不存在異常

          • 如果有多個(gè)實(shí)例,ObjectProvider的方法會(huì)根據(jù)Bean實(shí)現(xiàn)的Ordered接口或@Order注解指定的先后順序獲取一個(gè)Bean, 從而了提供了一個(gè)更加寬松的依賴注入方式

          回到,RedisConnectionConfiguration這個(gè)父類構(gòu)造函數(shù)本身,其實(shí)就是實(shí)現(xiàn)這樣的功能:如果用戶提供了RedisSentinelConfigurationRedisSentinelConfiguration , 會(huì)在構(gòu)造函數(shù)中加載進(jìn)來,而RedisProperties則比較簡(jiǎn)單,就是redis的相關(guān)配置。

          RedisProperties

          從配置中讀取redis的相關(guān)配置,最簡(jiǎn)單的單機(jī)redis配置的是簡(jiǎn)單的屬性,sentinel是哨兵相關(guān)配置,cluster是集群相關(guān)配置,Pool是連接池的相關(guān)配置

          @ConfigurationProperties(prefix = "spring.redis")
          public class RedisProperties {
          private int database = 0;
          private String url;
          private String host = "localhost";
          private int port = 6379;

          private String username;
          private String password;

          private Sentinel sentinel;
          private Cluster cluster;
          public static class Pool {}
          public static class Cluster {}
          public static class Sentinel {}
          // ... 省略非必要代碼
          }
          復(fù)制代碼

          小結(jié)一下,目前,我們可以看到RedisAutoConfiguration依賴于配置類LettuceConnectionConfiguration, 其構(gòu)造函數(shù)讀取了用戶定義的redis配置,其中包含 單機(jī)配置+集群配置+哨兵配置+連接池配置,其中集群配置和哨兵配置是兩個(gè)允許用戶自定義的Bean。

          createLettuceConnectionFactory

          LettuceConnectionConfiguration中實(shí)現(xiàn)連接池的方法中調(diào)用了createLettuceConnectionFactory, 其實(shí)現(xiàn)如下

          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);
          }
          復(fù)制代碼

          其實(shí)就是依次讀取哨兵的配置,集群的配置 以及 單機(jī)的配置,如果有就創(chuàng)建連接池返回。

          其中getSentinelConfig()getClusterConfiguration() 是父類的方法,其實(shí)現(xiàn)如下,

          protected final RedisSentinelConfiguration getSentinelConfig() {
          if (this.sentinelConfiguration != null) {
          return this.sentinelConfiguration;
          }
          RedisProperties.Sentinel sentinelProperties = this.properties.getSentinel();
          if (sentinelProperties != null) {
          RedisSentinelConfiguration config = new RedisSentinelConfiguration();
          // 省略裝載代碼
          config.setDatabase(this.properties.getDatabase());
          return config;
          }
          return null;
          }

          protected final RedisClusterConfiguration getClusterConfiguration() {
          if (this.clusterConfiguration != null) {
          return this.clusterConfiguration;
          }
          if (this.properties.getCluster() == null) {
          return null;
          }
          RedisProperties.Cluster clusterProperties = this.properties.getCluster();
          RedisClusterConfiguration config = new RedisClusterConfiguration(clusterProperties.getNodes());
          // 省略裝載代碼
          return config;
          }
          復(fù)制代碼

          從中,我們可以知道,其優(yōu)先讀取在構(gòu)造函數(shù)中由ObjectProvider引入的可能存在的用戶自定義配置Bean,如果沒有,再通過讀取RedisProperties完成裝配。

          但是,細(xì)心的讀者要問了,How about 單機(jī)配置?

          protected final RedisStandaloneConfiguration getStandaloneConfig() {
          RedisStandaloneConfiguration config = new RedisStandaloneConfiguration();
          if (StringUtils.hasText(this.properties.getUrl())) {
          // 省略裝載代碼
          }
          else {
          // 省略裝載代碼
          }
          config.setDatabase(this.properties.getDatabase());
          return config;
          }
          復(fù)制代碼

          是的,你沒有看錯(cuò),單身狗不配……

          總結(jié)起來就是,在構(gòu)造函數(shù)中獲取合適的配置bean,然后在創(chuàng)建連接池的方法里面查找,如果沒有就用配置文件構(gòu)造一個(gè),但是不支持單實(shí)例的redis。

          提一個(gè)issue吧

          保護(hù)單身狗,人人有責(zé),于是,我以“單身狗保護(hù)協(xié)會(huì)”的名義給SpringBoot社區(qū)提了一個(gè)issue

          然后,大佬回復(fù),可以保護(hù)可以支持,很開心。

          其中,有提到使用BeanPostProcessor的方法去改寫RedisProperties的配置,中途我有想到,所以把issue關(guān)了,沉吟一陣,覺得不優(yōu)雅,不開心,又把issue給打開了,很感謝開源團(tuán)隊(duì)的支持和理解,備受鼓舞。


          作者:PeakSong
          鏈接:https://juejin.cn/post/7008568299361402911
          來源:掘金
          著作權(quán)歸作者所有。商業(yè)轉(zhuǎn)載請(qǐng)聯(lián)系作者獲得授權(quán),非商業(yè)轉(zhuǎn)載請(qǐng)注明出處。



          瀏覽 65
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  中文字幕HEYZ0毛片 | 在线观看a网站 | 亚洲无码视频观看 | 国产乱伦肏屄视频 | 在线观看中国精品网站 |