springboot自动配置原理

概述

使用SpringBoot时,我们只需引人对应的Starters,SpringBoot启动时便会自动加载相关依赖,配置相应的初始化参数,以最快捷、简单的形式对第三方软件进行集成,这便是SpringBoot的自动配置功能。先从整体上看一下SpringBoot实现该运作机制涉及的核心部分,如图所示:

在这里插入图片描述

上图描述了SpringBoot自动配置功能运作过程中涉及的几个核心功能及其相互之间的关系包括@EnableAutoConfiguration、spring.factories、各组件对应的AutoConfiguration类、@Conditional注解以及各种Starters

可以用一句话来描述整个过程:SpringBoot通过@EnableAutoConfiguration注解开启自动配置,加载spring.factories中注册的各种AutoConfiguration类,当某个AutoConfiguration类满足其注解@Conditional指定的生效条件(Starters提供的依赖、配置或Spring容器中是否存在某个Bean等)时,实例化该AutoConfiguration类中定义的Bean(组件等),并注入Spring容器,就可以完成依赖框架的自动配置。

以下是对核心注解、类的描述:

  • @EnableAutoConfiguration:该注解由组合注解@SpringBootApplication引入,完成自动配置开启,扫描各个jar包下的spring.factories文件,并加载文件中注册的AutoConfiguration类等。
  • spring.factories:配置文件,位于jar包的META-INF目录下,按照指定格式注册了自动配置的AutoConfiguration类。spring.factories也可以包含其他类型待注册的类。该配置文件不仅存在于SpringBoot项目中,也可以存在于自定义的自动配置(或Starter)项目中。
  • AutoConfiguration类:自动配置类,代表了SpringBoot中一类以XXAutoConfiguration命名的自动配置类。其中定义了三方组件集成Spring所需初始化的Bean和条件。
  • @Conditional:条件注解及其衍生注解,在AutoConfiguration类上使用,当满足该条件注解时才会实例化AutoConfiguration类。
  • Starters:第三方组件的依赖及配置,SpringBoot已经预置的组件。SpringBoot默认的Starters项目往往只包含了一个pom依赖的项目。如果是自定义的starter,该项目还需包含spring.factories文件、AutoConfiguration类和其他配置类。

@EnableAutoConfiguration源码解析

@EnableAutoConfiguration是开启自动配置的注解,在创建的SpringBoot项目中并不能直接看到此注解,它是由组合注解@SpringBootApplication引入的。

入口类和@SpringBootApplication注解

SpringBoot项目创建完成会默认生成一个*Application的入口类。通过main方法即可启动该项目。

在SpringBoott人口类中,唯一的一个注解就是@SpringBootApplication。它是SpringBoot项目的核心注解,用于开启自动配置,准确说是通过该注解内组合的@EnableAutoConfiguration开启了自动配置。



@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(
    excludeFilters = {@Filter(
    type = FilterType.CUSTOM,
    classes = {TypeExcludeFilter.class}
), @Filter(
    type = FilterType.CUSTOM,
    classes = {AutoConfigurationExcludeFilter.class}
)}
)
public @interface SpringBootApplication {
    @AliasFor(
        annotation = EnableAutoConfiguration.class
    )
    // 排除指定自动配置类
    Class<?>[] exclude() default {};

    @AliasFor(
        annotation = EnableAutoConfiguration.class
    )
    // 排除指定自动配置类名
    String[] excludeName() default {};

    @AliasFor(
        annotation = ComponentScan.class,
        attribute = "basePackages"
    )
    // 指定扫描的包路径
    String[] scanBasePackages() default {};

    @AliasFor(
        annotation = ComponentScan.class,
        attribute = "basePackageClasses"
    )
    // 指定扫描的类
    Class<?>[] scanBasePackageClasses() default {};
}

  • exclude:根据类(Class)排除指定的自动配置,该成员属性覆盖了@SpringBootApplication中组合的@EnableAutoConfiguration中定义的exclude成员属性。
  • excludeName:根据类名排除指定的自动配置,覆盖了@EnableAutoConfiguration中的excludeName的成员属性。
  • scanBasePackages:指定扫描的基础package,用于激活@Component等注解类的初始化。
  • scanBasePackageClasses:扫描指定的类,用于组件的初始化

SpringBoot中大量使用了@AliasFor注解,该注解用于桥接到其他注解,该注解的属性中指定了所桥接的注解类。如果点进去查看,会发现@SpringBootApplication定义的属性在其他注解中已经定义过了。之所以使用@AliasFor注解并重新在@SpringBootApplication中定义,更多是为了减少用户使用多注解带来的麻烦。

@SpringBootApplication注解中组合了@SpringBootConfiguration、@EnableAutoConfiguration和@ComponentScan。因此,在实践过程中也可以使用这3个注解来替代@SpringBootApplication

@SpringBootApplication除了组合元注解之外,其核心作用还包括:激活SpringBoot自动配置的@EnableAutoConfiguration、激活@Component扫描的@ComponentScan、激活配置类的@Configuratione

在这里插入图片描述

注解@EnableAutoConfiguration功能解析

在未使用SpringBoot的情况下,Bean的生命周期由Spring来管理,然而Spring无法自动配置@Configuration注解的类。而SpringBoot的核心功能之一就是根据约定自动管理该注解标注的类。用来实现该功能的组件之一便是@EnableAutoConfiguration注解。

@EnableAutoConfiguration位于spring-boot-autoconfigure包内,当使用@SpringBootApplication注解时,@EnableAutoConfiguration注解会自动生效。

@EnableAutoConfiguration的主要功能是启动Spring应用程序上下文时进行自动配置,它会尝试猜测并配置项目可能需要的Bean。自动配置通常是基于项目classpath中引人的类和已定义的Bean来实现的。在此过程中,被自动配置的组件来自项目自身和项目依赖的jar包中。


@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import({AutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {
// 用来覆盖配置启动|关闭自动配置功能
    String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
    //  根据类排除指定的自动配置
    Class<?>[] exclude() default {};
    //  根据类名排除指定的自动配置
    String[] excludeName() default {};
}

@EnableAutoConfiguration注解提供了一个常量和两个成员参数的定义。

  • ENABLED_OVERRIDE_PROPERTY:用来覆盖开启/关闭自动配置的功能
  • exclude:根据类(Class)排除指定的自动配置。
  • excludeName:根据类名排除指定的自动配置。

@EnableAutoConfiguration会猜测你需要使用的Bean,但如果在实战中你并不需要它预置初始化的Bean,可通过该注解的exclude或excludeName参数进行有针对性的排除。

需要注意的是,被@EnableAutoConfiguration注解的类所在package还具有特定的意义,通常会被作为扫描注解@Entity的根路径。这也是在使用@SpringBootApplication注解时需要将被注解的类放在顶级package下的原因,如果放在较低层级,它所在package的同级或上级中的类就无法被扫描到。

而对于入口类和其main方法来说,并不依赖@SpringBootApplication注解或@EnableAutoConfiguration注解,也就是说该注解可以使用在其他类上,而非入口类上。

AutoConfigurationImportSelector源码解析

@EnableAutoConfiguration的关键功能是通过@Import注解导入的ImportSelector来完成的。从源代码得知@lmport(AutoConfigurationImportSelector.class)是@EnableAutoConfiguration注解的组成部分,也是自动配置功能的核心实现者。@Import(AutoConfigurationlmportSelector.class)又可以分为两部分:@Import和对应的ImportSelector。

@Import注解

@Import的作用和xml配置中标签的作用一样,我们可以通过t@lmport引入@Configuration注解的类,也可以导入实现了ImportSelector或ImportBeanDefinitionRegistrar的类,还可以通过@Import导入普通的POJO。

ImportSelector接口

@Import的许多功能都需要借助接口ImportSelector来实现,ImportSelector决定可引入哪些@Configuration。


public interface ImportSelector {
    String[] selectImports(AnnotationMetadata var1);
}

ImportSelector接口只提供了一个参数为AnnotationMetadata的方法,返回的结果为一个字符串数组。其中参数AnnotationMetadata内包含了被@Import注解的类的注解信息。在selectlmports方法内可根据具体实现决定返回哪些配置类的全限定名,将结果以字符申数组的形式返回。

如果实现了接口ImportSelector的类的同时又实现了以下4个Aware接口,那么Spring保证在调用ImportSelector之前会先调用Aware接口的方法。这4个接口为:EnvironmentAware、BeanFactoryAwareBeanClassLoaderAware 和ResourceLoaderAware

AutoConfigurationlmportSelector并没有直接实现ImportSelector接口,而是实现了它的子接口DeferredlmportSelector。DeferredlmportSelector接口与ImportSelector的区别是,前者会在所有的@Configuration类加载完成之后再加载返回的配置类,而ImportSelector是在加载完@Configuration类之前先去加载返回的配置类。

DeferredImportSelector的加载顺序可以通过@Order注解或实现Ordered接口来指定。同时,DeferredlmportSelector提供了新的方法getlmportGroupO来跨DeferredImportSelector实现自定义Configuration的加载顺序。

AutoConfigurationlmportSelector功能概述

在这里插入图片描述

当AutoConfigurationlmportSelector被@Import注解引人之后,它的selectlmports方法会被调用并执行其实现的自动装配逻辑,selectlmports方法几乎涵盖了组件自动装配的所有处理逻辑。AutoConfigurationlmportSelector的selectlmports方法源代码如下:


public String[] selectImports(AnnotationMetadata annotationMetadata) {
    // 检查自动装配是否开启,默认开启
        if (!this.isEnabled(annotationMetadata)) {
            return NO_IMPORTS;
        } else {
    // 加载自动装配元信息
            AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);
 // 封装将被引入的自动装配信息           AutoConfigurationImportSelector.AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(autoConfigurationMetadata, annotationMetadata);
 // 返回符合条件的配置类的全限定名数组
            return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
        }
    }

protected AutoConfigurationImportSelector.AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata, AnnotationMetadata annotationMetadata) {
        if (!this.isEnabled(annotationMetadata)) {
            return EMPTY_ENTRY;
        } else {
            AnnotationAttributes attributes = this.getAttributes(annotationMetadata);
            List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
            configurations = this.removeDuplicates(configurations);
            Set<String> exclusions = this.getExclusions(annotationMetadata, attributes);
            this.checkExcludedClasses(configurations, exclusions);
            configurations.removeAll(exclusions);
            configurations = this.filter(configurations, autoConfigurationMetadata);
            this.fireAutoConfigurationImportEvents(configurations, exclusions);
            return new AutoConfigurationImportSelector.AutoConfigurationEntry(configurations, exclusions);
        }
    }



自动配置开关

检查自动配置是否开启的代码位于AutoConfigurationImportSelector 的jselectlmports方法第一段中。如果开启自动配置功能,就继续执行后续操作;如果未开启,就返回空数组。


public String[] selectImports(AnnotationMetadata annotationMetadata) {
        if (!this.isEnabled(annotationMetadata)) {
            return NO_IMPORTS;
        } else {
            AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);
            AutoConfigurationImportSelector.AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(autoConfigurationMetadata, annotationMetadata);
            return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
        }
    }

 protected boolean isEnabled(AnnotationMetadata metadata) {
        return this.getClass() == AutoConfigurationImportSelector.class ? (Boolean)this.getEnvironment().getProperty("spring.boot.enableautoconfiguration", Boolean.class, true) : true;
    }

通过isEnabled方法可以看出,如果当前类为AutoConfigurationlmportSelector,程序会从环境中获取key为EnableAutoConfiguration.ENABLEDOVERRIDEPROPERTY的配置,该常量的值为spring.boot.enableautoconfiguration。如果获取不到该属性的配置,isEnabled默认为tue,也就是默认会使用自动配置。如果当前类为其他类,直接返回trues

如果想覆盖或重置EnableAutoConfiguration.ENABLEDOVERRIDEPROPERTY的配置,可获取该常量的值,并在applicationproperties或application.yml中针对此参数进行配置。

加载元数据配置


public static AutoConfigurationMetadata loadMetadata(ClassLoader classLoader) {
        return loadMetadata(classLoader, "META-INF/spring-autoconfigure-metadata.properties");
    }

加载元数据配置主要是为后续操作提供数据支持。加载元数据的配置用到了AutoConfigurationMetadataLoader类提供的loadMetaData方法,该方法会默认加载类路径下META-INF/spring-autoconfigure-metadata.properties内的配置。

AutoConfigurationMetadataLoader 调用loadMetadata(ClassLoader classLoader)方法,会获取默认变量PATH指定的文件,然后加载并存储于Enumeration数据结构中。随后,从变量PATH指定的文件中获取其中配置的属性存储于Properties内,最终调用在该类内部实现的AutoConfigurationMetadata的子类的构造方法。

