1. 程式人生 > 程式設計 >SpringBoot進階之道-@SpringBootApplication

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

我們知道,@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。