Spring boot自動裝配實現原理

自動裝配原理分析

條件註冊機制

spring-context模塊中有兩個組件:Condition接口和@Conditional註解,在@Conditional註解中可以指定一組Condition實現, 通常@Conditional是和@Component@Configuration@Bean搭配使用的,當用在@Configuration配置類上 則對其中的所有@Bean方法以及@Import導入的所有配置類都有效。

Condition接口表示執行條件測試,它有一個matches方法,方法返回true時條件測試通過,只有@Conditional中 給出的所有條件測試通過後纔會註冊bean。

Spring boot項目的 spring-boot-autoconfigure 模塊裏面寫了很多種@ConditionalOn...註解,每一個這種 註解本質就是@Conditional註解的使用,每一個註解表示一組需要測試的條件,例如常見的有:

  • @ConditionalOnClass測試classpath中存在指定的類型則生效。
  • @ConditionalOnClass測試classpath種不存在指定的類型則生效。
  • @ConditionalOnBean測試容器中已經註冊了某bean,則生效。
  • @ConditionalOnMissingBean測試容器種不存在某bean,則生效。
  • ConditionalOnProperty測試Environment中存在指定的屬性,則生效。
  • @ConditionalOnWebApplication測試應用程序是否是一個web應用程序。

還有很多這樣的註解,可以到...autoconfigure.condition包下面查看。

spring-boot-autoconfigure模塊裏面還有各種...AutoConfiguration類,它們都被特定的 @ConditionalOn... 註解修飾,是實現自動配置的關鍵,例如JdbcTemplateAutoConfiguration等,打開它的源代碼查看一下是如何聲明自動配置的:

@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ DataSource.class, JdbcTemplate.class })
@ConditionalOnSingleCandidate(DataSource.class)
@AutoConfigureAfter(DataSourceAutoConfiguration.class)
@EnableConfigurationProperties(JdbcProperties.class)
@Import({ DatabaseInitializationDependencyConfigurer.class, JdbcTemplateConfiguration.class, NamedParameterJdbcTemplateConfiguration.class })
public class JdbcTemplateAutoConfiguration {

}

查看其中導入的JdbcTemplateConfiguration:

@Configuration(proxyBeanMethods = false)
@ConditionalOnMissingBean(JdbcOperations.class)
class JdbcTemplateConfiguration {
    @Bean
    @Primary
    JdbcTemplate jdbcTemplate(DataSource dataSource, JdbcProperties properties) {
        JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
        JdbcProperties.Template template = properties.getTemplate();
        jdbcTemplate.setFetchSize(template.getFetchSize());
        jdbcTemplate.setMaxRows(template.getMaxRows());
        if (template.getQueryTimeout() != null) {
            jdbcTemplate.setQueryTimeout((int) template.getQueryTimeout().getSeconds());
        }
        return jdbcTemplate;
    }
}

只有在classpath中存在DataSource.class、JdbcTemplate.class、容器中只存在一個DataSource實例、並且容器中不存在 JdbcOperation實例,這些條件滿足後纔會在容器中註冊一個JdbcTemplate實例。

總結:@ConditionalOn......AutoConfiguration是理解spring boot自動配置機制的關鍵。

工廠加載機制

理解了...AutoConfiguration配置類的作用和效果,那這些自動配置類又是如何發生效果的呢?

背後的大致原理:其實是@EnableAutoConfiguration 的作用,@SpringBootApplication 這個總的註解導入了 @EnableAutoConfiguration,而@EnableAutoConfiguration 通過 @Import 導入了 AutoConfigurationImportSelector 這個ImportSelector實現,AutoConfigurationImportSelector 的主要作用就是導入各種...AutoConfiguration 自動配置類,在背後它是通過Spring工廠加載機制實現加載的,這就是Spring boot程序只需要一個 @SpringBootApplication 註解,就可以啓動自動配置原因。

// 這是@EnableAutoConfiguration的源代碼概要
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
    Class<?>[] exclude() default {}; //要排出的XxxAutoConfiguration類
    String[] excludeName() default {};
}

AutoConfigurationImportSelector 是一個 ImportSelector接口實現,跟蹤其selectImorts方法,下面看看局部重要代碼:

@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
    // ...
    // 調getAutoConfigurationEntry方法查找自動配置類
    AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);
    return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}

protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
    // ...
    AnnotationAttributes attributes = getAttributes(annotationMetadata);
    // 調getCandidateConfigurations方法加載自動配置類
    List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
    configurations = removeDuplicates(configurations);
    Set<String> exclusions = getExclusions(annotationMetadata, attributes);
    checkExcludedClasses(configurations, exclusions);
    configurations.removeAll(exclusions);
    configurations = getConfigurationClassFilter().filter(configurations);
    fireAutoConfigurationImportEvents(configurations, exclusions);
    return new AutoConfigurationEntry(configurations, exclusions);
}

protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
    // 這裏就是SpringFactoiesLoader加載的地方
    // 加載的組件就是用“org.springframework.boot.autoconfigure.EnableAutoConfiguration.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;
}

protected Class<?> getSpringFactoriesLoaderFactoryClass() {
    return EnableAutoConfiguration.class;
}

spring.factories

