@EnableAutoConfiguration自動配置原理分析


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

這個類上邊添加了很多注解,我們一個個來看。

  1. @Configuration: 這個注解我們非常熟悉,標明這就是一個配置類;
  2. @ConditionalOnClass():這個注解是可以輸入幾個class,相當於給這個配置類添加了一個開關,當檢測存在輸入的類時候,該配置類生效,否則將不被實例化。也就是說,如果你的項目存在對應的依賴,將自動開啟配置類,這個是非常實用的一個注解。
  3. @EnableConfigurationProperties(): 啟用一個@ConfigurationProperties注解
  4. @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類。


免責聲明!

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



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