Spring boot 之 Auto-configuration 工做原理

spring boot 的 auto-configuration 功能會根據你的應用程序所依賴的 pom 來進行自動配置。 例如,咱們在 pom 中添加 spring-boot-starter-web的依賴,spring 就會幫咱們自動完成 spring mvc 相關的配置而不須要咱們手動來進行。咱們只須要將 @EnableAutoConfiguration 或者 @SpringBootApplication註解標註在 @Configuration 配置類上面便可啓用自動裝配。web

那麼 auto-configuration 是如何工做的呢?帶着問題,咱們經過閱讀相關的源代碼來一探究竟。spring

既然開啓 auto-configuration 須要經過 @EnableAutoConfiguration 或 @SpringBootApplication來驅動,那麼咱們就從這兩個註解着手。api

注:本文基於 spring boot 版本 1.5.12.RELEASE。數組

源碼解析

  • @SpringBootApplicationwebsocket

源碼以下:mvc

  1. @Target(ElementType.TYPE)app

  2. @Retention(RetentionPolicy.RUNTIME)socket

  3. @Documentedide

  4. @Inheritedspring-boot

  5. @SpringBootConfiguration

  6. @EnableAutoConfiguration

  7. @ComponentScan(excludeFilters = {

  8.        @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),

  9.        @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })

  10. public @interface SpringBootApplication {

  11.    // 省略

  12. }

  1.  

    @SpringBootApplication註解自己也標註了 @EnableAutoConfiguration 註解,因此自動裝配最終仍是經過 @EnableAutoConfiguration來啓用。

     

  2.  

    @SpringBootApplication還標註了 @ComponentScan和 @SpringBootConfiguration, @SpringBootConfiguration上又標註 @Configuration註解。

     

由於 @SpringBootApplication將上述多個註解集成於一身,因此咱們只要在類上標註 @SpringBootApplication就等價於同時添加了 @EnableAutoConfiguration、 @ComponentScan和 @Configuration


注:類上標註的註解(direct annotation)和註解上的註解(meta-annotation)之因此都能生效這和 spring 自己對註解的處理有關。

@SpringBootApplication 比較簡單,看來再來看看 @EnableAutoConfiguration

  • @EnableAutoConfiguration

源碼以下:

  1. @SuppressWarnings("deprecation")

  2. @Target(ElementType.TYPE)

  3. @Retention(RetentionPolicy.RUNTIME)

  4. @Documented

  5. @Inherited

  6. @AutoConfigurationPackage

  7. @Import(EnableAutoConfigurationImportSelector.class)

  8. public @interface EnableAutoConfiguration {

  9.  

  10.    String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";

  11.  

  12.    /**

  13.     * Exclude specific auto-configuration classes such that they will never be applied.

  14.     * @return the classes to exclude

  15.     */

  16.    Class<?>[] exclude() default {};

  17.  

  18.    /**

  19.     * Exclude specific auto-configuration class names such that they will never be

  20.     * applied.

  21.     * @return the class names to exclude

  22.     * @since 1.3.0

  23.     */

  24.    String[] excludeName() default {};

  25.  

  26. }

@EnableAutoConfiguration 上標註了 @Import,引入了 EnableAutoConfigurationImportSelector

EnableAutoConfigurationImportSelector 源碼以下:

  1. public class EnableAutoConfigurationImportSelector

  2.        extends AutoConfigurationImportSelector {

  3.  

  4.    @Override

  5.    protected boolean isEnabled(AnnotationMetadata metadata) {

  6.        if (getClass().equals(EnableAutoConfigurationImportSelector.class)) {

  7.            return getEnvironment().getProperty(

  8.                    EnableAutoConfiguration.ENABLED_OVERRIDE_PROPERTY, Boolean.class,

  9.                    true);

  10.        }

  11.        return true;

  12.    }

  13.  

  14. }

EnableAutoConfigurationImportSelector 源碼並沒能給咱們提供太多參考信息,只重寫了 isEnabled方法,咱們來看看他的基類 AutoConfigurationImportSelector,源碼以下:

  1. public class AutoConfigurationImportSelector

  2.        implements DeferredImportSelector, BeanClassLoaderAware, ResourceLoaderAware,

  3. BeanFactoryAware, EnvironmentAware, Ordered {

  4.    // 省略

  5. }

