1. 程式人生 > >參照SpringBoot的自動配置

參照SpringBoot的自動配置

“無感”就是最棒的設計

問題

想直接解決問題不願聽故事的,進屋坐坐看第二節。

最近在公司領到一個任務,優化公司的快取框架的使用體驗。公司的快取框架封裝成一個JAR檔案,提供基於註解的對幾種快取的一致性使用。覆蓋的快取方案有ehcache memcached redis,先向公司大牛致敬。
要使用快取框架,需要做這麼幾件事:

  1. 引入快取框架的JAR檔案

  2. 引入想要使用的快取JAR,比如memcached.jar

  3. 做相關的Spring Bean配置,比如要使用Memcached快取就要有如下的配置:

    <!-- memcache配置示例 -->
    <bean name
    ="ljmemcachedClient" class="net.rubyeye.xmemcached.utils.XMemcachedClientFactoryBean" destroy-method="shutdown">
    <!-- 多個地址空格分隔 --> <property name="servers" value="${cache.memcacheAddress}" /> <!-- 連線池大小一般5已經足夠用,根據專案組實際情況調整 --> <property name="connectionPoolSize" value="${cache.memcachePoolSize}"
    />
    <!-- 一致雜湊分佈 --> <property name="sessionLocator"> <bean class="net.rubyeye.xmemcached.impl.KetamaMemcachedSessionLocator"></bean> </property> <!-- 二進位制協議,提高資料傳輸效率,支援touch等 --> <property name="commandFactory"> <bean class="net.rubyeye.xmemcached.command.BinaryCommandFactory"
    >
    </bean> </property> </bean>
  4. 必要的配置檔案:

    cache.memcacheAddress=10.8.1.107:11211
    cache.memcachePoolSize=1

如果要使用redis快取,則要引入另外一套配置。每個新專案都要配置,這顯然是令人沮喪的。能不能簡化這些配置呢?

  • 方案一:在快取框架中引入三種快取的JAR包,並做三種快取的Bean配置,不提供配置檔案,由使用快取框架的專案提供。
  • 方案二:在快取框架中提供三種快取的“自動配置”,由使用快取框架的專案提供配置檔案和相關快取JAR包,根據引入的快取JAR包“自動配置”適合的Bean物件,完成快取的使用。

來看下兩種方案的優缺:

  • 方案一
    • 優點:簡單易實現
    • 缺點:JAR包臃腫,無論實際使用哪種快取,都引入了三種快取JAR,並且需要提供三種的配置檔案。不符合程式設計師美學–簡約
  • 方案二:
    • 優點:“智慧”發現快取,按需引入,語義明確,使用方便
    • 缺點:程式實現複雜

我選擇方案二

然而Spring Boot 已經有成熟的自動配置實現方案,如果直接引入 spring-boot-autoconfigure.jar 會面臨額外問題:

  1. spring-boot-autoconfigure.jar 沒有“直接”提供針對 memcahced 的自動配置;
  2. spring-boot-autoconfigure.jar 提供了太多自動配置支援,而我只想要關於快取的幾個;

那就只好模仿Spring Boot搞一搞了。

終焉

撒花…完結

在開始贅述之前,先給出最終的成品。

如何使用自動配置?

  1. 引入自動配置JAR包

  2. 使用註解 @EnableAutoConfigure 開啟自動配置。建議寫在基礎類上或者專門提供一個類,如:

    @EnableAutoConfigure
    public class AutoConfigure {
    }
  3. 根據需要選擇具體快取,引入JAR並給出配置資訊。比如memcached.jar

    cache.memcached.servers=10.8.1.107:11211
    cache.memcached.connectionPoolSize=2

PS: 這裡有一個糾結的地方,其實可以做到省略第2步,也就是在自動配置JAR包裡把這事做了。但筆者感覺這樣不好,好像是降低了使用的成本,但是損失了使用的自由。所以最終還是沒有進一步封裝。這裡僅代表筆者個人感受,未必是對的。

PS2:然而什麼是對的呢?世界本來就不是0和1.

贅述

接下來詳細說說如何實現的。

有條件的建立

除非滿足條件,不然。。。哼!

最本質的需求是根據條件載入Bean。 Spring4 提供了實現方案 — @Conditional ,可以通過條件判斷建立 Bean

示例:

// java config
@Configuration
public class TestBeanConfig {

    // 根據條件建立, 條件寫在TestConditional類裡
    @Bean
    @Conditional(TestConditional.class)
    public TestBean createTestBean() {
        return new TestBean();
    }
}

