( 八 ) SpringBoot 自动配置原理解析

 ( 八 ) SpringBoot 自动配置原理解析

 

 

1、简介

    我们知道,Spring Boot 项目创建完成后,即使不进行任何的配置,也能够顺利地运行,这都要归功于 Spring Boot 的自动化配置。
Spring Boot 默认使用 application.properties 或 application.yml 作为其全局配置文件,我们可以在该配置文件中对各种自动配置属性(server.port、logging.level.* 、spring.config.active.no-profile 等等)进行修改,并使之生效,那么您有没有想过这些属性是否有据可依呢?答案是肯定的。
Spring Boot 官方文档:常见应用属性 中对所有的配置属性都进行了列举和解释,我们可以根据官方文档对 Spring Boot 进行配置,但 Spring Boot 中的配置属性数量庞大,仅仅依靠官方文档进行配置也十分麻烦。我们只有了解了 Spring Boot 自动配置的原理,才能更加轻松熟练地对 Spirng Boot 进行配置。本节为你揭开 SpringBoot 自动配置的神秘面纱。

 

2、SpringBoot 基于 spring-boot-autoconfigure-2.0.3.RELEASE.jar 进行自动配置

Spring Boot 自动化配置也是基于 Spring Factories 机制实现的,在 spring-boot-autoconfigure-xxx.jar 类路径下的 META-INF/spring.factories 中设置了 Spring Boot 自动配置的内容

Springboot如何自动生成创建时间和更新时间 springboot 自动_spring

 

jar包依赖关系如下:

说明我们创建一个web项目, 就会以引入自动配置的依赖

 

3、Spring Factories 机制

Spring Boot 的自动配置是基于 Spring Factories 机制实现的。
Spring Factories 机制是 Spring Boot 中的一种服务发现机制,这种扩展机制与 Java SPI 机制十分相似。Spring Boot 会自动扫描所有 Jar 包类路径下 META-INF/spring.factories 文件,并读取其中的内容,进行实例化,这种机制也是 Spring Boot Starter 的基础。

spring.factories 

spring.factories 文件本质上与 properties 文件相似,其中包含一组或多组键值对(key=vlaue),其中,key 的取值为接口的完全限定名;value 的取值为接口实现类的完全限定名,一个接口可以设置多个实现类,不同实现类之间使用“,”隔开,例如:

# 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,\

注意:文件中配置的内容过长,为了阅读方便而手动换行时,为了防止内容丢失可以使用“\”。

 

4、Spring Factories 实现原理

spring-core 包里定义了 SpringFactoriesLoader 类,这个类会扫描所有 Jar 包类路径下的 META-INF/spring.factories 文件,并获取指定接口的配置。在 SpringFactoriesLoader 类中定义了两个对外的方法,如下表。

返回值

方法

描述

<T> List<T> 

loadFactories(Class<T> factoryType, @Nullable ClassLoader classLoader)

静态方法;

根据接口获取其实现类的实例;

该方法返回的是实现类对象列表。

List<String>

loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) 

公共静态方法;

根据接口l获取其实现类的名称;

该方法返回的是实现类的类名的列表

以上两个方法的关键都是从指定的 ClassLoader 中获取 spring.factories 文件,并解析得到类名列表,具体代码如下。

loadFactories() 方法能够获取指定接口的实现类对象,具体代码如下:

public static <T> List<T> loadFactories(Class<T> factoryType, @Nullable ClassLoader classLoader) {
    Assert.notNull(factoryType, "'factoryType' must not be null");
    ClassLoader classLoaderToUse = classLoader;
    if (classLoader == null) {
        classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
    }
    // 调用loadFactoryNames获取接口的实现类
    List<String> factoryImplementationNames = loadFactoryNames(factoryType, classLoaderToUse);
    if (logger.isTraceEnabled()) {
        logger.trace("Loaded [" + factoryType.getName() + "] names: " + factoryImplementationNames);
    }
    // 遍历 factoryNames 数组,创建实现类的对象
    List<T> result = new ArrayList(factoryImplementationNames.size());
    Iterator var5 = factoryImplementationNames.iterator();
    //排序
    while(var5.hasNext()) {
        String factoryImplementationName = (String)var5.next();
        result.add(instantiateFactory(factoryImplementationName, factoryType, classLoaderToUse));
    }
    AnnotationAwareOrderComparator.sort(result);
    return result;
}

loadFactoryNames() 方法能够根据接口获取其实现类类名的集合,具体代码如下:

public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
    ClassLoader classLoaderToUse = classLoader;
    if (classLoader == null) {
        classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
    }
    String factoryTypeName = factoryType.getName();
    //获取自动配置类
    return (List)loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList());
}

loadSpringFactories() 方法能够读取该项目中所有 Jar 包类路径下 META-INF/spring.factories 文件的配置内容,并以 Map 集合的形式返回,具体代码如下:

