1. 程式人生 > >讓SpringBoot自動化配置不再神祕

讓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`應用。需要做的僅僅是引入`SpringBoot`的`maven`或者`gradle`依賴即可。 `SpringBoot`要做的就是,讓你開箱即用! 將使用`Spring`的成本降到儘可能低,為使用者帶來了極大的便利。 當然`SpringBoot`做的也不僅僅只有這些,不過這裡僅討論下它的自動化配置,不討論其他的。 如果瞭解`Spring`對`@Configuration`這個註解的處理過程,會更加容易理解`SpringBoot`的自動化配置。 如果沒有,可以參考這篇[解釋](https://www.cnblogs.com/heartlake/p/12909362.html) ## 窮其林 這第一件事,就是找門,門都找不到,那不是`沒門`嗎! 既然想找門,就得從程式的啟動入口去找,任何`SpringBoot`程式都會用到這麼兩個 ```java @SpringBootApplication public class Application{ public static void main(String[] args){ SpringApplication.run(Application.class, args); } } ``` 看到這個後,如果好奇其實現,應該會首先檢視`SpringApplication#run`方法,實際呼叫的是這個過載的靜態方法。 ```java // 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`。 ```java @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 ```java @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @AutoConfigurationPackage @Import(AutoConfigurationImportSelector.class) public @interface EnableAutoConfiguration { ···省略··· } ``` 這個註解又使用了兩個註解,分別是`@AutoConfigurationPackage`和`@Import` ```java @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @Import(AutoConfigurationPackages.Registrar.class) public @interface AutoConfigurationPackage { } ``` 可以發現,這兩個註解最終都指向了同一個註解`@Import` `@Import`是`Annotation`時代的`
`,作用是向`BeanDefinitionRegistry`註冊`Bean`的。 所以`@EnableAutoConfiguration`這個註解一共註冊了兩個`Bean`,分別是:`AutoConfigurationPackages.Registrar.class`和`AutoConfigurationImportSelector.class` 先說說`AutoConfigurationPackages.Registrar`的用處 這個類就幹一個事,註冊一個`Bean`,這個`Bean`就是`org.springframework.boot.autoconfigure.AutoConfigurationPackages.BasePackages`,它有一個引數,這個引數是使用了`@AutoConfigurationPackage`這個註解的類所在的包路徑。有了這個包路徑後,就會掃描這個包下的所有`class`檔案,然後將需要註冊到`Bean`容器的類,給註冊進去。 >
具體可以參見這裡 `org.springframework.boot.autoconfigure.AutoConfigurationPackages#register` > 這裡就解釋了為什麼有時候主配置類放的位置不對,導致有些類沒被Spring容器納入管理 ## 桃花源 經歷了一番折騰,就要進入桃花源了 `AutoConfigurationImportSelector`就是那最後一層窗戶紙 ```java // 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 configurations = getCandidateConfigurations(annotationMetadata, attributes); ··· 省略 ··· return new AutoConfigurationEntry(configurations, exclusions); } protected List getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) { // 使用SpringFactoriesLoader#loadFactoryNames方法,從所有的包及classpath目錄下, // 查詢META-INF/spring.factories檔案,且名稱為org.springframework.boot.autoconfigure.EnableAutoConfiguration的配置 List 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`中的`BeanFactory`,`ApplicationContext`等等所有的一些框架內的元件,用於後面使用。 之後完成自己框架的一些初始化工作,主要就是將原先和`Spring`整合時,需要手動配置的那些,`在這裡通過程式設計式的方式`,給做了。 這樣,就完成了所謂的自動化配置,全程不需要我們的任何參與。 > PS: 這個僅僅是做了一個通用的配置,讓使用者可以在不做任何配置的情況下能直接使用。但是一些個性化的配置,還是需要通過配置檔案的方式,寫入配置。對於這部分配置的處理,`SpringBoot`也都給攬下了 ## 總結 整體看下來,`SpringBoot`乾的這些,更像是一個體力活,將於`Spring`整合的那麼多三方庫的配置,使用程式碼全部實現了一遍,其使用的核心功能,依然是`Spring Framework`的那些東西。 但是這個體力活是為使用者省下的,也讓`Spring Framework`更加的具有活力了。 同時微服務的興起,也是`Spring`為了順勢而必須作出的一個改變,也可以說為`Spring`在微服務領域立下了汗馬