Spring工廠加載機制查找組件的地址是 META-INF/spring.factories 文件,是一個properties文件,對格式有一定要求, key部分是某一類功能組件的上層抽象接口的全限定名,value是具體實現類的全限定名列表,多個實現類型用逗號分割,下面是 spring-boot-autoconfigure模塊中的例子:

# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\
.....

結合前面的代碼可以看到,AutoConfigurationImportSelector委託SpringFactoriesLoader以 org.springframework.boot.autoconfigure.EnableAutoConfiguration爲key,從META-INF/spring.factories文件中讀取 所有的自動配置組件。

Spring boot 2.7 加載邏輯有點變更

在Spring boot 2.7中,自動配置類的加載機制發生了變化,增加了一種自動配置類加載方式,下面列出的是變化的結果。

(1)自動配置類列表除了可以使用 META-INF/spring.factories 文件來維護,又增加一個 META-INF/org.springframework.boot.autoconfigure.AutoConfiguration.imports 文件維護,新的文件格式 不再是properties文件,而是每個自動配置類單獨寫一行,更像Java SPI services配置文件。

(2)Spring boot 2.7 引入一個新的註解 @AutoConfiguration,所有的自動配置類現在要增加這個註解的標註聲明。

(3)加載方式變化,除了對 SpringFactoriesLoader 加載方式仍然保持兼容,又引入了一個ImportCandidates組件, 專門用於加載@Import導入的自動配置類,這個組件即從新的 META-INF/xxxConfigration.imports 文件中查找配置,這點 查看2.7版本中 AutoConfigurationImportSelector#getCandidateConfigurations 方法的實現。

保持對 SpringFactoriesLoader 加載方式保持兼容是有必要的,因爲還有除開Spring boot自身之外的第三方模塊, 已經存在的三方模塊任然是依賴於 SpringFactoriesLoader 機制在加載。

總結

XxxAutoConfiguration,@ConditionalOnXxx

@Import(ImportSelector)

@EnableXxx <=> @Import(xxxImportSelector)

具體的就是:

@SpringBootApplication <= @EnableAutoConfiguration

@EnableAutoConfiguration <=> @Import(AutoConfigurationImportSelector)

AutoConfigurationImportSelector查找所有XxxAutoConfiguration配置對象(SpringFactoriesLoader)

屬性加載原理

PropertySourceLoader

Spring boot有一個PropertySourceLoader組件,外部配置就是通過它完成加載的,默認有兩種實現:

  • PropertiesPropertySourceLoader
  • YamlPropertySourceLoader

分別對應properties屬性配置和yml屬性配置。

那麼,PropertySourceLoader是如何被觸發加載的呢,仍然是通過工廠加載機制實現的,看下面的ConfigFileApplicationListener

ConfigFileApplicationListener監聽器

ConfigFileApplicationListener是一個spring事件監聽器,同時也是一個EnvironmentPostProcessor,這意味着它可以修改底層的Environment, 外部配置就是這樣被填充到Environment管理的屬性集中的。

SpringApplication會發布事件,當在調用容器的refresh方法前,此時容器底層的Environment已經初始化完成,SpringApplication 會發佈一個ApplicationEnvironmentPreparedEvent事件,這個事件被ConfigFileApplicationListener監聽到,在其實現的 onApplicationEvent(event)方法中會加載所有EnvironmentPostProcessor組件,而加載EnvironmentPostProcessor是通過 SpringFactoriesLoader完成的,看看下面這幾個方法:

public class ConfigFileApplicationListener implements EnvironmentPostProcessor, SmartApplicationListener, Ordered {
    //......
    
	@Override
	public void onApplicationEvent(ApplicationEvent event) {
        // 如果是ApplicationEnvironmentPreparedEvent事件:
		if (event instanceof ApplicationEnvironmentPreparedEvent) {
			onApplicationEnvironmentPreparedEvent((ApplicationEnvironmentPreparedEvent) event);
		}
		if (event instanceof ApplicationPreparedEvent) {
			onApplicationPreparedEvent(event);
		}
	}

	private void onApplicationEnvironmentPreparedEvent(ApplicationEnvironmentPreparedEvent event) {
        // 加載EnvironmentPostProcessor:
		List<EnvironmentPostProcessor> postProcessors = loadPostProcessors();
		postProcessors.add(this);
		AnnotationAwareOrderComparator.sort(postProcessors);
		for (EnvironmentPostProcessor postProcessor : postProcessors) {
			postProcessor.postProcessEnvironment(event.getEnvironment(), event.getSpringApplication());
		}
	}

    // SpringFactoriesLoader加載EnvironmentPostProcessor:
	List<EnvironmentPostProcessor> loadPostProcessors() {
		return SpringFactoriesLoader.loadFactories(EnvironmentPostProcessor.class, getClass().getClassLoader());
	}
    
    //......
}

加載全部的EnvironmentPostProcessor後,就會回調 postProcessEnvironment(env, springApplication)方法, 這其中一個就是 ConfigFileApplicationListener 的實現,在此方法中通過創建一個Loader內部類對象,最後通過 PropertySourceLoader完成加載,在 ConfigFileApplicationListener 中,PropertySourceLoader 的所有實現 也是通過工廠加載機制創建的。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章