private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {
    Map<String, List<String>> result = (Map)cache.get(classLoader);
    if (result != null) {
        return result;
    } else {
        HashMap result = new HashMap();
    try {
        //扫描所有 Jar 包类路径下的 META-INF/spring.factories 文件
        Enumeration urls = classLoader.getResources("META-INF/spring.factories");
        while(urls.hasMoreElements()) {
                URL url = (URL)urls.nextElement();
                UrlResource resource = new UrlResource(url);
                //将扫描到的 META-INF/spring.factories 文件中内容包装成 properties 对象
                Properties properties = PropertiesLoaderUtils.loadProperties(resource);
                Iterator var6 = properties.entrySet().iterator();
                while(var6.hasNext()) {
                    Map.Entry<?, ?> entry = (Map.Entry)var6.next();
                    //提取 properties 对象中的 key 值
                    String factoryTypeName = ((String)entry.getKey()).trim();
                    //提取 proper 对象中的 value 值(多个类的完全限定名使用逗号连接的字符串)
                    // 使用逗号为分隔符转换为数组,数组内每个元素都是配置类的完全限定名
                    String[] factoryImplementationNames = StringUtils.commaDelimitedListToStringArray((String)entry.getValue());
                    String[] var10 = factoryImplementationNames;
                    int var11 = factoryImplementationNames.length;
                    //遍历配置类数组,并将数组转换为 list 集合
                    for(int var12 = 0; var12 < var11; ++var12) {
                        String factoryImplementationName = var10[var12];
                        ((List)result.computeIfAbsent(factoryTypeName, (key) -> {
                            return new ArrayList();
                        })).add(factoryImplementationName.trim());
                    }
                }
            }
            //将 propertise 对象的 key 与由配置类组成的 List 集合一一对应存入名为 result 的 Map 中
            result.replaceAll((factoryType, implementations) -> {
                return (List)implementations.stream().distinct()
                 .collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList));
            });
            cache.put(classLoader, result);
            //返回 result
            return result;
        } catch (IOException var14) {
            throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", var14);
        }
     }
}

5、@SpringBootApplication 注解

  所有 Spring Boot 项目的主启动程序类上都使用了一个 @SpringBootApplication 注解,该注解是 Spring Boot 中最重要的注解之一 ,也是 Spring Boot 实现自动化配置的关键。 
 @SpringBootApplication 是一个组合元注解,其主要包含两个注解:@SpringBootConfiguration  和  @EnableAutoConfiguration,其中 @EnableAutoConfiguration 注解是 SpringBoot 自动化配置的核心。

  • @EnableAutoConfiguration:启用 SpringBoot 的自动配置机制。
  • @SpringBootConfiguration(实际是:@Configuration):允许在上下文中注册额外的 bean 或导入其他配置类。
  • @ComponentScan: 扫描被@Component (@Service,@Controller)注解的 bean,注解默认会扫描启动类所在的包下所有的类 ,可以自定义不扫描某些 bean。如下图所示,容器中将排除TypeExcludeFilter和AutoConfigurationExcludeFilter。

Springboot如何自动生成创建时间和更新时间 springboot 自动_List_02

@EnableAutoConfiguration 注解

@EnableAutoConfiguration 注解用于开启 Spring Boot 的自动配置功能, 它使用 Spring 框架提供的 @Import 注解通过 AutoConfigurationImportSelector类(选择器)给容器中导入自动配置组件。

Springboot如何自动生成创建时间和更新时间 springboot 自动_Group_03

AutoConfigurationImportSelector 类

AutoConfigurationImportSelector 类实现了 DeferredImportSelector 接口,AutoConfigurationImportSelector 中还包含一个静态内部类 AutoConfigurationGroup,它实现了 DeferredImportSelector 接口的内部接口 Group(Spring 5 新增)。
AutoConfigurationImportSelector 类中包含 3 个方法,如下表:

返回值

方法声明

描述

内部类方法

内部类

Class<? extends Group>

getImportGroup()

该方法获取实现了 Group 接口的类,并实例化


 

void

process(AnnotationMetadata annotationMetadata, DeferredImportSelector deferredImportSelector)

该方法用于引入自动配置的集合


AutoConfigurationGroup

Iterable<Entry>

selectImports()

遍历自动配置类集合(Entry 类型的集合),并逐个解析集合中的配置类


AutoConfigurationGroup

AutoConfigurationImportSelector 内各方法执行顺序如下。

  1. getImportGroup() 方法
  2. process() 方法
  3. selectImports() 方法

下面我们将分别对以上 3 个方法及其调用过程进行介绍。

1. getImportGroup() 方法

AutoConfigurationImportSelector 类中 getImportGroup() 方法主要用于获取实现了 DeferredImportSelector.Group 接口的类,代码如下

