0


【SpringBoot源码剥析】| 自动配置原理

目录

1. 概述

本文,我们来分享 Spring Boot 自动配置的实现源码。在故事的开始,我们先来说两个事情:

  • 自动配置和自动装配的区别?
  • Spring Boot 配置的原理

2. 自动配置 VS 自动装配

在这篇文章的开始,狮子是有点混淆自动配置和自动装配的概念,后来经过 Google 之后,发现两者是截然不如同的:

  • 自动配置:是 SpringBoot 提供的,实现通过 jar 包的依赖,能够自动配置应用程序。例如说:我们引入 spring-boot-starter-web 之后,就自动引入了 Spring MVC 相关的 jar 包,从而自动配置 Spring MVC 。
  • 自动装配:是 Spring 提供的 IOC 注入方式,具体看看 《Spring源码剥析 —— Beans 自动装配》文档。

3. 自动装配原理

可以查看下《Spring Boot的自动装配原理》

4. @SpringBootApplication

image-20230419151852631

org.springframework.boot.autoconfigure.@SpringBootApplication

注解,基本我们的 Spring Boot 应用,一定会去有这样一个注解。并且,通过使用它,不仅仅能标记这是一个 Spring Boot 应用,而且能够开启自动配置的功能。这是为什么呢?

tips:

@SpringBootApplication

注解,它在

spring-boot-autoconfigure

模块中。所以,我们使用 Spring Boot 项目时,如果不想使用自动配置功能,就不用引入它。当然,我们貌似不太会存在这样的需求,是吧~

@SpringBootApplication

是一个组合注解。代码如下:

/**
 * Indicates a {@link Configuration configuration} class that declares one or more
 * {@link Bean @Bean} methods and also triggers {@link EnableAutoConfiguration
 * auto-configuration} and {@link ComponentScan component scanning}. This is a convenience
 * annotation that is equivalent to declaring {@code @Configuration},
 * {@code @EnableAutoConfiguration} and {@code @ComponentScan}.
 *
 * @author Phillip Webb
 * @author Stephane Nicoll
 * @author Andy Wilkinson
 * @since 1.2.0
 */@Target(ElementType.TYPE)// 注解的适用范围,Type表示注解可以描述在类、接口、注解或枚举中@Retention(RetentionPolicy.RUNTIME)// 表示注解的生命周期,Runtime运行时@Documented// 表示注解可以记录在javadoc中@Inherited// 表示可以被子类继承该注解// ----------------------上面是源注解,下面是组合注解(重点)-----------------------------@SpringBootConfiguration// 标明该类为配置类@EnableAutoConfiguration// 标明该类为配置类@ComponentScan(excludeFilters ={@Filter(type =FilterType.CUSTOM, classes =TypeExcludeFilter.class),@Filter(type =FilterType.CUSTOM, classes =AutoConfigurationExcludeFilter.class)})//注解扫描public@interfaceSpringBootApplication{//源码/**
     * Exclude specific auto-configuration classes such that they will never be applied.
     * @return the classes to exclude
     */@AliasFor(annotation =EnableAutoConfiguration.class)Class<?>[]exclude()default{};/**
     * Exclude specific auto-configuration class names such that they will never be
     * applied.
     * @return the class names to exclude
     * @since 1.3.0
     */@AliasFor(annotation =EnableAutoConfiguration.class)String[]excludeName()default{};/**
     * Base packages to scan for annotated components. Use {@link #scanBasePackageClasses}
     * for a type-safe alternative to String-based package names.
     * <p>
     * <strong>Note:</strong> this setting is an alias for
     * {@link ComponentScan @ComponentScan} only. It has no effect on {@code @Entity}
     * scanning or Spring Data {@link Repository} scanning. For those you should add
     * {@link org.springframework.boot.autoconfigure.domain.EntityScan @EntityScan} and
     * {@code @Enable...Repositories} annotations.
     * @return base packages to scan
     * @since 1.3.0
     */@AliasFor(annotation =ComponentScan.class, attribute ="basePackages")String[]scanBasePackages()default{};/**
     * Type-safe alternative to {@link #scanBasePackages} for specifying the packages to
     * scan for annotated components. The package of each class specified will be scanned.
     * <p>
     * Consider creating a special no-op marker class or interface in each package that
     * serves no purpose other than being referenced by this attribute.
     * <p>
     * <strong>Note:</strong> this setting is an alias for
     * {@link ComponentScan @ComponentScan} only. It has no effect on {@code @Entity}
     * scanning or Spring Data {@link Repository} scanning. For those you should add
     * {@link org.springframework.boot.autoconfigure.domain.EntityScan @EntityScan} and
     * {@code @Enable...Repositories} annotations.
     * @return base packages to scan
     * @since 1.3.0
     */@AliasFor(annotation =ComponentScan.class, attribute ="basePackageClasses")Class<?>[]scanBasePackageClasses()default{};/**
     * Specify whether {@link Bean @Bean} methods should get proxied in order to enforce
     * bean lifecycle behavior, e.g. to return shared singleton bean instances even in
     * case of direct {@code @Bean} method calls in user code. This feature requires
     * method interception, implemented through a runtime-generated CGLIB subclass which
     * comes with limitations such as the configuration class and its methods not being
     * allowed to declare {@code final}.
     * <p>
     * The default is {@code true}, allowing for 'inter-bean references' within the
     * configuration class as well as for external calls to this configuration's
     * {@code @Bean} methods, e.g. from another configuration class. If this is not needed
     * since each of this particular configuration's {@code @Bean} methods is
     * self-contained and designed as a plain factory method for container use, switch
     * this flag to {@code false} in order to avoid CGLIB subclass processing.
     * <p>
     * Turning off bean method interception effectively processes {@code @Bean} methods
     * individually like when declared on non-{@code @Configuration} classes, a.k.a.
     * "@Bean Lite Mode" (see {@link Bean @Bean's javadoc}). It is therefore behaviorally
     * equivalent to removing the {@code @Configuration} stereotype.
     * @since 2.2
     * @return whether to proxy {@code @Bean} methods
     */@AliasFor(annotation =Configuration.class)booleanproxyBeanMethods()defaulttrue;}