咱們看到 AutoConfigurationImportSelector實現了 DeferredImportSelector接口(從 ImportSelector派生)。 ImportSelector源碼以下:

  1. public interface ImportSelector {

  2.  

  3.    /**

  4.     * Select and return the names of which class(es) should be imported based on

  5.     * the {@link AnnotationMetadata} of the importing @{@link Configuration} class.

  6.     */

  7.    String[] selectImports(AnnotationMetadata importingClassMetadata);

  8.  

  9. }

selectImports方法中定義了參數 importingClassMetadata ,類型是 AnnotationMetadata ,這是啥?

咱們把 importingClassMetadata這個詞組分解一下,importing + class metadata:

importing:動名詞,強調的是幹了 import 這件事的發起者(這裏指配置類)

class metadata:類的元信息,什麼元信息,註解元信息,即 AnnotationMetadata

那麼 selectImports方法的邏輯,咱們能夠這麼描述:

基於發起 import 操做的配置類(@Configuration class)的元信息進行運算並返回計算結果(class 名稱數組)。

那麼 spring 對返回結果中的 class 有沒有什麼特別要求呢?

實際上你能夠返回任意的 class 名稱,而不至於使程序出錯,固然,你確定不會返回沒有任何意義的 class 名稱。筆者總結的返回結果分爲下面兩類:

  • @Configuration 配置類

  • ImportSelector(或 DeferredImportSelector)實現類

ImportSelector與 DeferredImportSelector的區別?

DeferredImportSelector是 ImportSelector的變體,兩者的觸發前後順序不一樣, DeferredImportSelector在全部的 @Configurationbean 都被處理了以後再進行處理,這與它的名稱 deferred (推遲)很是貼合。

題外話:經過以上內容,咱們能夠看出 spring 在命名上很是講究,代碼閱讀起來比較符合人的邏輯思惟。

selectImports 方法的實現由 ConfigurationClassParser 類的 parse 方法觸發, ConfigurationClassParser的 parse 方法會被 ConfigurationClassPostProcessor類的 postProcessBeanDefinitionRegistry 方法調用,而觸發這一切的最上層是 spring application context 的 refresh()方法。

信息量有點大,咱們經過下圖來作簡單說明:

  1. Application 啓動

  2.     // refresh spring application context

  3. AbstractApplicationContext.refresh

  4.     // 執行 BeanFactoryPostProcessor 鉤子回調

  5. AbstractApplicationContext.invokeBeanFactoryPostProcessors

  6.     // 委託給 PostProcessorRegistrationDelegate 來執行鉤子回調

  7. PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors

  8.     // ConfigurationClassPostProcessor 是 BeanFactoryPostProcessor 的實現類,被觸發

  9. ConfigurationClassPostProcessor.postProcessBeanDefinitionRegistry

  10.     // ConfigurationClassPostProcessor 委託 ConfigurationClassParser 來對配置類進行解析

  11. ConfigurationClassParser.parse

  12.     // ConfigurationClassParser 處理 DeferredImportSelector

  13. ConfigurationClassParser.processDeferredImportSelectors

  14.     // 執行 selectImports

  15. DeferredImportSelector.selectImports

 

關於 ConfigurationClassParser類對 @Configuration配置類的處理,本文並不打算做過多講解,筆者會放在後續文章中來和你們進行探討。