加载元数据主要是为了后续过滤自动配置使用。SpringBoot使用一个Annotation的处理器来收集自动加载的条件,这些条件可以在元数据文件中进行配置。SpringBoot会将收集好的@Configuration进行一次过滤,进而剔除不满足条件的配置类。

加载自动配置组件

加载自动配置组件是自动配置的核心组件之一,这些自动配置组件在类路径中META-INF目录下的spring.factories文件中进行注册。SpringBoot预置了一部分常用组件,如果我们需要创建自己的组件。

通过Spring Core提供的SpringFactoriesLoader类可以读取spring.factories文件中注册的类。下面我们通过源代码来看一下如何在AutoConfigurationlmportSelector类中通过getCandidateConfigurations方法来读取spring.factories文件中注册的类。

getCandidateConfigurations方法使用SpringFactoriesLoader类提供的loadFactoryNames方法来读取META-INF/spring.factories中的配置。如果程序未读取到任何配置内容,会抛出异常信息。而loadFactoryNames方法的第一个参数为getSpringFactoriesLoaderFactoryClass方法返回的EnableAutoConfiguration.class,也就是说loadFactoryNames只会读取配置文件中针对自动配置的注册类。

SpringFactoriesLoader加载器加载指定ClassLoader下面的所有META-INF/spring.factories文件,并将文件解析内容存于Map<String,List内。然后,通过loadFactoryNames传递过来的class的名称从Map中获得该类的配置列表。

我们继续以EnableAutoConfiguration的配置为例,Map<String,List>内存储的对应数据就是key值为org.springframework.boot.autoconfigure.EnableAutoConfiguration,Value值为其等号后面以分号分割的各种AutoConfiguration类。

当然,spring.factories文件内还有其他的配置,比如用于监听的Listeners和用于过滤的Filters等。很显然,在加载自动配置组件时,此方法只用到了EnableAutoConfiguration对应的配置。

因为程序默认加载的是ClassLoader下面的所有META-INF/spring.factories文件中的配置,所以难免在不同的jar包中出现重复的配置。我们可以在源代码中使用Set集合数据不可重复的特性进行去重操作。

排除指定组件

获得了spring.factories文件中注册的自动加载组件,但如果在实际应用的过程中并不需要其中的某个或某些组件,可通过配置@EnableAutoConfiguration的注解属性exclude或excludeName进行有针对性的排除,当然也可以通过配置文件进行排除。


protected Set<String> getExclusions(AnnotationMetadata metadata, AnnotationAttributes attributes) {
        Set<String> excluded = new LinkedHashSet();
        excluded.addAll(this.asList(attributes, "exclude"));
        excluded.addAll(Arrays.asList(attributes.getStringArray("excludeName")));
        excluded.addAll(this.getExcludeAutoConfigurationsProperty());
        return excluded;
    }

private List<String> getExcludeAutoConfigurationsProperty() {
        if (this.getEnvironment() instanceof ConfigurableEnvironment) {
            Binder binder = Binder.get(this.getEnvironment());
            return (List)binder.bind("spring.autoconfigure.exclude", String[].class).map(Arrays::asList).orElse(Collections.emptyList());
        } else {
            String[] excludes = (String[])this.getEnvironment().getProperty("spring.autoconfigure.exclude", String[].class);
            return excludes != null ? Arrays.asList(excludes) : Collections.emptyList();
        }
    }

AutoConfigurationImportSelector中通过调用getExclusions方法来获取被排除类的集合。它会收集@EnableAutoConfiguration注解中配置的exclude属性值、excludeName属性值,并通过方法getExcludeAutoConfigurationsProperty获取在配置文件中key为spring.autoconfigure.exclude的配置值。获取到被排除组件的集合之后,首先是对待排除类进行检查操作。

checkExcludedClasses方法用来确保被排除的类存在于当前的ClassLoader中,并且包含在spring.factories注册的集合中。如果不满足以上条件,调用handlelnvalidExcludes方法抛出异常。

如果被排除类都符合条件,调用configurations.removeAll(exclusions)方法从自动配置集合中移除被排除集合的类,至此完成初步的自动配置组件排除。

过滤自动配置组件

当完成初步的自动配置组件排除工作之后,AutoConfigurationImportSelector会结合在此之前获取的AutoConfigurationMetadata对象,对组件进行再次过滤。