// 配套的條件類實現
public class TestConditional implements Condition {

    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        return false;
    }
}

只有當 TestConditional.matches() 結果為 true 時才會建立 TestBean

註解 @Conditional

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface Conditional {

    Class<? extends Condition>[] value();
}
  • 作用範圍: 類,方法
  • 包含引數: value, 介面Condition的實現類陣列。陣列內所有的條件都要滿足喲!

如果value中的條件都滿足,就建立接下來的Bean。沒啥好說的了;

介面 Condition

public interface Condition {

    // 當返回值為true時,建立Bean;否則忽略。
    boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);
}

介面很簡單隻有一個matches方法,可以通過context和metadata獲得相應判斷資訊,輔助判斷;

引數 ConditionContext
public interface ConditionContext {
    // 檢查Bean定義
    BeanDefinitionRegistry getRegistry();
    // 檢查Bean是否存在,甚至探查Bean的屬性
    ConfigurableListableBeanFactory getBeanFactory();
    // 檢查環境變數是否存在以及它的值是什麼
    Environment getEnvironment();
    // 檢查載入的資源
    ResourceLoader getResourceLoader();
    // 載入並檢查類是否存在
    ClassLoader getClassLoader();
}
AnnotatedTypeMetadata
public interface AnnotatedTypeMetadata {

    boolean isAnnotated(String annotationName);
    Map<String, Object> getAnnotationAttributes(String annotationName);
    Map<String, Object> getAnnotationAttributes(String annotationName, boolean classValuesAsString);
    MultiValueMap<String, Object> getAllAnnotationAttributes(String annotationName);
    MultiValueMap<String, Object> getAllAnnotationAttributes(String annotationName, boolean classValuesAsString);
}
  • 藉助 isAnnotated() 能夠判斷帶有 @Bean 註解的方法是不是還有其他特定註解;
  • 藉助其他方法可以檢查 @Bean 註解的方法上其他註解的屬性;

嘗試自動配置

判斷條件

至此,基礎技能已經 GET ,要解決第一節的問題,只要做判斷:當前類路徑下是否存在某類,有就建立無則忽略。
Spring Boot@Conditional 基礎上擴充套件了幾個註解方便做此類判斷:@ConditionalOnClass, @ConditionalOnMissingClass , @ConditionalOnBean , @ConditionalOnMissingBean ;
這裡就不貼原始碼了,想看的朋友去這裡 org.springframework.boot.autoconfigure.condition;
當然可以自行編寫條件類實現判斷,我這裡就懶省事了,直接使用 Spring Boot 的實現。(才沒有這麼簡單)

編寫自動配置類

編寫 memcachedredis 的Java Config類。核心程式碼:

/**
 * memcached 快取自動配置
 * 在引入googlecode-xmemcached.jar時啟用
 * Created by mw4157 on 16/7/6.
 */
@Configuration
@ConditionalOnClass(MemcachedClient.class)
public class MemcachedAutoConfiguration {
    /**
     * 建立一個工廠Bean
     */
    @Bean
    @ConditionalOnMissingBean
    public XMemcachedClientFactoryBean memcachedClientFactory() {
        return ...;
    }
}

/**
 * redis 自動配置
 * 在引入redis-clients.jar和spring-data-redis.jar時啟用
 * Created by mw4157 on 16/7/8.
 */
@Configuration
@ConditionalOnClass({ JedisConnection.class, RedisOperations.class, Jedis.class })
public class RedisAutoConfiguration {

    /**
     * Redis 連線配置
     */
    @Bean
    @ConditionalOnMissingBean(RedisConnectionFactory.class)
    public JedisConnectionFactory redisConnectionFactory() throws UnknownHostException {
        return ...;
    }
}

程式碼的核心功能是創造需要的Bean,同時輔以@Conditional相關的註解,就可以在大意上解決問題。

贅述2.0

之所以說大意上,是因為還有其他內容沒有注意到:
1. 如何將配置檔案與自動配置類結合起來?
2. @Configuration 註解本身繼承於 @Component ,可以被 component-scan 掃描到,如果專案配置的掃描根路徑範圍過大包括了自動配置類,則可能會引發錯誤(這其實是不可避免的“缺陷”,讀者可以嘗試在自己的專案中掃描 org.spring 會發現專案無法啟動)。以 MemcachedAutoConfiguration 為例,如果專案沒有引入 googlecode-xmemcached.jar,這個類上的 MemcachedClient.class 一定是無法識別的,此時被 Springcomponent-scan 直接掃描到,會在載入類(load class)階段報錯NoClassDefFoundError

問題1.配置檔案

Spring 提供了一個註解用於匯入配置檔案中的資料 — @Value
基本用法:

@Value("${name}")
private String address;
@Value("${name:defaultValue}")
private String address2;

name 對應 properties 檔案裡的 key ; defaultValue 是引數的預設值,在 key 不存在時生效。有一點需要注意的是:如何預設null?

@Value("${name:null}")

這是不對的,注入的是字串“null”,正確的做法是:

@Value("${name:#{null}}")

問題2.自動配置的類載入

要解決問題2,首先要避免自動配置類被專案的 component-scan 直接發現,其次我們要在合適的時候讓 Spring 發現自動配置類,並在載入前判斷 @Conditional 是否滿足,不滿足就直接跳過。老實說,我沒有信心說明白這個問題。

下面介紹幾個相關知識點:

@Configuration
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Configuration {
    String value() default "";
}

可以注意到 @Configuration 是被 @Component 修飾的,所以可以被 component-scan 發現。

component-scan
<!-- 自動掃描且不掃描@Controller-->
<context:component-scan base-package="org.msdg">
    <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller" />
    <context:exclude-filter type="annotation" expression="org.springframework.web.bind.annotation.ControllerAdvice" />
    <context:exclude-filter type="regex" expression="org.msdg.core.conf.autoconfigure.*"/>
</context:component-scan>

Spring 會自動掃描 base-package 下的Java類,如果發現帶有 @Component (包括用其修飾的註解 @Repository @Service @Controller @RestController @Configuration 等)的類,將會放入Spring容器中管理,成為眾多Bean裡的一員。
component-scan 還包括兩個子標籤 和 ,注意兩個不是必須的,但如果同時出現,要保證 include-filter 出現在前面,並且如果範圍重合,重合部分被後續的 exclude-filter 排除掉。兩個子標籤都具有 typeexpression 兩個屬性:
- type
- annotation 擁有指定註解的類
- aspectj AspectJ語法
- assignable 指定class或interface的全名
- regex 表示式,可以寫類似org.spring.*,org.spring包下的類都被包括
- custom Spring自定義型別,並沒有研究過
- expression 就是表示式了,可以寫全限定類名、符合規範的正則表達等

在必要的時候可以通過 來避免掃描自動配置類。但真要這麼做的話只能說明 component-scan 的範圍過大了,不夠精細,不優雅。

使用Selector載入Bean

在避免了基礎掃描後,我們要在適當的時候引入自動配置的類。這裡需要用到介面 DeferredImportSelector

public interface DeferredImportSelector extends ImportSelector {
}

這是一個典型的“標識型”介面,沒有任何需要實現的方法。來看下官方註釋:

A variation of ImportSelector that runs after all @Configuration beans have been processed. This type of selector can be particularly useful when the selected imports are @Conditional.
Implementations can also extend the Ordered interface or use the Order annotation to indicate a precedence against other DeferredImportSelectors.

大意是這個介面的實現類將會在 @Configuration 的Bean處理後執行。並且 DeferredImportSelector 是專門用來選擇匯入 @Conditional 修飾的Bean。我們也可以用 @Ordered 決定多個 DeferredImportSelector 的執行順序。
突破口就在這裡, 讓我們更詳盡的瞭解一下介面 ImportSelector 的作用。

public interface ImportSelector {

    /**
     * Select and return the names of which class(es) should be imported based on
     * the {@link AnnotationMetadata} of the importing @{@link Configuration} class.
     */
    String[] selectImports(AnnotationMetadata importingClassMetadata);
}

根據 importingClassMetadata 的值,從帶有註解 @Configuration 的類中選擇並返回合適的類命名陣列,將其匯入 Spring 容器。執行順序是在 @Configuration 引入的Bean處理之後。介面 ImportSelector 的作用類似於註解 @Import

一個 ImportSelector 通常應該實現任意一個或多個 Aware 介面,比如:

  • EnvironmentAware
  • BeanFactoryAware
  • BeanClassLoaderAware
  • ResourceLoaderAware
編寫我們的ImportSelector

參考了Spring Boot的實現,哦哈哈,別告我抄襲
程式碼是部分程式碼,完全體見文章結尾

@Order(Ordered.LOWEST_PRECEDENCE - 1)
@Configuration
public class EnableAutoConfigurationImportSelector implements DeferredImportSelector,
        BeanClassLoaderAware, ResourceLoaderAware, BeanFactoryAware, EnvironmentAware {

    @Override
    public String[] selectImports(AnnotationMetadata metadata) {
        List<String> configurations = getCandidateConfigurations();
        configurations = removeDuplicates(configurations);
        return configurations.toArray(new String[configurations.size()]);
    }

    /**
     * 核心方法,引入需要的類
     */
    protected List<String> getCandidateConfigurations() {
        return SpringFactoriesLoader.loadFactoryNames(this.getClass(), getBeanClassLoader());
    }

    /**
     * 刪除重複引用
     */
    protected final <T> List<T> removeDuplicates(List<T> list) {
        return new ArrayList<T>(new LinkedHashSet<T>(list));
    }

    ...
}

為了方便多方面裝配,四大介面全部實現了,但這不重要。核心方法是 getCandidateConfigurations() ,通過 SpringFactoriesLoader 載入自動配置類。Spring Boot 是這麼用的,不要問我為什麼,我也不知道有沒有別的替代方案。T_T
SpringFactoriesLoader 會載入並例項化寫在檔案 “META-INF/spring.factories” 中的類。key-value 形式,需要寫清全限定類名。根據官方解釋是用來表達工廠類和多種實現類的管理,應該是類似父子關係,但這裡並沒有強制限定。所以,我們的 spring.factories 是這樣的:

# Auto Configure
org.msdg.core.conf.autoconfigure.EnableAutoConfigurationImportSelector=\
  org.msdg.core.conf.autoconfigure.memcached.MemcachedAutoConfiguration,\
  org.msdg.core.conf.autoconfigure.redis.RedisAutoConfiguration

至此突破口已經準備好了。

使用@Import註冊Bean

現在只需要將 ImportSelector 放入Spring容器中就可以了。
因此我們要引入 @Import , 這個註解可以匯入一個配置類到另一個配置類中。在 Spring4.2 中對這個註解進行了加強,可以直接將一個類加入Spring容器。那麼只要寫上 @Import(EnableAutoConfigurationImportSelector.class) 即可。
為了便於使用,這裡自定義一個註解:

@Configuration
@Import(EnableAutoConfigurationImportSelector.class)
public @interface EnableAutoConfigure {
}

結語

先告一段落吧。如有表述不清,歡迎討論。

PS:

Memchached配置輔助類
import org.springframework.beans.factory.annotation.Value;

/**
 * Created by mw4157 on 16/7/9.
 */
public class MemcachedProperties {

    @Value("${cache.memcached.servers:localhost}")
    private String servers;
    @Value("${cache.memcached.connectionPoolSize:1}")
    private int connectionPoolSize;
    @Value("${cache.memcached.sessionLocatorKind:ketama}")
    private String sessionLocatorKind;
    @Value("${cache.memcached.commandFactory:binary}")
    private String commandFactory;

    // getter and setter
}
Memcached自動配置類
import org.msdg.core.cache.impl.MemCacheManager;
import org.msdg.core.conf.autoconfigure.condition.ConditionalOnMissingBean;
import net.rubyeye.xmemcached.MemcachedClient;
import net.rubyeye.xmemcached.command.BinaryCommandFactory;
import net.rubyeye.xmemcached.command.KestrelCommandFactory;
import net.rubyeye.xmemcached.command.TextCommandFactory;
import net.rubyeye.xmemcached.impl.ArrayMemcachedSessionLocator;
import net.rubyeye.xmemcached.impl.ElectionMemcachedSessionLocator;
import net.rubyeye.xmemcached.impl.KetamaMemcachedSessionLocator;
import net.rubyeye.xmemcached.utils.XMemcachedClientFactoryBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import org.msdg.core.conf.autoconfigure.condition.ConditionalOnClass;

/**
 * memcached 快取自動配置
 * 當類路徑中存在MemcachedClient.class時,載入配置
 * Created by mw4157 on 16/7/6.
 */
@Configuration
@ConditionalOnClass(MemcachedClient.class)
public class MemcachedAutoConfiguration {

    /**
     * memcached配置實體
     * 用於引入相關的配置, 具體參見 {@link MemcachedProperties}
     */
    @Bean(name = "org.msdg.core.conf.autoconfigure.cache.MemcachedProperties")
    @ConditionalOnMissingBean
    public MemcachedProperties memcachedProperties() {
        return new MemcachedProperties();
    }

    /**
     * 抽象配置memcahced的邏輯
     * 具體配置可繼承此類
     */
    protected abstract static class AbstractMemcachedConfiguration {
        @Autowired
        private MemcachedProperties properties;

