讓SpringBoot自動化配置不再神祕

早知今日發表於2020-05-22

本文若有任何紕漏、錯誤,還請不吝指出!

注:本文提到的Spring容器或者Bean容器,或者Spring Bean容器,都是指同一個事情,那就是代指BeanFactory。關於BeanFactory,後面有機會會再說下。

花絮

幾年前接觸過SpringBoot,跑過Demo,當時剛入行,連Spring都沒搞明白,更別說SpringBoot了,就是覺得,哇塞,好厲害,然後一臉懵逼。

工作中沒有用到,又沒有去主動學習它。覺得很恐懼,這麼厲害的東西,肯定是很深奧,很複雜吧!。

這種心理也造成了一定程度上,對某些事物的望而卻步,其實只要向前邁出了步子,一步步慢慢來,才發現,以前的那種恐懼心理是多麼的幼稚、膽怯、可笑!

序言

SpringBoot本身並沒有多大的花樣,所有的知識點其實還都是Spring Framework的。

SpringBoot之前,使用Spring可以說,並不是那麼的方便,其實也主要是在搭建一個基於Spring Framework的專案時這個困擾。Spring本身的配置,整合SpringMVC,整合Struts2,整合mybatis,整合Hibernate,整合SpringSecurity等等,如果是Web應用還有個web.xml需要配置。什麼都要你去配置一下,第一步就是去找怎麼配置,記住這麼配置是如何配的,其實並沒有切實的意義,畢竟又不是經常需要去搭建一個專案。正因為不常這麼配置,不值得記住如何配置,導致每次實際用到時,很麻煩,到處去找如何配置的XML配置檔案。

SpringBoot的出現,正是為了解決這個問題,讓你可以不去做任何配置的情況下,執行一個Spring應用,或者Web應用。需要做的僅僅是引入SpringBootmaven或者gradle依賴即可。

SpringBoot要做的就是,讓你開箱即用!

將使用Spring的成本降到儘可能低,為使用者帶來了極大的便利。

當然SpringBoot做的也不僅僅只有這些,不過這裡僅討論下它的自動化配置,不討論其他的。

如果瞭解Spring@Configuration這個註解的處理過程,會更加容易理解SpringBoot的自動化配置。

如果沒有,可以參考這篇解釋

窮其林

這第一件事,就是找門,門都找不到,那不是沒門嗎!

既然想找門,就得從程式的啟動入口去找,任何SpringBoot程式都會用到這麼兩個

@SpringBootApplication
public class Application{
  public static void main(String[] args){
    SpringApplication.run(Application.class, args);
  }
}

看到這個後,如果好奇其實現,應該會首先檢視SpringApplication#run方法,實際呼叫的是這個過載的靜態方法。

// org.springframework.boot.SpringApplication
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
   return new SpringApplication(primarySources).run(args);
}

public ConfigurableApplicationContext run(String... args) {
    ···省略···
    try {
        ···省略···
        context = createApplicationContext();
        exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,new Class[] { ConfigurableApplicationContext.class }, context);
        // 真正啟動應用程式上下文前的一些準備動作
        // 這裡會去將Application.class,註冊到org.springframework.context.annotation.AnnotatedBeanDefinitionReader
        // 也就是去把Application.class註冊成一個BeanDefinition例項
        // 不過Application必須要是一個@Component
        prepareContext(context, environment, listeners, applicationArguments, printedBanner);
        // 重新整理上下文,這個過程中主要就是Bean的例項化和屬性賦值繫結
        // 如果是Web環境,涉及到Web相關的一些東西,但是本質上還是各種Bean的例項化
        // 和Bean之間依賴關係的處理,代理Bean的生成(涉及到AspectJ的Advice處理)等等
        refreshContext(context);

    }
    return context;
}

BeanDefinition例項有了,就能去啟動上下文,處理Bean容器了,容器啟動完成後,整個SpringBoot程式基本啟動完成!

等等! 是不是少了什麼?

這裡就註冊了一個BeanDefinition,那麼多@Component@Configuration@Service@Controller怎麼辦?

先留著疑問,且待後面解答!

遇山口

林盡水源,便得一山,山有小口,彷彿若有光。

注意到上面的準備階段,被註冊的Bean必須要被@Component註解,現在Application.class僅有一個註解@SpringBootApplication

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
      @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
    ···省略···
}

挨個檢視幾個註解的定義後,會發現@SpringBootConfiguration@Component所註解,這就解釋了為什麼被@SpringBootApplication所註解的Application.class類可以被作為一個Bean註冊到BeanDefinitionRegistry

