@EnableAutoConfiguration 原理分析 @SpringBootApplication中包含了@EnableAutoConfiguration註解,@EnableAutoConfiguration的作用是啟用Spring的自動載入配置。 SpringBoot一個最核心的觀點就是,約 ...
@EnableAutoConfiguration 原理分析
@SpringBootApplication中包含了@EnableAutoConfiguration註解,@EnableAutoConfiguration的作用是啟用Spring的自動載入配置。
SpringBoot一個最核心的觀點就是,約定大於配置,這種看似降低了靈活度的方法,卻大大簡化了SpringBoot的開發過程。這種約定在實現角度看就是SpringBoot提供了大量的預設配置參數,那麼問題來了,SpringBoot在哪裡存放這些預設的配置參數,並如何自動配置這些預設的參數呢?
實際上,SpringBoot的很多Starter都有@Enable開頭的註解,實現原理也很是類似,這裡先來看這個註解的實現原理。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
這裡我們可以看到@EnableAutoConfiguration使用@Import添加了一個AutoConfigurationImportSelector類,Spring自動註入配置的核心功能就依賴於這個對象。
在這個類中,提供了一個getCandidateConfigurations()
方法用來載入配置文件。藉助Spring提供的工具類SpringFactories的loadFactoryNames()方法載入配置文件。掃描的預設路徑位於META-INF/spring.factories
中。
# Initializers
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,\
org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener
# Application Listeners
org.springframework.context.ApplicationListener=\
org.springframework.boot.autoconfigure.BackgroundPreinitializer
# Auto Configuration Import Listeners
org.springframework.boot.autoconfigure.AutoConfigurationImportListener=\
org.springframework.boot.autoconfigure.condition.ConditionEvaluationReportAutoConfigurationImportListener
# Auto Configuration Import Filters
org.springframework.boot.autoconfigure.AutoConfigurationImportFilter=\
org.springframework.boot.autoconfigure.condition.OnBeanCondition,\
org.springframework.boot.autoconfigure.condition.OnClassCondition,\
org.springframework.boot.autoconfigure.condition.OnWebApplicationCondition
# 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,\
...
上邊只是一部分的配置信息。等號左邊的是對應配置的介面,而右邊是配置類。我們來看具體是如何載入這些類的。
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
MultiValueMap<String, String> result = cache.get(classLoader);
if (result != null) {
return result;
}
try {
// 從META-INF/spring.factories中載入urls。
Enumeration<URL> urls = (classLoader != null ?
classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
result = new LinkedMultiValueMap<>();
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
UrlResource resource = new UrlResource(url);
// 從文件中載入配置
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
for (Map.Entry<?, ?> entry : properties.entrySet()) {
String factoryClassName = ((String) entry.getKey()).trim();
for (String factoryName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
result.add(factoryClassName, factoryName.trim());
}
}
}
// 添加到緩存中防止重新載入
cache.put(classLoader, result);
return result;
}
catch (IOException ex) {
throw new IllegalArgumentException("Unable to load factories from location [" +
FACTORIES_RESOURCE_LOCATION + "]", ex);
}
}
最後返回的result實際上就是META-INF/spring.factories對應的Map數據結構。在這個Map中實際上保存了整個Spring中的各個配置類的全類名,包括一些Listener、Filter等等。載入一個類的第一步,就是要獲取到對應類的全類名,而之後具體的載入過程實際上是由SpringApplication具體完成的,這裡不做先仔細介紹,我們回到我們的重點上,如何自動載入配置。在剛剛的spring.factories中,我們來看下邊的內容:
# 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,\
org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,\
org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration,\
org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration,\
...
在這裡我們可以看到非常多熟悉的名稱,Aop,Rabbit等等非常多,看來自動載入的配置遠遠不止我們使用到的簡單配置,SpringBoot會載入全部可能用到的配置類。這裡我們先來打開一個熟悉的RabbitAutoConfiguration看一看。
@Configuration
@ConditionalOnClass({ RabbitTemplate.class, Channel.class })
@EnableConfigurationProperties(RabbitProperties.class)
@Import(RabbitAnnotationDrivenConfiguration.class)
public class RabbitAutoConfiguration {
這個類上邊添加了很多註解,我們一個個來看。
- @Configuration: 這個註解我們非常熟悉,標明這就是一個配置類;
- @ConditionalOnClass():這個註解是可以輸入幾個class,相當於給這個配置類添加了一個開關,當檢測存在輸入的類時候,該配置類生效,否則將不被實例化。也就是說,如果你的項目存在對應的依賴,將自動開啟配置類,這個是非常實用的一個註解。
- @EnableConfigurationProperties(): 啟用一個@ConfigurationProperties註解
- @Import(RabbitAnnotationDrivenConfiguration.class): 導入了一個基於註解的配置類
在上邊的註解中你可能很疑惑@ConfigurationProperties的作用,這裡我們看看與之對應的RabbitProperties的具體實現。
@ConfigurationProperties(prefix = "spring.rabbitmq")
public class RabbitProperties {
/**
* RabbitMQ host.
*/
private String host = "localhost";
/**
* RabbitMQ port.
*/
private int port = 5672;
/**
* Login user to authenticate to the broker.
*/
private String username = "guest";
...
整體上看,RabbitProperies整體上大概就是一個POJO的類,其中包含了RabbitMQ的各項參數,其中有一些已經設置了初始值,但是直接在類中配置初始值的方式很難讓我們自定義修改這些配置參數,所以在這個類上使用了@ConfigurationProperties()註解。
@ConfigurationProperties(perfix = "spring.rabbitmq")作用是從spring的配置中載入指定首碼的配置,並自動設置到對應的成員變數上。也正是通過這種方式,真正實現了配置的自動註入。
小結
到這裡,我們實際上已經從@EnableAutoConfiguration這個註解入手,分析了整個自動配置的流程。簡單再覆述下上邊的流程:
@EnableAutoConfiguration->AutoConfigurationImportSelector->spring.factories->EnableAutoConfiguration->RabbitAutoConfiguration(具體的配置類)->@EnableConfigurationProperties->RabbitProperties(具體的配置類)->@ConfigurationProperties(prefix = "spring.rabbitmq")
通過上邊的層層載入,我們很容易就可以實現配置的自動註入。這個過程也並不複雜,下一篇將嘗試按照上面的流程,自己實現一個starter類。