        /**
         * 應用配置資訊, 建立工廠類
         */
        protected final XMemcachedClientFactoryBean applyProperties(XMemcachedClientFactoryBean clientFactory) {
            // 設定伺服器
            clientFactory.setServers(properties.getServers());
            // 連線池大小一般5已經足夠用,根據專案組實際情況調整
            clientFactory.setConnectionPoolSize(properties.getConnectionPoolSize());

            // 分佈策略
            switch (properties.getSessionLocatorKind()) {
                case "ketama":
                    // 一致性雜湊(預設)
                    clientFactory.setSessionLocator(new KetamaMemcachedSessionLocator());
                    break;
                case "array":
                    // 餘數雜湊
                    clientFactory.setSessionLocator(new ArrayMemcachedSessionLocator());
                    break;
                case "election":
                    // 選舉雜湊
                    clientFactory.setSessionLocator(new ElectionMemcachedSessionLocator());
                    break;
            }

            // 誰來補充這裡的註釋, 我不造啊
            switch (properties.getCommandFactory()) {
                case "binary":
                    // 二進位制協議,提高資料傳輸效率,支援touch等(預設)
                    clientFactory.setCommandFactory(new BinaryCommandFactory());
                    break;
                case "kestrel":
                    clientFactory.setCommandFactory(new KestrelCommandFactory());
                    break;
                case "text":
                    clientFactory.setCommandFactory(new TextCommandFactory());
                    break;
            }

            return clientFactory;
        }
    }

    /**
     * 建立一個工廠類
     */
    @Configuration
    protected static class MemcachedConnectionConfiguration extends AbstractMemcachedConfiguration{

        @Bean
        @ConditionalOnMissingBean
        public XMemcachedClientFactoryBean memcachedClientFactory() {
            return applyProperties(new XMemcachedClientFactoryBean());
        }
    }

    /**
     * 建立管理器, 用於配合公司基於註解的那套快取使用方案
     */
    @Configuration
    protected static class MemcahchedConfiguration {

        @Bean
        @ConditionalOnMissingBean
        public MemCacheManager memcachedManager() {
            return new MemCacheManager();
        }
    }

    @Configuration
    protected static class MemcahchedConfiguration1 {

        @Bean
        @ConditionalOnMissingBean
        public MemCacheManager memcachedManager() {
            return new MemCacheManager();
        }
    }
}
Redis配置輔助類
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;

public class RedisProperties {

   /**
    * Database index used by the connection factory.
    */
   @Value("${cache.redis.database:0}")
   private int database;

   /**
    * Redis server host.
    */
   @Value("${cache.redis.host:localhost}")
   private String host;

   /**
    * Login password of the redis server.
    */
   @Value("${cache.redis.password:#{null}}")
   private String password;

   /**
    * Redis server port.
    */
   @Value("${cache.redis.port:6379}")
   private int port;

   /**
    * Connection timeout in milliseconds.
    */
   @Value("${cache.redis.timeout:300000}")
   private int timeout;

   @Autowired
   private Pool pool;

   @Autowired
   private Sentinel sentinel;

   public int getDatabase() {
      return this.database;
   }

   // getter and setter

   /**
    * Pool properties.
    */
   public static class Pool {

      /**
       * Max number of "idle" connections in the pool. Use a negative value to indicate
       * an unlimited number of idle connections.
       */
      @Value("${cache.redis.pool.maxIdle:8}")
      private int maxIdle;

      /**
       * Target for the minimum number of idle connections to maintain in the pool. This
       * setting only has an effect if it is positive.
       */
      @Value("${cache.redis.pool.minIdle:0}")
      private int minIdle;

      /**
       * Max number of connections that can be allocated by the pool at a given time.
       * Use a negative value for no limit.
       */
      @Value("${cache.redis.pool.maxActive:8}")
      private int maxActive;

      /**
       * Maximum amount of time (in milliseconds) a connection allocation should block
       * before throwing an exception when the pool is exhausted. Use a negative value
       * to block indefinitely.
       */
      @Value("${cache.redis.pool.maxWait:-1}")
      private int maxWait;

      // getter and setter
   }

   /**
    * Redis sentinel properties.
    */
   public static class Sentinel {

      /**
       * Name of Redis server.
       */
      @Value("${cache.redis.sentinel.master:#{null}}")
      private String master;

      /**
       * Comma-separated list of host:port pairs.
       */
      @Value("${cache.redis.sentinel.nodes:#{null}}")
      private String nodes;