下面,我们来逐个看

@SpringBootApplication

上的每个注解。

4.1 @Inherited

Java 自带的注解。

java.lang.annotation.@Inherited

注解,使用此注解声明出来的自定义注解,在使用自定义注解时,如果注解在类上面时,子类会自动继承此注解,否则的话,子类不会继承此注解。

这里一定要记住,使用

@Inherited

声明出来的注解,只有在类上使用时才会有效,对方法,属性等其他无效。

4.2 @SpringBootConfiguration

Spring Boot 自定义的注解

org.springframework.boot.@SpringBootConfiguration

注解,标记这是一个 Spring Boot 配置类。代码如下:

@Target(ElementType.TYPE)@Retention(RetentionPolicy.RUNTIME)@Documented//    ------------------上面是源注解,下面是组合注解---------------@Configurationpublic@interfaceSpringBootConfiguration{}
  • 可以看到,它上面继承自 @Configuration 注解,所以两者功能也一致,可以将当前类内声明的一个或多个以 @Bean 注解标记的方法的实例纳入到 Spring 容器中,并且实例名就是方法名。

4.3 @ComponentScan

Spring 自定义的注解

org.springframework.context.annotation.@ComponentScan

注解,扫描指定路径下的 Component(

@Componment

@Configuration

@Service

等等)。

4.4 @EnableAutoConfiguration

Spring Boot 自定义的注解

org.springframework.boot.autoconfigure.@EnableAutoConfiguration

注解,用于开启自动配置功能,是

spring-boot-autoconfigure

项目最核心的注解。代码如下:

@Target(ElementType.TYPE)@Retention(RetentionPolicy.RUNTIME)@Documented@Inherited// ----------------------上面是源注解,下面是组合注解(重点)-----------------------------@AutoConfigurationPackage// 自动配置包@Import(AutoConfigurationImportSelector.class)// Spring的底层注解@Import,给容器中导入一个组件:public@interfaceEnableAutoConfiguration{String ENABLED_OVERRIDE_PROPERTY ="spring.boot.enableautoconfiguration";/**
     * Exclude specific auto-configuration classes such that they will never be applied.
     * @return the classes to exclude
     */Class<?>[]exclude()default{};/**
     * Exclude specific auto-configuration class names such that they will never be
     * applied.
     * @return the class names to exclude
     * @since 1.3.0
     */String[]excludeName()default{};}
  • org.springframework.boot.autoconfigure.@AutoConfigurationPackage 注解,主要功能自动配置包,它会获取主程序类所在的包路径,并将包路径(包括子包)下的所有组件注册到 Spring IOC 容器中。代码如下:@Target(ElementType.TYPE)@Retention(RetentionPolicy.RUNTIME)@Documented@Inherited// spring的底层注解@Import,给容器中导入一个组件;// 导入的组件是AuGoConfigurationPackages.Registrar.class@Import(AutoConfigurationPackages.Registrar.class)public@interfaceAutoConfigurationPackage{}- org.springframework.context.annotation.@Import 注解,可用于资源的导入。- AutoConfigurationPackages.Registrar ,有点神奇,这里先不说。最后再去看「6. AutoConfigurationPackages」 小节。
  • @Import(AutoConfigurationImportSelector.class) 注解部分,是重头戏的开始。- org.springframework.context.annotation.@Import 注解,可用于资源的导入。- AutoConfigurationImportSelector ,导入自动配置相关的资源。

