讓SpringBoot自動化配置不再神秘


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

注:本文提到的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在微服務領域立下了汗馬功勞!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM