本文若有任何紕漏、錯誤,還請不吝指出!
注:本文提到的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
的自動化配置。
如果沒有,可以參考這篇解釋
窮其林
這第一件事,就是找門,門都找不到,那不是沒門
嗎!
既然想找門,就得從程式的啟動入口去找,任何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
@Import
是Annotation
時代的<import/>
,作用是向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
就是那最後一層窗戶紙
// 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
中的BeanFactory
,ApplicationContext
等等所有的一些框架內的元件,用於後面使用。
之後完成自己框架的一些初始化工作,主要就是將原先和Spring
整合時,需要手動配置的那些,在這裡通過程式設計式的方式
,給做了。
這樣,就完成了所謂的自動化配置,全程不需要我們的任何參與。
PS: 這個僅僅是做了一個通用的配置,讓使用者可以在不做任何配置的情況下能直接使用。但是一些個性化的配置,還是需要通過配置檔案的方式,寫入配置。對於這部分配置的處理,
SpringBoot
也都給攬下了
總結
整體看下來,SpringBoot
乾的這些,更像是一個體力活,將於Spring
整合的那麼多三方庫的配置,使用程式碼全部實現了一遍,其使用的核心功能,依然是Spring Framework
的那些東西。
但是這個體力活是為使用者省下的,也讓Spring Framework
更加的具有活力了。
同時微服務的興起,也是Spring
為了順勢而必須作出的一個改變,也可以說為Spring
在微服務領域立下了汗馬功勞!