SpringBoot進階之道-@SpringBootApplication
相信小夥伴們在寫springboot專案的時候,在啟動類上加上@SpringBootApplication註解引導,就可以自動裝配。例如下面這樣:
@SpringBootApplication
public class SpringbootTestApplication {
public static void main(String[] args) {
SpringApplication.run(SpringbootTestApplication.class,args);
}
}
複製程式碼
- 那麼@SpringBootApplication註解是怎麼做到的?
- 什麼叫 @Component派生註解?
1、理解@SpringBootApplication語義
引用官方的話:@SpringBootApplication被用於啟用@EnableAutoConfiguration
、@ComponentScan
和@Configuration
三個特性。其中,@EnableAutoConfiguration
負責啟用SpringBoot自動裝配機制,@ComponentScan
啟用@Component的掃描,@Configuration
宣告被標註為配置類。官方檔案繼續告訴開發人員@SpringBootApplication註解等同於@Configuration
、@EnableAutoConfiguraion
@ComponentScan
註解,且它們均使用預設屬性。我們這樣改造上面的程式碼:
//@SpringBootApplication
@Configuration
@EnableAutoConfiguration
@ComponentScan
public class SpringbootTestApplication {
public static void main(String[] args) {
SpringApplication.run(SpringbootTestApplication.class,args);
}
}
複製程式碼
我們啟動執行,觀察日誌,沒錯,一切正如你想象的正常。
額~ 相信有不少人翻過springBoot2.x的@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 {
....
}
複製程式碼
由此可見,那麼有個疑問?@SpringBootSpplication是不是不等價上面介紹的@EnableAutoConfiguration
、@ComponentScan
和@Configuration
註解?
那麼我們分析,由上可見,@SpringBootApplication等價於@SpringBootConfiguration
、@ComponentScan
和@EnableAutoConfiguration
,不過@ComponentScan並非使用了預設值,而是添加了排除的TypeFilter實現:TypeExcludeFilter和AutoConfigurationExcludeFilter。前者由Springboot1.4引入,用於查詢BeanFactory中已註冊的TypeExcludeFilter Bean,作為代理執行物件:
public class TypeExcludeFilter implements TypeFilter,BeanFactoryAware {
...
@Override
public boolean match(MetadataReader metadataReader,MetadataReaderFactory metadataReaderFactory) throws IOException {
if (this.beanFactory instanceof ListableBeanFactory && this.getClass() == TypeExcludeFilter.class) {
Collection<TypeExcludeFilter> delegates = ((ListableBeanFactory)this.beanFactory).getBeansOfType(TypeExcludeFilter.class).values();
Iterator var4 = delegates.iterator();
while(var4.hasNext()) {
TypeExcludeFilter delegate = (TypeExcludeFilter)var4.next();
if (delegate.match(metadataReader,metadataReaderFactory)) {
return true;
}
}
}
return false;
}
...
}
複製程式碼
而後者從SpringBoot1.5開始支援,用於排除其他同時標註@Configuration和@EnableAutoConfiguration的類:
public class AutoConfigurationExcludeFilter implements TypeFilter,BeanClassLoaderAware {
...
@Override
public boolean match(MetadataReader metadataReader,MetadataReaderFactory metadataReaderFactory) throws IOException {
return this.isConfiguration(metadataReader) && this.isAutoConfiguration(metadataReader);
}
private boolean isConfiguration(MetadataReader metadataReader) {
return metadataReader.getAnnotationMetadata().isAnnotated(Configuration.class.getName());
}
private boolean isAutoConfiguration(MetadataReader metadataReader) {
return this.getAutoConfigurations().contains(metadataReader.getClassMetadata().getClassName());
}
protected List<String> getAutoConfigurations() {
if (this.autoConfigurations == null) {
this.autoConfigurations = SpringFactoriesLoader.loadFactoryNames(EnableAutoConfiguration.class,this.beanClassLoader);
}
return this.autoConfigurations;
}
}
複製程式碼
我們來對比一下SpringBoot1.3.8的@SpringBootApplication的宣告:
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Configuration和
@EnableAutoConfiguration
@ComponentScan
public @interface SpringBootApplication {
....
}
複製程式碼
由此可見,SpringBoot1.3.8的實現與官方檔案描述的是一樣的。儘管SpringBoot1.4之後的@SpringBootApplication通常不會表現出與檔案相異的行為,但官方沒更新檔案也沒給出具體有什麼不一樣的解釋。
2、@Component ‘派生性’
但我們知道,從SpringBoot1.4開始,@SpringBootApplication註解不再標註@Configuration,而是@SpringBootConfiguration
,不過兩者在執行上的行為並沒有什麼不一樣,這種類似於物件之間的繼承關係,我們稱之為“多層次 @Component‘派生性’”
,哈哈哈,並且這種能力也允許我們擴充套件使用的,是不是很嗨。我們看下@Configuration註解,它其實標註了@Component註解:
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Configuration {
...
}
複製程式碼
所以我們得知,@Configuration實際上是@Component的派生性註解,同理@SpringBootConfiguration標註了@Configuration:
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
public @interface SpringBootConfiguration {
}
複製程式碼
因此三者的關係為:
- @component
- @Configuration
- @SpringBootConfiguration
- @Configuration
我們知道,@CompoentScan僅掃描帶有@Component註解的類,並註冊成bean,然而由於@SpringBootConfiguration屬於多層次的@Component“派生”註解,所以能被@CompoentScan識別。但是我們知道@CompoentScan屬於Spring Framework,而@SpringBootConfiguration來自SpringBoot,那麼是什麼機制讓@CompoentScan能夠識別@SpringBootConfiguration註解呢?這種機制就是前面提到的“多層次 @Component ‘派生性’”。
3、小結
SpringBoot是通過註解@EnableAutoConfiguration的方式,去查詢,過濾,載入所需的Configuration,@ComponentScan掃描我們自定義的bean,@SpringBootConfiguration(派生性@Component)使得被@SpringBootApplication註解的類宣告為註解類,因此@SpringBootApplication的作用等價於同時組合使用@EnableAutoConfiguration,@ComponentScan,@SpringBootConfiguration。