好了,有了對 ImportSelector的相關了解後,咱們來看看 AutoConfigurationImportSelector的 selectImports方法的實現,源碼以下:

  1. @Override

  2.    public String[] selectImports(AnnotationMetadata annotationMetadata) {

  3.        // 檢查是否啓用 auto-configuration,默認爲啓用

  4.        // 由子類 EnableAutoConfigurationImportSelector 重寫

  5.        if (!isEnabled(annotationMetadata)) {

  6.            // 若是不開啓 auto-configuration,返回 emtpy array

  7.            return NO_IMPORTS;

  8.        }

  9.        try {

  10.            AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader

  11.                    .loadMetadata(this.beanClassLoader);

  12.            AnnotationAttributes attributes = getAttributes(annotationMetadata);

  13.            // 經過 SpringFactoriesLoader 找到全部的侯選配置類(auto-configuration 類)

  14.            List<String> configurations = getCandidateConfigurations(annotationMetadata,

  15.                    attributes);

  16.            // 去除重複的配置類

  17.            configurations = removeDuplicates(configurations);

  18.            // 根據 auto-configuration 的前後配置順序的要求進行排序

  19.            // 可經過 @AutoConfigureBefore & @AutoConfigureAfter 來指定

  20.            configurations = sort(configurations, autoConfigurationMetadata);

  21.            // 須要被剔除的 auto-configuration 類

  22.            // 可在 properties 配置文件中經過 spring.autoconfigure.exclude 來指定

  23.            // 也可經過 @EnableAutoConfiguration 註解的 exclude() & excludeName() 屬性來指定

  24.            Set<String> exclusions = getExclusions(annotationMetadata, attributes);

  25.            checkExcludedClasses(configurations, exclusions);

  26.            // 從侯選配置類中剔除須要被排除在外的(auto-configuration 類)

  27.            configurations.removeAll(exclusions);

  28.            // 經過 AutoConfigurationImportFilter 來過濾侯選配置類,再次進行剔除

  29.            // 經過 SpringFactoriesLoader 獲取所的 AutoConfigurationImportFilter 實現

  30.            configurations = filter(configurations, autoConfigurationMetadata);

  31.            // 發佈 AutoConfigurationImportEvent 事件,通知 AutoConfigurationImportListener,

  32.            // 觸發 onAutoConfigurationImportEvent(AutoConfigurationImportEvent event) 方法

  33.            // 經過 SpringFactoriesLoader 獲取所的 AutoConfigurationImportListener 實現

  34.            fireAutoConfigurationImportEvents(configurations, exclusions);

  35.            // 返回須要處理的 auto-configuration 類(名稱)

  36.            return configurations.toArray(new String[configurations.size()]);

  37.        }

  38.        catch (IOException ex) {

  39.            throw new IllegalStateException(ex);

  40.        }

  41.    }

  42.  

  43.    protected boolean isEnabled(AnnotationMetadata metadata) {

  44.        return true;

  45.    }


相信你們看了筆者所添加的相關注釋後對 AutoConfigurationImportSelector的邏輯已經有了一個大體的瞭解。

AutoConfigurationImportSelector的 selectImports方法的主要邏輯就是經過 SpringFactoriesLoader找到全部的 auto-configuration 侯選類,而後在此基礎上進行去重、排序和剔除操做,最終獲得須要進行 auto-configuration 的全部類的名稱。

拿到了所的 auto-configuration 類,spring boot 就能夠加載這些 class,因爲這些類自己標註了 @Configuration,而後就能夠被 ConfigurationClassParser類來進行解析了,最終 @Bean工廠方法就會被調用,完成 bean 的加載。

在筆者的註釋中,不止一次提到了 SpringFactoriesLoader,這個類究竟有何神奇之處?

一樣的,爲了搞清楚這件事,咱們還得看 spring 源碼,部分源碼以下:

  1. public abstract class SpringFactoriesLoader {

  2.  

  3.    /**

  4.     * The location to look for factories.

  5.     * <p>Can be present in multiple JAR files.

  6.     */

  7.    public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";

  8.  

  9.    // 經過 ClassLoader 加載全部的全部 META-INF/spring.factories 文件,解析成 Properties 對象,

  10.    // 根據 key (指定的 class 名稱) 來獲取全部的配置項 (類名稱)

  11.    public static List<String> loadFactoryNames(Class<?> factoryClass, ClassLoader classLoader) {

  12.        String factoryClassName = factoryClass.getName();

  13.        try {

  14.            Enumeration<URL> urls = (classLoader != null ? classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :

  15.                    ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));

  16.            List<String> result = new ArrayList<String>();

  17.            while (urls.hasMoreElements()) {

  18.                URL url = urls.nextElement();

  19.                Properties properties = PropertiesLoaderUtils.loadProperties(new UrlResource(url));

  20.                String factoryClassNames = properties.getProperty(factoryClassName);

  21.                result.addAll(Arrays.asList(StringUtils.commaDelimitedListToStringArray(factoryClassNames)));

  22.            }

  23.            return result;

  24.        }

  25.        catch (IOException ex) {

  26.            // 省略

  27.        }

  28.    }

  29. }