private List<String> filter(List<String> configurations, AutoConfigurationMetadata autoConfigurationMetadata) {
        long startTime = System.nanoTime();
        String[] candidates = StringUtils.toStringArray(configurations);
        boolean[] skip = new boolean[candidates.length];
        boolean skipped = false;
        Iterator var8 = this.getAutoConfigurationImportFilters().iterator();

        while(var8.hasNext()) {
            AutoConfigurationImportFilter filter = (AutoConfigurationImportFilter)var8.next();
            this.invokeAwareMethods(filter);
            boolean[] match = filter.match(candidates, autoConfigurationMetadata);

            for(int i = 0; i < match.length; ++i) {
                if (!match[i]) {
                    skip[i] = true;
                    candidates[i] = null;
                    skipped = true;
                }
            }
        }

        if (!skipped) {
            return configurations;
        } else {
            List<String> result = new ArrayList(candidates.length);

            int numberFiltered;
            for(numberFiltered = 0; numberFiltered < candidates.length; ++numberFiltered) {
                if (!skip[numberFiltered]) {
                    result.add(candidates[numberFiltered]);
                }
            }

            if (logger.isTraceEnabled()) {
                numberFiltered = configurations.size() - result.size();
                logger.trace("Filtered " + numberFiltered + " auto configuration class in " + TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime) + " ms");
            }

            return new ArrayList(result);
        }
    }


protected List<AutoConfigurationImportFilter> getAutoConfigurationImportFilters() {
        return SpringFactoriesLoader.loadFactories(AutoConfigurationImportFilter.class, this.beanClassLoader);
    }

  • configurations:List,经过初次过滤之后的自动配置组件列表。
  • autoConfigurationMetadata:AutoConfigurationMetadata,元数据文件META-INF/spring-autoconfigure-metadataproperties
    中配置对应实体类。
  • List:META-INF/spring.factories中配置key为AutoConfigurationlmportFilter的Filters列表。

getAutoConfigurationlmportFilters 方法是通过SpringFactoriesLoader的loadFactories方法将META-INF/spring.factories中配置key为AutoConfigurationlmportFilter的值进行加载。

在spring-boot-autoconfigure中默认配置了3个筛选条件,OnBeanCondition、OnClassCondition和OnWebApplicationCondition,它们均实现了AutoConfigurationImportFilter接口

filter方法就是对自动配置组件列表进行再次过滤,过滤条件为该列表中自动配置类的注解得包含在OnBeanCondition、COnClassCondition和OnWebApplicationCondition中指定的注解,依次包含@ConditionalOnBean、@ConditionalOnClass 和@ConditionalOnWebApplication

事件注册

获得了要进行自动配置的类的集合,在将该集合返回之前,在AutoConfigurationImportSelector类中完成的最后一步操作就是相关事件的封装和广播。


 private void fireAutoConfigurationImportEvents(List<String> configurations, Set<String> exclusions) {
        List<AutoConfigurationImportListener> listeners = this.getAutoConfigurationImportListeners();
        if (!listeners.isEmpty()) {
            AutoConfigurationImportEvent event = new AutoConfigurationImportEvent(this, configurations, exclusions);
            Iterator var5 = listeners.iterator();

            while(var5.hasNext()) {
                AutoConfigurationImportListener listener = (AutoConfigurationImportListener)var5.next();
                this.invokeAwareMethods(listener);
                listener.onAutoConfigurationImportEvent(event);
            }
        }

    }

protected List<AutoConfigurationImportListener> getAutoConfigurationImportListeners() {
        return SpringFactoriesLoader.loadFactories(AutoConfigurationImportListener.class, this.beanClassLoader);
    }

首先通过SpringFactoriesLoader类提供的loadFactories方法将spring.factories中配置的接口AutoConfigurationlmportListenerf的实现类加载出来。然后,将筛选出的自动配置类集合和被排除的自动配置类集合封装成AutoConfigurationImportEvent事件对象,并传入该事件对象通过监听器提供的onAutoConfigurationlmportEvent方法,最后进行事件广播。

何时触发@EnableAutoConfiguration

我们知道了springboot自动配置的核心入口在@EnableAutoConfiguration注解,那么是什么时候,在哪里去触发、执行这个注解的?在springboot启动时,run方法内会构建AnnotationConfigApplicationContext,AnnotationConfigApplicationContext在执行refresh时会解析@EnableAutoConfiguration注解,并执行自动配置流程。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值