      // getter and setter
   }
}
Redis自動配置類
import org.msdg.core.cache.impl.RedisCacheManager;
import org.msdg.core.conf.autoconfigure.condition.ConditionalOnClass;
import org.msdg.core.conf.autoconfigure.condition.ConditionalOnMissingBean;
import org.msdg.core.conf.autoconfigure.condition.ConditionalOnMissingClass;
import org.apache.commons.pool2.impl.GenericObjectPool;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.RedisNode;
import org.springframework.data.redis.connection.RedisSentinelConfiguration;
import org.springframework.data.redis.connection.jedis.JedisConnection;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.data.redis.core.RedisOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPoolConfig;

import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.List;

/**
 * Created by mw4157 on 16/7/8.
 */
@Configuration
@ConditionalOnClass({ JedisConnection.class, RedisOperations.class, Jedis.class })
public class RedisAutoConfiguration {

    @Bean(name = "org.springframework.autoconfigure.redis.RedisProperties")
    @ConditionalOnMissingBean
    public RedisProperties redisProperties() {
        return new RedisProperties();
    }

    @Bean(name = "org.springframework.autoconfigure.redis.RedisProperties.Pool")
    @ConditionalOnMissingBean
    public RedisProperties.Pool redisPoolProperties() {
        return new RedisProperties.Pool();
    }

    @Bean(name = "org.springframework.autoconfigure.redis.RedisProperties.Sentinel")
    @ConditionalOnMissingBean
    public RedisProperties.Sentinel redisSentinelProperties() {
        return new RedisProperties.Sentinel();
    }

    /**
     * Base class for Redis configurations.
     */
    protected abstract class AbstractRedisConfiguration {

        @Autowired
        protected RedisProperties properties;

        @Autowired(required = false)
        private RedisSentinelConfiguration sentinelConfiguration;

        protected final JedisConnectionFactory applyProperties(JedisConnectionFactory factory) {
            factory.setHostName(this.properties.getHost());
            factory.setPort(this.properties.getPort());
            if (this.properties.getPassword() != null) {
                factory.setPassword(this.properties.getPassword());
            }
            factory.setDatabase(this.properties.getDatabase());
            if (this.properties.getTimeout() > 0) {
                factory.setTimeout(this.properties.getTimeout());
            }
            return factory;
        }

        protected final RedisSentinelConfiguration getSentinelConfig() {
            if (this.sentinelConfiguration != null) {
                return this.sentinelConfiguration;
            }
            RedisProperties.Sentinel sentinelProperties = this.properties.getSentinel();
            if (sentinelProperties.getMaster() != null) {
                RedisSentinelConfiguration config = new RedisSentinelConfiguration();
                config.master(sentinelProperties.getMaster());
                config.setSentinels(createSentinels(sentinelProperties));
                return config;
            }
            return null;
        }

        private List<RedisNode> createSentinels(RedisProperties.Sentinel sentinel) {
            List<RedisNode> sentinels = new ArrayList<RedisNode>();
            String nodes = sentinel.getNodes();
            for (String node : StringUtils.commaDelimitedListToStringArray(nodes)) {
                try {
                    String[] parts = StringUtils.split(node, ":");
                    Assert.state(parts.length == 2, "Must be defined as 'host:port'");
                    sentinels.add(new RedisNode(parts[0], Integer.valueOf(parts[1])));
                }
                catch (RuntimeException ex) {
                    throw new IllegalStateException(
                            "Invalid redis sentinel " + "property '" + node + "'", ex);
                }
            }
            return sentinels;
        }

    }

    /**
     * Redis connection configuration.
     */
    @Configuration
    @ConditionalOnMissingClass("org.apache.commons.pool2.impl.GenericObjectPool")
    protected class RedisConnectionConfiguration extends AbstractRedisConfiguration {

        @Bean
        @ConditionalOnMissingBean(RedisConnectionFactory.class)
        public JedisConnectionFactory redisConnectionFactory() throws UnknownHostException {
            return applyProperties(new JedisConnectionFactory(getSentinelConfig()));
        }

    }

    /**
     * Redis pooled connection configuration.
     */
    @Configuration
    @ConditionalOnClass(GenericObjectPool.class)
    protected class RedisPooledConnectionConfiguration extends AbstractRedisConfiguration {

        @Bean
        @ConditionalOnMissingBean(RedisConnectionFactory.class)
        public JedisConnectionFactory redisConnectionFactory() throws UnknownHostException {
            return applyProperties(createJedisConnectionFactory());
        }

        private JedisConnectionFactory createJedisConnectionFactory() {
            if (this.properties.getPool() != null) {
                return new JedisConnectionFactory(getSentinelConfig(), jedisPoolConfig());
            }
            return new JedisConnectionFactory(getSentinelConfig());
        }