原來 spring 使用了一種相似 ServiceLoader (jdk 1.6 中增長)的處理方式。經過這種約定,咱們只須要將要進行處理的目標類名稱配置在相對固定的配置文件中,spring 按照統一的方式來讀取便可。對咱們的應用程序而言並不須要知道具體的實現類是哪些,也不須要知道這些類存在於哪些 jar 中,均可以被輕鬆獲取。

相對固定指的是:

  •  

    相對於 classpath, META-INF/spring.factories 文件的位置固定和名稱固定。

     

  •  

    同一類別的配置,不一樣配置文件中 key 名稱固定不變。

    注:若是你對 ServiceLoader感到陌生,請查看 jdk api 瞭解相關內容。

     

既然瞭解了 spring 的 spring.factories 的套路,那麼咱們就來找找看都有哪些 auto-configuration 類,

咱們的 key 是 @EnableAutoConfiguration註解的 class 名稱,即 org.springframework.boot.autoconfigure.EnableAutoConfiguration,展開 spring-boot-autoconfigure-1.5.12.RELEASE.jar 文件,找到 spring.factories 文件,部分配置以下(「……」 表示筆者省略 ):

  1. # Auto Configure

  2. org.springframework.boot.autoconfigure.EnableAutoConfiguration=\

  3. org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\

  4. org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\

  5. org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\

  6. org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,\

  7. org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration,\

  8. # ……

  9. org.springframework.boot.autoconfigure.context.ConfigurationPropertiesAutoConfiguration,\

  10. org.springframework.boot.autoconfigure.context.MessageSourceAutoConfiguration,\

  11. org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration,\

  12. # ……

  13. org.springframework.boot.autoconfigure.data.jpa.JpaRepositoriesAutoConfiguration,\

  14. # ……

  15. org.springframework.boot.autoconfigure.data.mongo.MongoDataAutoConfiguration,\

  16. org.springframework.boot.autoconfigure.data.mongo.MongoRepositoriesAutoConfiguration,\

  17. # ……

  18. org.springframework.boot.autoconfigure.web.MultipartAutoConfiguration,\

  19. org.springframework.boot.autoconfigure.web.ServerPropertiesAutoConfiguration,\

  20. org.springframework.boot.autoconfigure.web.WebClientAutoConfiguration,\

  21. org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration,\

  22. org.springframework.boot.autoconfigure.websocket.WebSocketAutoConfiguration,\

  23. org.springframework.boot.autoconfigure.websocket.WebSocketMessagingAutoConfiguration,\

  24. org.springframework.boot.autoconfigure.webservices.WebServicesAutoConfiguration

上述這些 auto-configuration 侯選類名稱都會被 AutoConfigurationImportSelector經過 SpringFactoriesLoader所獲取,而後 AutoConfigurationImportSelector就能夠進行後續的去重、排序和剔除過濾操做了。

好了,本文至此,你是否已經對 spring boot 的 auto-configuration 工做機制有所瞭解了呢?

小結

本文,咱們主要介紹了 spring boot auto-configuration 的工做機制,提到了幾個重要的概念:

  • @EnableAutoConfiguration

  • ImportSelector

  • SpringFactoriesLoader

經過 @EnableAutoConfiguration註解驅動引入 AutoConfigurationImportSelector,該類經過 SpringFactoriesLoader加載全部的 META-INF/spring.factories文件,獲取到全部的 auto-configuration 候選類,而後進行去重、排序和剔除過濾等操做獲得待處理的 auto-configuration 類。

相關文章
相關標籤/搜索