5. AutoConfigurationImportSelector

org.springframework.boot.autoconfigure.AutoConfigurationImportSelector

,实现 DeferredImportSelector、BeanClassLoaderAware、ResourceLoaderAware、BeanFactoryAware、EnvironmentAware、Ordered 接口,处理

@EnableAutoConfiguration

注解的资源导入。

5.1 getCandidateConfigurations

getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes)

方法,获得符合条件的配置类的数组。代码如下:

protectedList<String>getCandidateConfigurations(AnnotationMetadata metadata,AnnotationAttributes attributes){// <1> 加载指定类型 EnableAutoConfiguration 对应的,在 `META-INF/spring.factories` 里的类名的数组List<String> configurations =SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),getBeanClassLoader());// 断言,非空Assert.notEmpty(configurations,"No auto configuration classes found in META-INF/spring.factories. If you "+"are using a custom packaging, make sure that file is correct.");return configurations;}
  • <1> 处,调用 getSpringFactoriesLoaderFactoryClass() 方法,获得要从 META-INF/spring.factories 加载的指定类型为 EnableAutoConfiguration 类。代码如下:protectedClass<?>getSpringFactoriesLoaderFactoryClass(){returnEnableAutoConfiguration.class;}- <1> 处,调用 SpringFactoriesLoader.loadFactoryNames(Class<?> factoryClass, ClassLoader classLoader) 方法,加载指定类型 EnableAutoConfiguration 对应的,在 META-INF/spring.factories 里的类名的数组。看看下图,相信就明白了: configurations

一般来说,和网络上 Spring Boot 敢于这块的源码解析,我们就可以结束了。如果单纯是为了了解原理 Spring Boot 自动配置的原理,这里结束也是没问题的。因为,拿到 Configuration 配置类后,后面的就是 Spring Java Config 的事情了。

😜 但是(“但是”同学,你赶紧坐下),具有倒腾精神的🦁,觉得还是继续瞅瞅

getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes)

方法是怎么被调用的。所以,我们来看看调用它的方法调用链,如下图所示:

调用链
  • ① 处、 refresh 方法的调用,我们在 《【SpringBoot源码剥析】 —— SpringApplication》 中,SpringApplication 启动时,会调用到该方法。
  • ② 处、getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) 方法被调用。
  • ③ 处、那么此处,就是问题的关键。代码如下:privatefinalDeferredImportSelector.Group group;publicIterable<Group.Entry>getImports(){for(DeferredImportSelectorHolder deferredImport :this.deferredImports){this.group.process(deferredImport.getConfigurationClass().getMetadata(), deferredImport.getImportSelector());// <1>}returnthis.group.selectImports();// <2>}- <1> 处,调用 DeferredImportSelector.Group#process(AnnotationMetadata metadata, DeferredImportSelector selector) 方法,处理被 @Import 注解的注解。- <2>处,调用DeferredImportSelector.Group#this.group.selectImports()方法,选择需要导入的。例如:selectImportsselectImports- 这里,我们可以看到需要导入的 Configuration 配置类。- 具体 <1><2> 处,在 「5.3 AutoConfigurationGroup」 详细解析。

5.2 getImportGroup

getImportGroup()

方法,获得对应的 Group 实现类。代码如下:

@Override// 实现自 DeferredImportSelector 接口publicClass<?extendsGroup>getImportGroup(){returnAutoConfigurationGroup.class;}
  • 关于 AutoConfigurationGroup 类,在 「5.3 AutoConfigurationGroup」 详细解析。

5.3 AutoConfigurationGroup

AutoConfigurationGroup ,是 AutoConfigurationImportSelector 的内部类,实现 DeferredImportSelector.Group、BeanClassLoaderAware、BeanFactoryAware、ResourceLoaderAware 接口,自动配置的 Group 实现类。

5.3.1 属性

/**
 * AnnotationMetadata 的映射
 *
 * KEY:配置类的全类名
 */privatefinalMap<String,AnnotationMetadata> entries =newLinkedHashMap<>();/**
 * AutoConfigurationEntry 的数组
 */privatefinalList<AutoConfigurationEntry> autoConfigurationEntries =newArrayList<>();privateClassLoader beanClassLoader;privateBeanFactory beanFactory;privateResourceLoader resourceLoader;/**
 * 自动配置的元数据
 */privateAutoConfigurationMetadata autoConfigurationMetadata;
  • entries 属性,AnnotationMetadata 的映射。其中,KEY 为 配置类的全类名。在后续我们将看到的 AutoConfigurationGroup.process(...) 方法中,被进行赋值。例如: entries
  • autoConfigurationEntries 属性,AutoConfigurationEntry 的数组。- 其中,AutoConfigurationEntry 是 AutoConfigurationImportSelector 的内部类,自动配置的条目。代码如下:protectedstaticclassAutoConfigurationEntry{/** * 配置类的全类名的数组 */privatefinalList<String> configurations;/** * 排除的配置类的全类名的数组 */privatefinalSet<String> exclusions;// 省略构造方法和 setting/getting 方法}- 属性比较简单。- 在后续我们将看到的 AutoConfigurationGroup.process(...) 方法中,被进行赋值。例如:autoConfigurationEntries
  • autoConfigurationMetadata 属性,自动配置的元数据(Metadata)。- 通过 getAutoConfigurationMetadata() 方法,会初始化该属性。代码如下:privateAutoConfigurationMetadatagetAutoConfigurationMetadata(){// 不存在,则进行加载if(this.autoConfigurationMetadata ==null){this.autoConfigurationMetadata =AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);}// 存在,则直接返回returnthis.autoConfigurationMetadata;}- 关于 AutoConfigurationMetadataLoader 类,我们先不去愁。避免,我们调试的太过深入。TODO 后续在补充下。- 返回的类型是 PropertiesAutoConfigurationMetadata ,比较简单- 如下是一个返回值的示例:autoConfigurationEntries- 可能会有点懵逼,这么多,并且 KEY / VALUE 结果看不懂?不要方,我们简单来说下 CouchbaseReactiveRepositoriesAutoConfiguration 配置类。如果它生效,需要 classpath 下有 Bucket、ReactiveCouchbaseRepository、Flux 三个类,所以红线那个条目,对应的就是 CouchbaseReactiveRepositoriesAutoConfiguration 类上的 @ConditionalOnClass({ Bucket.class, ReactiveCouchbaseRepository.class, Flux.class }) 注解部分。- 所以,autoConfigurationMetadata 属性,用途就是制定配置类(Configuration)的生效条件(Condition)。

5.3.2 process

process(AnnotationMetadata annotationMetadata, DeferredImportSelector deferredImportSelector)

方法,进行处理。代码如下:

@Overridepublicvoidprocess(AnnotationMetadata annotationMetadata,DeferredImportSelector deferredImportSelector){// 断言Assert.state(
            deferredImportSelector instanceofAutoConfigurationImportSelector,()->String.format("Only %s implementations are supported, got %s",AutoConfigurationImportSelector.class.getSimpleName(),
                    deferredImportSelector.getClass().getName()));// <1> 获得 AutoConfigurationEntry 对象 AutoConfigurationEntry autoConfigurationEntry =((AutoConfigurationImportSelector) deferredImportSelector).getAutoConfigurationEntry(getAutoConfigurationMetadata(), annotationMetadata);// <2> 添加到 autoConfigurationEntries 中this.autoConfigurationEntries.add(autoConfigurationEntry);// <3> 添加到 entries 中for(String importClassName : autoConfigurationEntry.getConfigurations()){this.entries.putIfAbsent(importClassName, annotationMetadata);}}
  • annotationMetadata 参数,一般来说是被 @SpringBootApplication 注解的元数据。因为,@SpringBootApplication 组合了 @EnableAutoConfiguration 注解。
  • deferredImportSelector 参数,@EnableAutoConfiguration 注解的定义的 @Import 的类,即 AutoConfigurationImportSelector 对象。
  • <1> 处,调用 AutoConfigurationImportSelector#getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata, AnnotationMetadata annotationMetadata) 方法,获得 AutoConfigurationEntry 对象。详细解析,见 「5.4 AutoConfigurationEntry」 。因为这块比较重要,所以先跳过去瞅瞅。
  • <2> 处,添加到 autoConfigurationEntries 中。
  • <3> 处,添加到 entries 中。

5.3.3 selectImports

#selectImports()

方法,获得要引入的配置类。代码如下:

@OverridepublicIterable<Entry>selectImports(){// <1> 如果为空,则返回空数组if(this.autoConfigurationEntries.isEmpty()){returnCollections.emptyList();}// <2.1> 获得 allExclusionsSet<String> allExclusions =this.autoConfigurationEntries.stream().map(AutoConfigurationEntry::getExclusions).flatMap(Collection::stream).collect(Collectors.toSet());// <2.2> 获得 processedConfigurationsSet<String> processedConfigurations =this.autoConfigurationEntries.stream().map(AutoConfigurationEntry::getConfigurations).flatMap(Collection::stream).collect(Collectors.toCollection(LinkedHashSet::new));// <2.3> 从 processedConfigurations 中,移除排除的
    processedConfigurations.removeAll(allExclusions);// <3> 处理,返回结果returnsortAutoConfigurations(processedConfigurations,getAutoConfigurationMetadata())// <3.1> 排序.stream().map((importClassName)->newEntry(this.entries.get(importClassName), importClassName))// <3.2> 创建 Entry 对象.collect(Collectors.toList());// <3.3> 转换成 List}
  • <1> 处,如果为空,则返回空数组。
  • <2.1><2.2><2.3> 处,获得要引入的配置类集合。 比较奇怪的是,上面已经做过一次移除的处理,这里又做一次。不过,没多大关系,可以先无视。
  • <3> 处,处理,返回结果。- <3.1> 处,调用 #sortAutoConfigurations(Set<String> configurations, AutoConfigurationMetadata autoConfigurationMetadata) 方法,排序。代码如下:privateList<String>sortAutoConfigurations(Set<String> configurations,AutoConfigurationMetadata autoConfigurationMetadata){returnnewAutoConfigurationSorter(getMetadataReaderFactory(), autoConfigurationMetadata).getInPriorityOrder(configurations);}- 具体的排序逻辑,胖友自己看。实际上,还是涉及哪些,例如说 @Order 注解。- <3.2> 处,创建 Entry 对象。- <3.3> 处,转换成 List 。结果如下图: 结果

5.4 getAutoConfigurationEntry

这是一个关键方法。因为会调用到,我们会在 「5.1 getCandidateConfigurations」的方法。

getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata, AnnotationMetadata annotationMetadata)

方法,获得 AutoConfigurationEntry 对象。代码如下:

protectedAutoConfigurationEntrygetAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata,AnnotationMetadata annotationMetadata){// <1> 判断是否开启。如未开启,返回空数组。if(!isEnabled(annotationMetadata)){return EMPTY_ENTRY;}// <2> 获得注解的属性AnnotationAttributes attributes =getAttributes(annotationMetadata);// <3> 获得符合条件的配置类的数组List<String> configurations =getCandidateConfigurations(annotationMetadata, attributes);// <3.1> 移除重复的配置类
    configurations =removeDuplicates(configurations);// <4> 获得需要排除的配置类Set<String> exclusions =getExclusions(annotationMetadata, attributes);// <4.1> 校验排除的配置类是否合法checkExcludedClasses(configurations, exclusions);// <4.2> 从 configurations 中,移除需要排除的配置类
    configurations.removeAll(exclusions);// <5> 根据条件(Condition),过滤掉不符合条件的配置类
    configurations =filter(configurations, autoConfigurationMetadata);// <6> 触发自动配置类引入完成的事件fireAutoConfigurationImportEvents(configurations, exclusions);// <7> 创建 AutoConfigurationEntry 对象returnnewAutoConfigurationEntry(configurations, exclusions);}

这里每一步都是细节的方法,所以会每一个方法,都会是引导到对应的小节的方法。

虽然有点长,但是很不复杂。简单的来说,加载符合条件的配置类们,然后移除需要排除(exclusion)的。

  • <1> 处,调用 #isEnabled(AnnotationMetadata metadata) 方法,判断是否开启。如未开启,返回空数组。详细解析,见 「5.4.1 isEnabled」 。
  • <2> 处,调用 #getAttributes(AnnotationMetadata metadata) 方法,获得注解的属性。详细解析,见 「5.4.2 getAttributes」
  • 【重要】<3> 处,调用 getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) 方法,获得符合条件的配置类的数组。> 嘻嘻,到达此之后,整个细节是不是就串起来了!- <3.1> 处,调用 #removeDuplicates(List<T> list) 方法,移除重复的配置类。代码如下:protectedfinal<T>List<T>removeDuplicates(List<T> list){returnnewArrayList<>(newLinkedHashSet<>(list));}- 简单粗暴
  • <4> 处,调用 #getExclusions(AnnotationMetadata metadata, AnnotationAttributes attributes) 方法,获得需要排除的配置类。详细解析,见 「5.4.3 getExclusions」 。- <4.1> 处,调用 #checkExcludedClasses(List<String> configurations, Set<String> exclusions) 方法,校验排除的配置类是否合法。详细解析,见 「5.4.4 checkExcludedClasses」 。- <4.2> 处,从 configurations 中,移除需要排除的配置类。
  • <5> 处,调用 #filter(List<String> configurations, AutoConfigurationMetadata autoConfigurationMetadata) 方法,根据条件(Condition),过滤掉不符合条件的配置类。
  • <6> 处,调用 #fireAutoConfigurationImportEvents(List<String> configurations, Set<String> exclusions) 方法,触发自动配置类引入完成的事件。详细解析,见 「5.4.5 fireAutoConfigurationImportEvents」]。
  • <7> 处,创建 AutoConfigurationEntry 对象。

整个 「5.4 getAutoConfigurationEntry」看完后,请跳回 「5.3.3 selectImports」 。

5.4.1 isEnabled

isEnabled(AnnotationMetadata metadata)

方法,判断是否开启自动配置。代码如下:

protectedbooleanisEnabled(AnnotationMetadata metadata){// 判断 "spring.boot.enableautoconfiguration" 配置判断,是否开启自动配置。// 默认情况下(未配置),开启自动配置。if(getClass()==AutoConfigurationImportSelector.class){returngetEnvironment().getProperty(EnableAutoConfiguration.ENABLED_OVERRIDE_PROPERTY,Boolean.class,true);}returntrue;}

5.4.2 getAttributes

getAttributes(AnnotationMetadata metadata)

方法,获得注解的属性。代码如下:

protectedAnnotationAttributesgetAttributes(AnnotationMetadata metadata){String name =getAnnotationClass().getName();// 获得注解的属性AnnotationAttributes attributes =AnnotationAttributes.fromMap(metadata.getAnnotationAttributes(name,true));// 断言Assert.notNull(attributes,()->"No auto-configuration attributes found. Is "+ metadata.getClassName()+" annotated with "+ClassUtils.getShortName(name)+"?");return attributes;}
  • 注意,此处 getAnnotationClass().getName() 返回的是 @EnableAutoConfiguration 注解,所以这里返回的注解属性,只能是 excludeexcludeName 这两个。
  • 举个例子,假设 Spring 应用上的注解如下:@SpringBootApplication(exclude = {SpringApplicationAdminJmxAutoConfiguration.class}, scanBasePackages = "cn.iocoder")- 返回的结果,如下图: attributes

5.4.3 getExclusions

getExclusions(AnnotationMetadata metadata, AnnotationAttributes attributes)

方法,获得需要排除的配置类。代码如下:

// AutoConfigurationImportSelector.java

protected Set<String> getExclusions(AnnotationMetadata metadata, AnnotationAttributes attributes) {
    Set<String> excluded = new LinkedHashSet<>();
    // 注解上的 exclude 属性
    excluded.addAll(asList(attributes, "exclude"));
    // 注解上的 excludeName 属性
    excluded.addAll(Arrays.asList(attributes.getStringArray("excludeName")));
    // 配置文件的 spring.autoconfigure.exclude 属性
    excluded.addAll(getExcludeAutoConfigurationsProperty());
    return excluded;
}
  • 一共有三种方式,配置排除属性。
  • 该方法会调用如下的方法,比较简单,胖友自己瞅瞅。privateList<String>getExcludeAutoConfigurationsProperty(){// 一般来说,会走这块的逻辑if(getEnvironment()instanceofConfigurableEnvironment){Binder binder =Binder.get(getEnvironment());return binder.bind(PROPERTY_NAME_AUTOCONFIGURE_EXCLUDE,String[].class).map(Arrays::asList).orElse(Collections.emptyList());}String[] excludes =getEnvironment().getProperty(PROPERTY_NAME_AUTOCONFIGURE_EXCLUDE,String[].class);return(excludes !=null)?Arrays.asList(excludes):Collections.emptyList();}protectedfinalList<String>asList(AnnotationAttributes attributes,String name){String[] value = attributes.getStringArray(name);returnArrays.asList(value);}

5.4.4 checkExcludedClasses

checkExcludedClasses(List<String> configurations, Set<String> exclusions)

方法,校验排除的配置类是否合法。代码如下:

privatevoidcheckExcludedClasses(List<String> configurations,Set<String> exclusions){// 获得 exclusions 不在 invalidExcludes 的集合,添加到 invalidExcludes 中List<String> invalidExcludes =newArrayList<>(exclusions.size());for(String exclusion : exclusions){if(ClassUtils.isPresent(exclusion,getClass().getClassLoader())// classpath 存在该类&&!configurations.contains(exclusion)){// configurations 不存在该类
            invalidExcludes.add(exclusion);}}// 如果 invalidExcludes 非空,抛出 IllegalStateException 异常if(!invalidExcludes.isEmpty()){handleInvalidExcludes(invalidExcludes);}}/**
 * Handle any invalid excludes that have been specified.
 * @param invalidExcludes the list of invalid excludes (will always have at least one
 * element)
 */protectedvoidhandleInvalidExcludes(List<String> invalidExcludes){StringBuilder message =newStringBuilder();for(String exclude : invalidExcludes){
        message.append("\t- ").append(exclude).append(String.format("%n"));}thrownewIllegalStateException(String.format("The following classes could not be excluded because they are"+" not auto-configuration classes:%n%s", message));}
  • 不合法的定义,exclusions 存在于 classpath 中,但是不存在 configurations 。这样做的目的是,如果不存在的,就不要去排除啦!
  • 代码比较简单,自己瞅瞅即可。

5.4.5 fireAutoConfigurationImportEvents

fireAutoConfigurationImportEvents(List<String> configurations, Set<String> exclusions)

方法,触发自动配置类引入完成的事件。代码如下:

privatevoidfireAutoConfigurationImportEvents(List<String> configurations,Set<String> exclusions){// <1> 加载指定类型 AutoConfigurationImportListener 对应的,在 `META-INF/spring.factories` 里的类名的数组。List<AutoConfigurationImportListener> listeners =getAutoConfigurationImportListeners();if(!listeners.isEmpty()){// <2> 创建 AutoConfigurationImportEvent 事件AutoConfigurationImportEvent event =newAutoConfigurationImportEvent(this, configurations, exclusions);// <3> 遍历 AutoConfigurationImportListener 监听器们,逐个通知for(AutoConfigurationImportListener listener : listeners){// <3.1> 设置 AutoConfigurationImportListener 的属性invokeAwareMethods(listener);// <3.2> 通知
            listener.onAutoConfigurationImportEvent(event);}}}
  • <1> 处,调用 #getAutoConfigurationImportListeners() 方法,加载指定类型 AutoConfigurationImportListener 对应的,在 META-INF/spring.factories 里的类名的数组。例如: listeners
  • <2> 处,创建 AutoConfigurationImportEvent 事件。
  • <3> 处,遍历 AutoConfigurationImportListener 监听器们,逐个通知。- <3.1> 处,调用 #invokeAwareMethods(Object instance) 方法,设置 AutoConfigurationImportListener 的属性。代码如下:privatevoidinvokeAwareMethods(Object instance){// 各种 Aware 属性的注入if(instance instanceofAware){if(instance instanceofBeanClassLoaderAware){((BeanClassLoaderAware) instance).setBeanClassLoader(this.beanClassLoader);}if(instance instanceofBeanFactoryAware){((BeanFactoryAware) instance).setBeanFactory(this.beanFactory);}if(instance instanceofEnvironmentAware){((EnvironmentAware) instance).setEnvironment(this.environment);}if(instance instanceofResourceLoaderAware){((ResourceLoaderAware) instance).setResourceLoader(this.resourceLoader);}}}- 各种 Aware 属性的注入。- <3.2> 处,调用 AutoConfigurationImportListener#onAutoConfigurationImportEvent(event) 方法,通知监听器。目前只有一个 ConditionEvaluationReportAutoConfigurationImportListener 监听器,没啥逻辑,有兴趣自己看哈。

6. AutoConfigurationPackages

org.springframework.boot.autoconfigure.AutoConfigurationPackages

,自动配置所在的包名。可能这么解释有点怪怪的,我们来看下官方注释:

Class for storing auto-configuration packages for reference later (e.g. by JPA entity scanner).
  • 简单来说,就是将使用 @AutoConfigurationPackage 注解的类所在的包(package),注册成一个 Spring IOC 容器中的 Bean 。酱紫,后续有其它模块需要使用,就可以通过获得该 Bean ,从而获得所在的包。例如说,JPA 模块,需要使用到。

6.1 Registrar

Registrar ,是 AutoConfigurationPackages 的内部类,实现 ImportBeanDefinitionRegistrar、DeterminableImports 接口,注册器,用于处理

@AutoConfigurationPackage

注解。代码如下:

staticclassRegistrarimplementsImportBeanDefinitionRegistrar,DeterminableImports{@OverridepublicvoidregisterBeanDefinitions(AnnotationMetadata metadata,BeanDefinitionRegistry registry){register(registry,newPackageImport(metadata).getPackageName());// <X>}@OverridepublicSet<Object>determineImports(AnnotationMetadata metadata){returnCollections.singleton(newPackageImport(metadata));}}
  • PackageImport 是 AutoConfigurationPackages 的内部类,用于获得包名。代码如下:privatestaticfinalclassPackageImport{/** * 包名 */privatefinalString packageName;PackageImport(AnnotationMetadata metadata){this.packageName =ClassUtils.getPackageName(metadata.getClassName());}publicStringgetPackageName(){returnthis.packageName;}@Overridepublicbooleanequals(Object obj){if(obj ==null||getClass()!= obj.getClass()){returnfalse;}returnthis.packageName.equals(((PackageImport) obj).packageName);}@OverridepublicinthashCode(){returnthis.packageName.hashCode();}@OverridepublicStringtoString(){return"Package Import "+this.packageName;}}- 如下是一个示例:PackageImport
  • <X> 处,调用 #register(BeanDefinitionRegistry registry, String... packageNames) 方法,注册一个用于存储报名(package)的 Bean 到 Spring IOC 容器中。详细解析,见 「6.2 register」。

6.2 register

register(BeanDefinitionRegistry registry, String... packageNames)

方法,注册一个用于存储报名(

package

)的 Bean 到 Spring IOC 容器中。代码如下:

privatestaticfinalString BEAN =AutoConfigurationPackages.class.getName();publicstaticvoidregister(BeanDefinitionRegistry registry,String... packageNames){// <1> 如果已经存在该 BEAN ,则修改其包(package)属性if(registry.containsBeanDefinition(BEAN)){BeanDefinition beanDefinition = registry.getBeanDefinition(BEAN);ConstructorArgumentValues constructorArguments = beanDefinition.getConstructorArgumentValues();
        constructorArguments.addIndexedArgumentValue(0,addBasePackages(constructorArguments, packageNames));// <2> 如果不存在该 BEAN ,则创建一个 Bean ,并进行注册}else{GenericBeanDefinition beanDefinition =newGenericBeanDefinition();
        beanDefinition.setBeanClass(BasePackages.class);
        beanDefinition.getConstructorArgumentValues().addIndexedArgumentValue(0, packageNames);
        beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
        registry.registerBeanDefinition(BEAN, beanDefinition);}}
  • 注册的 BEAN 的类型,为 BasePackages 类型。它是 AutoConfigurationPackages 的内部类。代码如下:staticfinalclassBasePackages{privatefinalList<String> packages;privateboolean loggedBasePackageInfo;BasePackages(String... names){List<String> packages =newArrayList<>();for(String name : names){if(StringUtils.hasText(name)){ packages.add(name);}}this.packages = packages;}publicList<String>get(){if(!this.loggedBasePackageInfo){if(this.packages.isEmpty()){if(logger.isWarnEnabled()){ logger.warn("@EnableAutoConfiguration was declared on a class "+"in the default package. Automatic @Repository and "+"@Entity scanning is not enabled.");}}else{if(logger.isDebugEnabled()){String packageNames =StringUtils.collectionToCommaDelimitedString(this.packages); logger.debug("@EnableAutoConfiguration was declared on a class "+"in the package '"+ packageNames +"'. Automatic @Repository and @Entity scanning is "+"enabled.");}}this.loggedBasePackageInfo =true;}returnthis.packages;}}- 就是一个有 packages 属性的封装类。
  • <1>处,如果已经存在该BEAN,则修改其包(package)属性。而合并package的逻辑,通过#addBasePackages(ConstructorArgumentValues constructorArguments, String[] packageNames)方法,进行实现。代码如下:privatestaticString[]addBasePackages(ConstructorArgumentValues constructorArguments,String[] packageNames){// 获得已存在的String[] existing =(String[]) constructorArguments.getIndexedArgumentValue(0,String[].class).getValue();// 进行合并Set<String> merged =newLinkedHashSet<>(); merged.addAll(Arrays.asList(existing)); merged.addAll(Arrays.asList(packageNames));returnStringUtils.toStringArray(merged);}
  • <2> 处,如果不存在该 BEAN ,则创建一个 Bean ,并进行注册。

6.3 has

#has(BeanFactory beanFactory)

方法,判断是否存在该

BEAN

在传入的容器中。代码如下:

publicstaticbooleanhas(BeanFactory beanFactory){return beanFactory.containsBean(BEAN)&&!get(beanFactory).isEmpty();}

6.4 get

get(BeanFactory beanFactory)

方法,获得

BEAN

。代码如下:

publicstaticList<String>get(BeanFactory beanFactory){try{return beanFactory.getBean(BEAN,BasePackages.class).get();}catch(NoSuchBeanDefinitionException ex){thrownewIllegalStateException("Unable to retrieve @EnableAutoConfiguration base packages");}}
标签: spring boot spring java

本文转载自: https://blog.csdn.net/m0_58847451/article/details/130246219
版权归原作者 狮子也疯狂 所有, 如有侵权,请联系我们删除。

“【SpringBoot源码剥析】| 自动配置原理”的评论:

还没有评论