讓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`應用。需要做的僅僅是引入`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`在微服務領域立下了汗馬