        private JedisPoolConfig jedisPoolConfig() {
            JedisPoolConfig config = new JedisPoolConfig();
            RedisProperties.Pool props = this.properties.getPool();
            config.setMaxTotal(props.getMaxActive());
            config.setMaxIdle(props.getMaxIdle());
            config.setMinIdle(props.getMinIdle());
            config.setMaxWaitMillis(props.getMaxWait());
            return config;
        }
    }

    /**
     * Standard Redis configuration.
     */
    @Configuration
    protected class RedisConfiguration {

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

        @Bean
        @ConditionalOnMissingBean(RedisCacheManager.class)
        public RedisCacheManager redisCacheManager() {
            return new RedisCacheManager();
        }
    }
}
ImportSelector
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanClassLoaderAware;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.context.EnvironmentAware;
import org.springframework.context.ResourceLoaderAware;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DeferredImportSelector;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.core.env.Environment;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.io.support.SpringFactoriesLoader;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.util.Assert;

@Order(Ordered.LOWEST_PRECEDENCE - 1)
@Configuration
public class EnableAutoConfigurationImportSelector implements DeferredImportSelector,
      BeanClassLoaderAware, ResourceLoaderAware, BeanFactoryAware, EnvironmentAware {

   private ConfigurableListableBeanFactory beanFactory;

   private Environment environment;

   private ClassLoader beanClassLoader;

   private ResourceLoader resourceLoader;

   @Override
   public String[] selectImports(AnnotationMetadata metadata) {
      List<String> configurations = getCandidateConfigurations();
      configurations = removeDuplicates(configurations);
      return configurations.toArray(new String[configurations.size()]);
   }

   protected List<String> getCandidateConfigurations() {
      return SpringFactoriesLoader.loadFactoryNames(this.getClass(), getBeanClassLoader());
   }

   protected final <T> List<T> removeDuplicates(List<T> list) {
      return new ArrayList<T>(new LinkedHashSet<T>(list));
   }

   @Override
   public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
      Assert.isInstanceOf(ConfigurableListableBeanFactory.class, beanFactory);
      this.beanFactory = (ConfigurableListableBeanFactory) beanFactory;
   }

   @Override
   public void setBeanClassLoader(ClassLoader classLoader) {
      this.beanClassLoader = classLoader;
   }

   protected ClassLoader getBeanClassLoader() {
      return this.beanClassLoader;
   }

   @Override
   public void setEnvironment(Environment environment) {
      this.environment = environment;
   }

   @Override
   public void setResourceLoader(ResourceLoader resourceLoader) {
      this.resourceLoader = resourceLoader;
   }
}
ConditionalOnClass
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import org.springframework.context.annotation.Conditional;

/**
 * {@link Conditional} that only matches when the specified classes are on the classpath.
 *
 * @author Phillip Webb
 */
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnClassCondition.class)
public @interface ConditionalOnClass {

   /**
    * The classes that must be present. Since this annotation parsed by loading class
    * bytecode it is safe to specify classes here that may ultimately not be on the
    * classpath.
    * @return the classes that must be present
    */
   Class<?>[] value() default { };

   /**
    * The classes names that must be present.
    * @return the class names that must be present.
    */
   String[] name() default { };

}
OnClassCondition
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;

import org.slf4j.Logger;

            
           

相關推薦

SpringBoot自動配置xxxAutoConfiguration 的使用

調用棧 是否 PE provider onf pub div 存在 ring 以MybatisAutoConfiguration為例1. 在jar包中/META-INF/spring.factories中配置org.springframework.boot.autoconf

SpringBoot自動配置原理

override activity eve rop 哪些 isf 代碼 文件 multi SpringBoot自動配置原理 備註:該SpringBoot自動配置原理不適合java剛入門學者以及不熟悉Spring4+Springmvc+maven的同學 1、當Sprin

SpringBoot-自動配置原理

factory ESS bean att cond active tor 條件判斷 common 可配置項參考: https://docs.spring.io/spring-boot/docs/2.0.1.BUILD-SNAPSHOT/reference/htmlsingl

(轉)入門SpringBoot-自動配置原理(三)

leg ebs jdbc 路徑 ket dao posit 把他 exc 1、自動配置原理: 1)、SpringBoot啟動的時候加載主配置類,開啟了自動配置功能 ==@EnableAutoConfiguration== 2)、@EnableAutoConfigurat

SpringBoot自動配置

