@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类。

posted @ 2019-08-04 23:08  NinWoo  阅读(...)  评论(... 编辑 收藏