public Class<? extends Group> getImportGroup() {
        //获取实现了 DeferredImportSelector.Gorup 接口的 AutoConfigurationImportSelector.AutoConfigurationGroup 类
        return AutoConfigurationImportSelector.AutoConfigurationGroup.class;
    }

 

2. process() 方法

静态内部类 AutoConfigurationGroup 中的核心方法是 process(),该方法通过调用 getAutoConfigurationEntry() 方法读取 spring.factories 文件中的内容,获得自动配置类的集合,代码如下

public void process(AnnotationMetadata annotationMetadata, DeferredImportSelector deferredImportSelector) {
    Assert.state(deferredImportSelector instanceof AutoConfigurationImportSelector, () -> {
        return String.format("Only %s implementations are supported, got %s", 
AutoConfigurationImportSelector.class.getSimpleName(), deferredImportSelector.getClass().getName());
    });
    //拿到 META-INF/spring.factories中的EnableAutoConfiguration,并做排除、过滤处理
    //AutoConfigurationEntry里有需要引入配置类和排除掉的配置类,最终只要返回需要配置的配置类
    AutoConfigurationImportSelector.AutoConfigurationEntry autoConfigurationEntry = 
((AutoConfigurationImportSelector)deferredImportSelector).getAutoConfigurationEntry(annotationMetadata);
    //加入缓存,List<AutoConfigurationEntry>类型
    this.autoConfigurationEntries.add(autoConfigurationEntry);
    Iterator var4 = autoConfigurationEntry.getConfigurations().iterator();
    while(var4.hasNext()) {
        String importClassName = (String)var4.next();
        //加入缓存,Map<String, AnnotationMetadata>类型
        this.entries.putIfAbsent(importClassName, annotationMetadata);
    }
}

 

getAutoConfigurationEntry() 方法通过调用 getCandidateConfigurations() 方法来获取自动配置类的完全限定名,并在经过排除、过滤等处理后,将其缓存到成员变量中,具体代码如下

protected AutoConfigurationImportSelector.AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
    if (!this.isEnabled(annotationMetadata)) {
        return EMPTY_ENTRY;
    } else {
        //获取注解元数据中的属性设置
        AnnotationAttributes attributes = this.getAttributes(annotationMetadata);
        //获取自动配置类
        List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
        //删除list 集合中重复的配置类
        configurations = this.removeDuplicates(configurations);
        //获取飘出导入的配置类
        Set<String> exclusions = this.getExclusions(annotationMetadata, attributes);
        //检查是否还存在排除配置类
        this.checkExcludedClasses(configurations, exclusions);
        //删除排除的配置类
        configurations.removeAll(exclusions);
        //获取过滤器,过滤配置类
        configurations = this.getConfigurationClassFilter().filter(configurations);
        //出发自动化配置导入事件
        this.fireAutoConfigurationImportEvents(configurations, exclusions);
        return new AutoConfigurationImportSelector.AutoConfigurationEntry(configurations, exclusions);
    }
}

    在 getCandidateConfigurations() 方法中,根据 Spring Factories 机制调用 SpringFactoriesLoader 的 loadFactoryNames() 方法,根据 EnableAutoConfiguration.class (自动配置接口)获取其实现类(自动配置类)的类名的集合,如下图。

 

Springboot如何自动生成创建时间和更新时间 springboot 自动_Group_04

3. process() 方法
以上所有方法执行完成后,AutoConfigurationImportSelector.AutoConfigurationGroup#selectImports() 会将 process() 方法处理后得到的自动配置类,进行过滤、排除,最后将所有自动配置类添加到容器中。到这里可能面试官会问你:“spring.factories中这么多配置,每次启动都要全部加载么?”。很明显,这是不现实的,@ConditionalOnXXX 中的所有条件都满足,该类才会生效。

6、条件注解使自动配置的生效和修改

spring.factories 文件中的所有自动配置类(xxxAutoConfiguration),都是必须在一定的条件下才会作为组件添加到容器中,配置的内容才会生效。这些限制条件在 Spring Boot 中以 @Conditional 派生注解的形式体现,如下表

注解

生效条件

@ConditionalOnJava

应用使用指定的 Java 版本时生效

@ConditionalOnBean

容器中存在指定的  Bean 时生效

@ConditionalOnMissingBean 

容器中不存在指定的 Bean 时生效

@ConditionalOnExpression

满足指定的 SpEL 表达式时生效

@ConditionalOnClass

存在指定的类时生效

@ConditionalOnMissingClass

不存在指定的类时生效

@ConditionalOnSingleCandidate 

容器中只存在一个指定的 Bean 或这个 Bean 为首选 Bean 时生效

@ConditionalOnProperty

系统中指定属性存在指定的值时生效

@ConditionalOnResource

类路径下存在指定的资源文件时生效

@ConditionalOnWebApplication

当前应用是 web 应用时生效

@ConditionalOnNotWebApplication

当前应用不是 web 应用生效