介紹下開發環境 JDK版本1.8 springboot版本是1.5.2 開發工具為 intellij idea(2018.2) 開發環境為 15款MacBook Pro 結束語 增加自動配置 使用自動配置,我們需要配置的東西很少,也就新增下檔案

SpringBoot自動配置註解原理解析

1. SpringBoot啟動主程式類: 1 @SpringBootApplication 2 public class DemoApplication { 3 public static void main(String[] args) { 4 5 SpringApp

springboot原始碼解析:自己實現一個springboot自動配置

上兩篇將到了springboot自動配置和條件註解是如何實現,這篇在前兩篇的基礎上自己實現一個springboot的自動配置,用到條件註解。 需求:加入自己手寫的jar。直接就可以使用StringRedisTemplate。 1.新建一個maven專案,pom.xml如下:

springboot 自動配置的原理

在spring程式main方法中 新增@SpringBootApplication或者@EnableAutoConfiguration         會自動去maven中讀取每個starter中的spring.factories檔案  該檔案裡配置了所有需要被建立sprin

SpringBoot自動配置的原理及實現

SpringBoot自動配置的實現原理 SpringBoot的核心就是自動配置,自動配置又是基於條件判斷來配置Bean。關於自動配置的原始碼在spring-boot-autoconfigure-2.0.3.RELEASE.jar 回顧配置屬性 在通常需要我們在

SpringBoot專欄:SpringBoot自動配置原理以及細節(第三講)

  自動配置原理 1)、SpringBoot啟動的時候載入主配置類,開啟了自動配置功能 @EnableAutoConfiguration 2)、@EnableAutoConfiguration 作用:        利用EnableA

SpringBoot-自動配置原始碼解析

接著上一篇部落格《 SpringBoot-快速搭建WEB工程》提出的需要分析的三個方面:我們來深入的探究SpringBoot是如何在沒有一個配置檔案的情況下為我們啟動好一個完整的WEB工程的,首先我們從@SpringBootApplication 開始這裡的分析

搭建springboot自動配置,基於自己的RedisUtil

1.建立專案如下 2.加入一下pom依賴 //這個是之前的部落格例子,對jedis連線操作的簡單分裝 <dependency> <groupId>com.cdy</groupId> <artifactId&

springboot——自動配置

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM

SpringBoot 為什麼能夠自動的注入一些常用的Bean ?詳細分析SpringBoot 自動配置的實現

有一個問題一直讓我好奇,為什麼在SpringBoot中有的bean 我們都沒有配置卻能夠自動注入,這個比如說我們使用JdbcTemplate 的時候那個物件明明我們都沒有配置但是卻能獲取到。再比如SpringBoot在結合Redis的時候,我們也沒有注入redisTempla

SpringBoot之十六:SpringBoot自動配置的原理

Spring Boot在進行SpringApplication物件例項化時會載入META-INF/spring.factories檔案,將該配置檔案中的配置載入到Spring容器。 一、初始化

SpringBoot學習筆記(三) SpringBoot 自動配置原理

SpringBoot自動配置 SpringBoot自動配置的註解是@EnableAutoConfiguration 所以來看@EnableAutoConfiguration註解的原始碼: 1、首先@EnableAutoConfiguration是包含在@S

springboot 自動配置原理

8、自動配置原理 配置檔案到底能寫什麼?怎麼寫?自動配置原理; 配置檔案能配置的屬性參照   1、自動配置原理: 1)、SpringBoot啟動的時候載入主配置類,開啟了自動配置功能 @EnableAutoConfiguration 2)、@EnableAutoConfigu

Springboot第二章 springboot 自動配置原理

springboot 官方文件記錄了properties/yml   能寫的所有配置 1、自動配置原理: 1)Sprintboot 啟動的時候,載入住配置類,開啟自動配置功能, @EnableAutoConfiguration 利用 @Import(Auto

SpringBoot--自動配置Demo實現

1 概述 上一篇文章(SpringBoot--自動配置原理解析)我們學習了SpringBoot自動配置的原理,對自動配置到底是怎麼回事進行了詳細地學習,那麼現在我們就需要自己來實現一個自動配置的功能,以加深自己對自動配置的瞭解與認識。 2 工程結構 首先直接上工程結構的

案例解析:springboot自動配置未生效問題定位(條件斷點)

  Spring Boot在為開發人員提供更高層次的封裝,進而提高開發效率的同時,也為出現問題時如何進行定位帶來了一定複雜性與難度。但Spring Boot同時又提供了一些診斷工具來輔助開發與分析,如spring-boot-starter-actuator。本文分享一個基於actuator與IDE