除此之外,還有個令人驚喜的名稱:@EnableAutoConfiguration,看名字就看出來它是做啥的了。

沒錯,SpringBoot的所謂自動配置,就是它在起作用。

這裡暫時不討論@ComponentScan

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
    ···省略···
}

這個註解又使用了兩個註解,分別是@AutoConfigurationPackage@Import

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {

}

可以發現,這兩個註解最終都指向了同一個註解@Import

@ImportAnnotation時代的<import/>,作用是向BeanDefinitionRegistry註冊Bean的。

所以@EnableAutoConfiguration這個註解一共註冊了兩個Bean,分別是:AutoConfigurationPackages.Registrar.classAutoConfigurationImportSelector.class

先說說AutoConfigurationPackages.Registrar的用處

這個類就幹一個事,註冊一個Bean,這個Bean就是org.springframework.boot.autoconfigure.AutoConfigurationPackages.BasePackages,它有一個引數,這個引數是使用了@AutoConfigurationPackage這個註解的類所在的包路徑。有了這個包路徑後,就會掃描這個包下的所有class檔案,然後將需要註冊到Bean容器的類,給註冊進去。

具體可以參見這裡 org.springframework.boot.autoconfigure.AutoConfigurationPackages#register

這裡就解釋了為什麼有時候主配置類放的位置不對,導致有些類沒被Spring容器納入管理

桃花源

經歷了一番折騰,就要進入桃花源了

AutoConfigurationImportSelector就是那最後一層窗戶紙

// org.springframework.boot.autoconfigure.AutoConfigurationImportSelector
public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware,
      ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {
    @Override
    public String[] selectImports(AnnotationMetadata annotationMetadata) {
        if (!isEnabled(annotationMetadata)) {
            return NO_IMPORTS;
        }
        // 為了載入spring-boot-autoconfiguration包下的配置檔案META-INF/spring-autoconfigure-metadata.properties
        // 這裡配置的主要是一些SpringBoot啟動時用到的一些@ConditionOnClass的配置
        AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
                .loadMetadata(this.beanClassLoader);
        // 這裡的AutoConfigurationEntry,就包含了所有的匯入的需要被例項化的Bean
        AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(autoConfigurationMetadata,
                annotationMetadata);
        // 返回這些被匯入Bean的類全限定名陣列
        return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
    }

    protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata,
            AnnotationMetadata annotationMetadata) {
        if (!isEnabled(annotationMetadata)) {
            return EMPTY_ENTRY;
        }
        ··· 省略 ···
        // 獲取所有的需要匯入的Bean,這些被匯入的Bean就是各個元件需要自動化配置的啟動點
        List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
        ··· 省略 ···
        return new AutoConfigurationEntry(configurations, exclusions);
    }

    protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
        // 使用SpringFactoriesLoader#loadFactoryNames方法,從所有的包及classpath目錄下,
        // 查詢META-INF/spring.factories檔案,且名稱為org.springframework.boot.autoconfigure.EnableAutoConfiguration的配置
        List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),
                getBeanClassLoader());
        Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you "
                + "are using a custom packaging, make sure that file is correct.");
        return configurations;
    }
}

最終這些配置在META-INF/spring.factories中需要自動配置的類,就會被註冊到Spring Bean容器中,然後被例項化,呼叫初始化方法等!

這些做自動配置的類,基本都會通過實現各種Aware介面,獲取到Spring Framework中的BeanFactoryApplicationContext等等所有的一些框架內的元件,用於後面使用。

之後完成自己框架的一些初始化工作,主要就是將原先和Spring整合時,需要手動配置的那些,在這裡通過程式設計式的方式,給做了。

這樣,就完成了所謂的自動化配置,全程不需要我們的任何參與。

PS: 這個僅僅是做了一個通用的配置,讓使用者可以在不做任何配置的情況下能直接使用。但是一些個性化的配置,還是需要通過配置檔案的方式,寫入配置。對於這部分配置的處理,SpringBoot也都給攬下了

總結

整體看下來,SpringBoot乾的這些,更像是一個體力活,將於Spring整合的那麼多三方庫的配置,使用程式碼全部實現了一遍,其使用的核心功能,依然是Spring Framework的那些東西。

但是這個體力活是為使用者省下的,也讓Spring Framework更加的具有活力了。

同時微服務的興起,也是Spring為了順勢而必須作出的一個改變,也可以說為Spring在微服務領域立下了汗馬功勞!

相關文章