@EnableAutoConfiguration 和 @Import 的原理


(version:springboot 2.3.2)

@EnableAutoConfiguration 的作用


 

@EnableAutoConfiguration 的作用是開啟 Spring 應用上下文的自動配置,它會嘗試去猜測和配置我們所需要的 bean。 例如:如果我們的 classpath 中有 tomcat-embedded.jar,那么我們可能需要一個 TomcatServletWebServerFactory 的 bean。

@EnableAutoConfiguration 試圖盡可能的智能化: 1, 當我們的 classpath 中存在某些 jar 或者類時,它會幫助我們自動配置 bean; 2, 當我們定義自己的配置時,自動配置的 bean 將不再加載。(具體是通過 一系列的 @ConditionalOnXxx 來實現的)

我們也可以通過注解中的 exclude() 來手動排除任何不想用的配置。 如果沒有權限訪問到指定排除的類的話,可以使用 excludeName(),或者 spring.autoconfigure.exclude 來指定

當使用 @SpringBootApplication 時,@EnableAutoConfiguration 是自動啟用的。

@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 {
    ...
}

 

org.springframework.boot.autoconfigure.SpringBootApplication
org.springframework.boot.autoconfigure.EnableAutoConfiguration 
這兩個注解都來自於 spring-boot-autoconfigure-xx.RELEASE.jar

 

@EnableAutoConfiguration 的原理


 

@Enable 開頭的注解上一般都有 @Import 來指定注解的邏輯實現類。

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
    String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
    Class<?>[] exclude() default {};
    String[] excludeName() default {};
}

@EnableAutoConfiguration 注解繼承了 @Import,@Import 導入的 AutoConfigurationImportSelector 實現了 ImportSelector 接口。

所以,@EnableAutoConfiguration 的原理與 @Import 的用法密不可分。 其實,@EnableXxx 的奧秘就是 @Import 的使用和原理。

AutoConfigurationImportSelector#selectImports() 調用棧:

1. AutoConfigurationImportSelector#getCandidateConfigurations
    1.1 AutoConfigurationImportSelector#getSpringFactoriesLoaderFactoryClass() // 返回 org.springframework.boot.autoconfigure.EnableAutoConfiguration
    1.2 SpringFactoriesLoader#loadFactoryNames // 通過 SpringBoot SPI 去加載 spring.factories 中 key=org.springframework.boot.autoconfigure.EnableAutoConfiguration 的值

通過 AutoConfigurationImportSelector#selectImports() 返回的自動配置類,ConfigurationClassParser#processImports() 方法再去加載和處理這些配置類。這就是 @EnableAutoConfiguration 的核心原理。

 

@Import 的使用和原理


 

ImportSelector

ImportSelector 接口可以根據給定的條件來決定導入哪些 @Configuration 類。 ImportSelector 可以實現以下任何 Aware 接口,這些 Aware 接口會在 selectImports() 之前被調用:

EnvironmentAware
BeanFactoryAware
BeanClassLoaderAware
ResourceLoaderAware

或者,也可以使用構造函數來注入這些類:

Environment
BeanFactory
ClassLoader
ResourceLoader

 

/**
* 根據 @Configuration 類給定的 AnnotationMetadata 注解元數據信息(具體是指 @EnableXxx 的元數據信息)選擇並返回應該導入的類的名稱。
* @return the class names, or an empty array if none
*/
String[] selectImports(AnnotationMetadata importingClassMetadata)

方法的解釋很明確,就是根據 selectImports() 方法入參里傳入的 AnnotationMetadata(具體是指 @EnableXxx 注解的元數據信息),selectImports() 會返回需要導入的 @Configuration 類的名稱。

ImportSelector 的處理最終由 ConfigurationClassParser 來處理的,詳細參看 org.springframework.context.annotation.ConfigurationClassParser#processImports()

 

ImportSelector 的實現類通常會與 @Import 以相同的方式進行處理,但是,我們也可以將導入的選擇推遲到所有的 @Configuration 類都處理完畢(詳細信息請參閱 DeferredImportSelector)。

 

