SpringBoot是對Spring的一種擴展,其中比較重要的擴展功能就是自動裝配:通過注解對常用的配置做默認配置,簡化xml配置內容,本文會對Spring的自動配置的原理和部分原始碼進行決議,本文主要參考了Spring的官方檔案,
自動裝配的組件
SpringBoot自動裝配通過多部分組件協調完成,這些組件主要有下面幾種,這幾種組件之間協調作業,最終完成了SpringBoot的自動裝配,
- @EnableAutoConfiguration:用于根據用戶所參考的jar包自動裝配Spring容器,比如用戶在ClassPath中包含了HSQLDB,但是沒有手動配置資料庫連接,那么Spring會自動使用HSQLDB作為資料源,
- @Condition:不同情況下按照條件進行裝配,Spring的JdbcTemplate是不是在Classpath里面?如果是,并且DataSource也存在,就自動配置一個JdbcTemplate的Bean
- @ComponentScan:掃描指定包下面的@Component注解的組件,
@EnableAutoConfiguration注解
Spring的自動裝配發展大致可以分為三個階段:
- 全手工配置的XML檔案階段,用戶需要的Bean全部需要在XML檔案中宣告,用戶手工管理全部的Bean,
- 半手工配置的注解階段,用戶可以安裝需求Enable對應的功能模塊,如添加@EnableWebMvc可以啟用MVC功能,
- 全自動配置的SpringBoot,用戶只需要引入對應的starter包,Spring會通過factories機制自動裝配需要的模塊,
全手工配置的XML檔案示意圖:

半自動注解配置示意圖:

全自動注解配置示意圖:

Spring啟用全自動配置功能的注解就是@EnableAutoConfiguration,應用添加了@EnableAutoConfiguration注解之后,會讀取所有jar包下面的spring.factories檔案,獲取檔案中配置的自動裝配模塊,然后去裝配對應的模塊,
@EnableAutoConfiguration的功能可總結為:使Spring啟用factories機制匯入各個starter模塊的配置,
原理分析
通過上面的分析我們知道Spring的@EnableAutoConfiguration主要功能是使Spring啟用factories機制匯入各個starter模塊的配置,下面我們會對@EnableAutoConfiguration的原始碼進行簡單分析,
@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注解的定義有兩部分比較重要的內容:
@AutoConfigurationPackage:將添加該注解的類所在的package作為自動配置package進行管理,
@Import({AutoConfigurationImportSelector.class}):用于匯入factories檔案中的AutoConfiguration,
@Import({AutoConfigurationImportSelector.class})
首先我們需要知道@Import注解的作用,從字面意思就可以看出來,@Import用于把一個Bean注入到Spring的容器中,@Import可以匯入三種型別的Bean:
- 匯入普通的Bean,通常是@Configuration注解的Bean,也可以是任意的@Component組件型別的類,
- 匯入實作了ImportSelector介面的Bean,ImportSelector介面可以根據注解資訊匯入需要的Bean,
- 匯入實作了ImportBeanDefinitionRegistrar注解的Bean, ImportBeanDefinitionRegistrar介面可以直接向容器中注入指定的Bean,
@Import({AutoConfigurationImportSelector.class})中的AutoConfigurationImportSelector實作了ImportSelector介面,會按照注解內容去裝載需要的Bean,
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!this.isEnabled(annotationMetadata)) {
return NO_IMPORTS;
} else {
// 獲取需要自動裝配的AutoConfiguration串列
AutoConfigurationImportSelector.AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(annotationMetadata);
// 獲取自動裝配類的類名稱串列
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
}
// 獲取需要自動裝配的AutoConfiguration串列
protected AutoConfigurationImportSelector.AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
if (!this.isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
} else {
// 獲取注解中的屬性
AnnotationAttributes attributes = this.getAttributes(annotationMetadata);
// 獲取所有META-INF/spring.factories中的AutoConfiguration類
List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
// 洗掉重復的類
configurations = this.removeDuplicates(configurations);
// 獲取注解中Execlud的類
Set<String> exclusions = this.getExclusions(annotationMetadata, attributes);
this.checkExcludedClasses(configurations, exclusions);
// 移除所有被Exclude的類
configurations.removeAll(exclusions);
// 使用META-INF/spring.factories中配置的過濾器
configurations = this.getConfigurationClassFilter().filter(configurations);
// 廣播相關的事件
this.fireAutoConfigurationImportEvents(configurations, exclusions);
// 回傳符合條件的配置類,
return new AutoConfigurationImportSelector.AutoConfigurationEntry(configurations, exclusions);
}
}
@AutoConfigurationPackage
@AutoConfigurationPackage用于將添加該注解的類所在的package作為自動配置package進行管理,聽起來是不是和@ComponentScan功能有所重復?我們來分析一下其具體實作,可以看到這個注解依舊是通過@Import注解向容器中注冊Bean,
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import({Registrar.class})
public @interface AutoConfigurationPackage {
String[] basePackages() default {};
Class<?>[] basePackageClasses() default {};
}
@AutoConfigurationPackage注解匯入了Registrar.class,其本質是一個ImportBeanDefinitionRegistrar,會把當前注解類所在的包注入到Spring容器中,
static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
Registrar() {
}
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
AutoConfigurationPackages.register(registry, (String[])(new AutoConfigurationPackages.PackageImports(metadata)).getPackageNames().toArray(new String[0]));
}
public Set<Object> determineImports(AnnotationMetadata metadata) {
return Collections.singleton(new AutoConfigurationPackages.PackageImports(metadata));
}
}
@ComponentScan并不會把類所在的包注入到容器中,@ComponentScan只注入指定的包,類所在的包通過@AutoConfigurationPackage注入,
@Conditional注解
@Conditional注解的組件只有在滿足特定條件的情況下才會被注冊到容器中,@Conditional注解的定義如下所示,可以看到這個注解只有一個內容:Condition,所以這個注解的重點就是Condition介面,
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Conditional {
/**
* All {@link Condition} classes that must {@linkplain Condition#matches match}
* in order for the component to be registered.
*/
Class<? extends Condition>[] value();
}
Condition介面的定義如下所示,該介面只包含一個方法,輸入當前的背景關系資訊和注解的引數,判斷注解的Bean是否可以注冊到Spring容器中,其中背景關系資訊包含了:Bean定義管理器(BeanDefinitionRegistry)/BeanFactory/背景關系環境Environment/資源加載器ResourceLoader/類加載器ClassLoader,
@FunctionalInterface
public interface Condition {
/**
* Determine if the condition matches.
* @param context the condition context
* @param metadata the metadata of the {@link org.springframework.core.type.AnnotationMetadata class}
* or {@link org.springframework.core.type.MethodMetadata method} being checked
* @return {@code true} if the condition matches and the component can be registered,
* or {@code false} to veto the annotated component's registration
*/
boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);
}
@Conditional判斷時機
@Conditionl注解作用于Spring讀取Bean定義的階段,這個階段大多數Bean尚未實體化,少數實體化的Bean屬于Spring的特殊Bean,不能條件控制是否加載,
Spring中的Bean有很多來源,如掃描包下的Component、@Bean注解的方法、@Import、用戶手工注冊Bean等方法注冊Bean,這些所有來源的Bean定義都可以使用@Conditional進行處理嗎?答案是不是所有Bean定義來源都會使用@Conditional注解進行過濾,只有掃描包或者@Configuration注解類中的的Bean會使用@Conditionl注解進行判斷,
-
掃描包注冊Bean,我們知道基于注解的Spring容器會通過掃描包的形式去獲取路徑下面的Component,這部分Bean定義是最早被加載到Spring容器中的,Spring通過
AnnotatedBeanDefinitionReader讀取并注冊@Component對應的Bean定義,其中@Conditional判斷的部分邏輯如下:private <T> void doRegisterBean(Class<T> beanClass, @Nullable String name, @Nullable Class<? extends Annotation>[] qualifiers, @Nullable Supplier<T> supplier, @Nullable BeanDefinitionCustomizer[] customizers) { AnnotatedGenericBeanDefinition abd = new AnnotatedGenericBeanDefinition(beanClass); // 此處判斷Bean是否應該被注冊 if (this.conditionEvaluator.shouldSkip(abd.getMetadata())) { return; } // 省略其它注冊邏輯 } -
@Bean方法注冊,在@Configuration中的@Bean和不在@Configuration中的@Bean的模式不同,我在其它文章中有介紹,但是獲取Bean定義的邏輯是一致的,@Bean注解的方法是通過
ConfigurationClassBeanDefinitionReader去讀取的,其中也包含了對@Conditional注解的判斷,其中部分關鍵判斷代碼如下所示,private void loadBeanDefinitionsForBeanMethod(BeanMethod beanMethod) { ConfigurationClass configClass = beanMethod.getConfigurationClass(); MethodMetadata metadata = https://www.cnblogs.com/yuhushen/p/beanMethod.getMetadata(); String methodName = metadata.getMethodName(); // Do we need to mark the bean as skipped by its condition? if (this.conditionEvaluator.shouldSkip(metadata, ConfigurationPhase.REGISTER_BEAN)) { configClass.skippedBeanMethods.add(methodName); return; } if (configClass.skippedBeanMethods.contains(methodName)) { return; }@Configuration類中的@Bean注解是在
ConfigurationClassPostProcessor中進行決議和注冊的,這是一個BeanDefinitionRegistryPostProcessor,執行時機顯然晚于掃描包的時機,
從@Conditional的判斷原理可以看出,Spring應當只允許Bean定義一個個進行注冊,并且要嚴格保證讀取順序,不允許Bean定義的批量注冊,
@Conditional擴展
為了簡化用戶的使用,Spring提供了幾種常見的@Conditional的實作,我們下文中會介紹常見的幾種實作,
- @ConditionalOnBean注解,當特定的Bean注解存在的時候注冊當前Bean,使用這個注解的時候要注解Bean的先后順序,因為@ConditionalOnBean會去當前容器中查找是否有滿足條件的Bean,后注冊的Bean會受先注冊Bean的影響,
- @ConditionalOnClass注解,當前類路徑中包含指定類的時候注冊當前Bean,
- @ConditionalOnMissingBean注解,當指定型別的Bean不存在的時候注冊當前Bean,
- @ConditionalOnMissingClass注解,當前類路徑中不包含指定類的時候注冊當前Bean,
- @ConditionalOnProperty注解,指定屬性滿足特定值是生效,
值得一提的是,@Profile注解本身也是使用@Conditional注解進行Bean的條件注冊的,
@Conditional(ProfileCondition.class)
public @interface Profile {
/**
* The set of profiles for which the annotated component should be registered.
*/
String[] value();
}
@ComponentScan組件
有些情況下,我們的組件不都定義在帶有@EnableAutoConfiguration注解的類對應的包下面,這個時候@AutoConfigurationPackage掃描的類就不能滿足用戶的需求了,Spring提供了@ComponentScan組件讓用戶添加指定包下面的Spring組件到Spring容器中,
Filter選項
掃描包程序中,Spring允許用戶按照條件過濾所需要的Bean,@SpringBootApplication中本身包含了@ComponentScan注解,并為注解配置了兩個Filter:TypeExcludeFilter和AutoConfigurationExcludeFilter注解,這兩個注解允許用戶實作特定的類,從而實作對Bean定義的過濾,
@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 {
// 省略屬性
}
@Import原理
通過上面的內容,我們知道@Import在Spring的自動裝配中有很重要的作用,用于自動裝配程序中匯入指定的配置類,接下來我們分析一下@Import注解的原始碼及其作用機制,
@Import的決議依舊是在關鍵類ConfigurationClassPostProcessor中進行的,ConfigurationClassPostProcessor包含了@Bean、@Import等注解的決議,
private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass,
Collection<SourceClass> importCandidates, Predicate<String> exclusionFilter,
boolean checkForCircularImports) {
if (importCandidates.isEmpty()) {
return;
}
if (checkForCircularImports && isChainedImportOnStack(configClass)) {
this.problemReporter.error(new CircularImportProblem(configClass, this.importStack));
}
// 其它邏輯
}

本文最先發布至微信公眾號,著作權所有,禁止轉載!
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/404265.html
標籤:Java