DeferredImportSelector


 

DeferredImportSelector 是 ImportSelector 的變種,在處理完所有 @Configuration bean 之后運行。當所選導入是 @Conditional 時,這種選擇器特別有用。DeferredImportSelector 的實現類還可以提供一個導入組 getImportGroup(),該組可以跨不同的選擇器提供額外的排序和過濾邏輯。

我想 DeferredImportSelector 延遲處理的原因可能是: 讓所有的 @Configuration 執行完之后,該加載到容器中的 bean 都已經加載了,這時再去加載 DeferredImportSelector 所要選擇導入的 @Configuration 時, @ConitionalOnBean 就可以發揮它的作用了,否則,如果提前加載了 DeferredImportSelector 所要選擇導入的 @Configuration,這時,用戶自定義的 @Configuration 還沒有加載呢,結果框架自動導入的配置就優先加載進去了,所以 @ConitionalOnBean 就起不到想要的效果了。

查看源碼可以發現 ConfigurationClassParser#processImports 對 DeferredImportSelector 有特殊的處理,也就是這個延遲的處理。

 

ImportBeanDefinitionRegistrar


 

通過 ImportBeanDefinitionRegistrar 可以在處理 @Configuration 類時注冊其他 beanDefinition 到容器中。 ImportBeanDefinitionRegistrar 可以實現以下任何 Aware 接口,這些 Aware 接口會在 registerBeanDefinitions() 之前被調用:

EnvironmentAware
BeanFactoryAware
BeanClassLoaderAware
ResourceLoaderAware

或者,也可以使用構造函數來注入這些類:

Environment
BeanFactory
ClassLoader
ResourceLoader

 

/**
* 根據 @Configuration 類給定的 AnnotationMetadata 注解元數據信息(具體是指 @EnableXxx 的元數據信息),注冊所需的 beanDefinition。
*/
void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry)

具體是通過 BeanDefinitionRegistry 來注冊 beanDefinition 的。

用法舉例:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(AspectJAutoProxyRegistrar.class)
public @interface EnableAspectJAutoProxy {
   boolean proxyTargetClass() default false;
   boolean exposeProxy() default false;
}

通過 @Import 來導入一個 ImportBeanDefinitionRegistrar 的實例,ImportBeanDefinitionRegistrar#registerBeanDefinitions() 被調用時可以往容器中注入 beanDefinition。

 

總結


 

  1. @EnableXxx 注解是用在 @Configuration 標記的類上,用來自動配置和加載一些 @Configuration 類或者 bean 的。 所以,@EnableXxx 一定是與 @Configuration 一起使用的。

  2. @EnableXxx 一定會與 @Import 一起使用,通過 @Import 來達到自動配置的目的。

@Import 有 3 種用法,分別是:

  1. 導入具體的配置類

  2. 導入 ImportSelector(或者 DeferredImportSelector)

  3. 導入 ImportBeanDefinitionRegistrar

這 3 種類的處理最終都是由org.springframework.context.annotation.ConfigurationClassParser#processImports() 方法來處理導入的。

實例:

導入具體的配置類 :直接導入配置類

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(SchedulingConfiguration.class)
@Documented
public @interface EnableScheduling {
}

 

導入ImportSelector:通過 ImportSelector#selectImports() 來選擇需要導入的配置類

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(AsyncConfigurationSelector.class)
public @interface EnableAsync {
    ...
}

 

導入DeferredImportSelector:主要是使用了 DeferredImportSelector 的延遲導入能力

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
    String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
    Class<?>[] exclude() default {};
    String[] excludeName() default {};
}

 

導入ImportBeanDefinitionRegistrar:通過 ImportBeanDefinitionRegistrar 來向容器中注入 bean

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(AspectJAutoProxyRegistrar.class)
public @interface EnableAspectJAutoProxy {
   boolean proxyTargetClass() default false;
   boolean exposeProxy() default false;
}

 

參考:
https://my.oschina.net/floor/blog/4354771

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM