SpringBoot核心运行原理解析之-------@EnableAutoConfiguration
作者:mmseoamin日期:2023-12-21

核心运行原理

我们通常在使用Spring Boot时,只需要引入对应的starters,Spring Boot启动时变回自动加载相关依赖,配置相应的初始化参数,以最快捷,简单的形式对第三方软件进行集成,这边是Spring Boot的自动配置功能。下图是Spring Boot实现该运作机制涉及的核心部分:

在这里插入图片描述

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

简单概括

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

核心注解解释

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

    运行原理源码解析之@EnableAutoConfiguration

    @EnableAutoConfiguration是开启自动配置的注解,在创建Spring Boot项目看不到该注解,它是由组合注解@SpringBootApplication引入的。先来看下启动类和@SpringBootApplication注解的源码

    启动类和@SpringBootApplication注解

    /**
     * @author lifly
     * @description
     * @date 2023-04-23 20:46
     * @versoin 1.0.0
     **/
    @SpringBootApplication
    public class SpringBootApplicationSource {
        public static void main(String[] args) {
            SpringApplication.run(SpringBootApplicationSource.class,args);
        }
    }
    

    相信你们在创建Spring Boot项目时也会有这样一个main方法,启动类的命名规范都是artifactId+Application.通过该启动类就可以启动Spring Boot项目了。这里只能看到一个注解@SpringBootApplication。它是Spring Boot项目的核心注解,包含了@SpringBootConfiguration,@EnableAutoConfiguration,@ComponentScan注解,它们用于开启自动配置,包扫描,加载配置类。@SpringBootApplication源码如下:

    @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 {};
    	//指定是否代理@Bean方法以强制执行Bean的生命周期行为
        @AliasFor(
            annotation = Configuration.class
        )
        boolean proxyBeanMethods() default true;
    }
    
    • exclude:根据类(Class)排除指定的自动配置,该成员属性覆盖了@SpringBootApplication中组合的@EnableAutoConfiguration中定义的exclude成员属性。
    • excludeName:根据类名排除指定的自动配置,覆盖了@EnableAutoConfiguration中的excludeName的成员属性。
    • scanBasePackages:指定扫描的基础package,用于激活@Component等注解类的初始化。
    • scanBasePackageClasses:扫描指定的类,用于组件的初始化。
    • proxyBeanMethods:指定是否代理@Bean方法以强制执行bean的生命周期行为。此功能需要通过运行时生成CGLIB子类来实现方法拦截。该子类有一定的限制,比如配置类及其方法不允许声明为final等。proxyBeanMethods的默认值为true,允许配置类中进行inter-bean reference(bean之间的引用)以及对该配置的@Bean方法的外部调用。如果@Bean方法都是自包含的,并且提供了容器使用的普通工程方法的功能,则可设置为false,避免处理CGLIB子类。

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

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

      以下是@SpringBootApplication注解组合结构图:

      在这里插入图片描述

      @EnableAutoConfiguration功能解析

      在未使用Spring Boot的情况下,Bean的生命周期由Spring管理,然而Spring无法自动配置@Configuration注解的类。而Spring Boot的核心功能之一就是根据约定自动管理该注解标注的类。用来实现该功能的组件之一便是@EnableAutoConfiguration注解。@EnableAutoConfiguration位于spring-boot-autoconfigure包内,当使用@SpringBootApplication注解时,@EnableAutoConfiguration注解会自动生效。@EnableAutoConfiguration的主要功能是启动Spring应用程序上下文时进行自动配置,它会尝试猜测并配置项目可能需要的Bean。自动配置通常是基于项目classPath中引入的和已定义的Bean来实现的。在此过程中,被自动配置的组件来自项目自身和项目依赖的jar包中。

      下面来看下@EnableAutoConfiguration注解的源码:

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

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

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

        正如上文所说,@EnableAutoConfiguration会猜测你需要使用的Bean,但如果在实战中你并不需要它预配置的Bean,可以通过该注解的exclude或excludeName参数进行有针对性的排除,例如,不需要数据库的自动配置时,可以通过以下两种方法进行排除:

        /**
         * @author lifly
         * @description
         * @date 2023-04-23 20:46
         * @versoin 1.0.0
         **/
        @SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
        public class SpringBootApplicationSource {
            public static void main(String[] args) {
                SpringApplication.run(SpringBootApplicationSource.class,args);
            }
        }
        /**
         * @author lifly
         * @description
         * @date 2023-04-28 22:16
         * @versoin 1.0.0
         **/
        @Configuration
        @EnableAutoConfiguration(exclude = DataSourceAutoConfiguration.class)
        public class TestConfiguration {
        }
        

        注意

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

        AutoConfigurationImportSelector源码分析

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

        @Import注解

        @Import注解位于spring-context项目内,主要提供导入配置类的功能。@Import源码:

        @Target({ElementType.TYPE})
        @Retention(RetentionPolicy.RUNTIME)
        @Documented
        public @interface Import {
            Class[] value();
        }
        

        @Import的作用和xml配置中"import/"标签的作用一样,我们可以通过@Import引入@Configuration注解的类,也可以导入实现了ImportSelector或ImportBeanDefinitionRegistrar的类,还可以通过@Import导入普通的POJO(将其注册成Spring Bean,导入POJO需要Spring4.2以上版本)。

        ImportSelector接口

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

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

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

        如果实现了接口ImportSelector的类的同时又实现了4个Aware接口,那么Spring保证在调用ImportSelector之前会先调用Aware接口的方法。这4个接口为EnvironmentAware,BeanFactoryAware,BeanClassLoaderAware和ResourceLoaderAware。在AutoConfigurationImportSelector的源代码中就实现了这4个接口

        public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware, ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {
        

        在上面的源代码中,AutoConfigurationImportSelector并没有直接实现ImportSelector接口,而是实现了它的子接口DeferredImportSelector。DeferredImportSelector接口与ImportSelector的区别是,前者会在所有的@Configuration类加载完成之后再加载返回的配置类,而ImportSelector是在加载完@Configuration类之前加载返回的配置类。

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

        AutoConfigutationImportSelector功能概述

        下面通过一张图来从整体了解AutoConfigurationImportSelector的核心功能及流程,然后再对照源码看具体的功能实现。

        在这里插入图片描述

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

        @Override
        	public String[] selectImports(AnnotationMetadata annotationMetadata) {
        		if (!isEnabled(annotationMetadata)) {
        			return NO_IMPORTS;
        		}
        		AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
        				.loadMetadata(this.beanClassLoader);
        		AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(autoConfigurationMetadata,
        				annotationMetadata);
        		return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
        	}
        protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata,
        			AnnotationMetadata annotationMetadata) {
        		if (!isEnabled(annotationMetadata)) {
        			return EMPTY_ENTRY;
        		}
        		AnnotationAttributes attributes = getAttributes(annotationMetadata);
        		List configurations = getCandidateConfigurations(annotationMetadata, attributes);
        		configurations = removeDuplicates(configurations);
        		Set exclusions = getExclusions(annotationMetadata, attributes);
        		checkExcludedClasses(configurations, exclusions);
        		configurations.removeAll(exclusions);
        		configurations = filter(configurations, autoConfigurationMetadata);
        		fireAutoConfigurationImportEvents(configurations, exclusions);
        		return new AutoConfigurationEntry(configurations, exclusions);
        	}
        

        在selectImports方法中,首先会去检查自动配置功能是否开启,默认为开启,然后加载自动配置的元信息,配置文件为类路径中META-INF目录下的;调用getAutoConfigurationEntry方法封装被引入的自动配置信息;返回符合条件的配置类的全限定名数组。

        在getAutoConfigurationEntry方法中,也是会去检查自动配置功能是否开启,获取注解属性;通过SpringFactoriesLoader类提供的方法加载类路径中META-INF目录下的spring.factories文件中针对EnableAutoConfiguration的注册配置类;对获得的注册配置类集合进行去重处理,防止多个项目引入同样的配置类;获得注解中被exclude或excludeName所排除的类的集合;检查被排除类是否可实例化,是否被自动注册配置所使用,不合符条件则抛出异常;从自动配置类集合中去除被排除的类;检查配置类的注解是否符合spring.factories文件中AutoConfigurationImportFilter指定的注解检查条件;将筛选完成的配置类和排查的配置类构建为事件类,并传入监听器。监听器的配置在于spring.factories文件中,通过AutoConfigurationImportListener指定;返回自动配置信息。

        @EnableAutoConfiguration自动配置开关

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

        @Override
        	public String[] selectImports(AnnotationMetadata annotationMetadata) {
        		if (!isEnabled(annotationMetadata)) {
        			return NO_IMPORTS;
        		}
        		......
        	}
        

        该方法主要使用isEnabled方法判断自动配置是否开启,代码如下:

        	protected boolean isEnabled(AnnotationMetadata metadata) {
        		if (getClass() == AutoConfigurationImportSelector.class) {
        			return getEnvironment().getProperty(EnableAutoConfiguration.ENABLED_OVERRIDE_PROPERTY, Boolean.class, true);
        		}
        		return true;
        	}
        

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

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

        spring.boot.enableautoconfiguration=false
        

        @EnableAutoconfiguration加载元数据配置

        加载元数据配置主要是为后续操作提供数据支持。

        @Override
        	public String[] selectImports(AnnotationMetadata annotationMetadata) {
        		......
        		AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
        				.loadMetadata(this.beanClassLoader);
        		......
        	}
        

        加载元数据使用了AutoConfigurationMetadataLoader类提供的loadMetadata方法,该方法会默认加载类路径下META-INF/spring-autoconfiguration-metadata-properties内的配置

        final class AutoConfigurationMetadataLoader {
        	//默认加载元数据的路径
        	protected static final String PATH = "META-INF/spring-autoconfigure-metadata.properties";
        	private AutoConfigurationMetadataLoader() {
        	}
        	//默认使用该方法,传入默认的PATH
        	static AutoConfigurationMetadata loadMetadata(ClassLoader classLoader) {
        		return loadMetadata(classLoader, PATH);
        	}
        	static AutoConfigurationMetadata loadMetadata(ClassLoader classLoader, String path) {
        		try {
        			//获取数据存储于Enumeration中
        			Enumeration urls = (classLoader != null) ? classLoader.getResources(path)
        					: ClassLoader.getSystemResources(path);
        			Properties properties = new Properties();
        			while (urls.hasMoreElements()) {
        			//遍历Enumeration中的urls,加载其中的属性,存储到properties中	
        			properties.putAll(PropertiesLoaderUtils.loadProperties(new UrlResource(urls.nextElement())));
        			}
        			return loadMetadata(properties);
        		}
        		catch (IOException ex) {
        			throw new IllegalArgumentException("Unable to load @ConditionalOnClass location [" + path + "]", ex);
        		}
        	}
        	//创建AutoConfigurationMetadata的实现类AutoConfigurationMetadata 
        	static AutoConfigurationMetadata loadMetadata(Properties properties) {
        		return new PropertiesAutoConfigurationMetadata(properties);
        	}
        	//内部实现类
        	/**
        	 * {@link AutoConfigurationMetadata} implementation backed by a properties file.
        	 */
        	private static class PropertiesAutoConfigurationMetadata implements AutoConfigurationMetadata {
        		private final Properties properties;
        		PropertiesAutoConfigurationMetadata(Properties properties) {
        			this.properties = properties;
        		}
        		@Override
        		public boolean wasProcessed(String className) {
        			return this.properties.containsKey(className);
        		}
        		@Override
        		public Integer getInteger(String className, String key) {
        			return getInteger(className, key, null);
        		}
        		@Override
        		public Integer getInteger(String className, String key, Integer defaultValue) {
        			String value = get(className, key);
        			return (value != null) ? Integer.valueOf(value) : defaultValue;
        		}
        		@Override
        		public Set getSet(String className, String key) {
        			return getSet(className, key, null);
        		}
        		@Override
        		public Set getSet(String className, String key, Set defaultValue) {
        			String value = get(className, key);
        			return (value != null) ? StringUtils.commaDelimitedListToSet(value) : defaultValue;
        		}
        		@Override
        		public String get(String className, String key) {
        			return get(className, key, null);
        		}
        		@Override
        		public String get(String className, String key, String defaultValue) {
        			String value = this.properties.getProperty(className + "." + key);
        			return (value != null) ? value : defaultValue;
        		}
        	}
        }
        

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

        为什么加载元数据

        主要是为了后续过滤自动配置使用。Srping Boot使用一个Annotation的处理器来收集自动加载的条件,这些条件可以在元数据文件中进行配置。Spring Boot会将收集好的@Configuration进行一次过滤,进行剔除不满足条件的配置类。官方文档指出:使用这种配置方式可以有效缩短Spring Boot的启动时间,减少@Configuration类的数量,减少初始化Bean的耗时。

        @EnableAutoConfiguration加载自动配置组件

        加载自动配组件时自动配置的核心组件之一,这些自动配置组件位于类路径中META-INF目录下的spring.factories文件中进行注册。Spring Boot预置了一些常用组件,如果我们需要自定义组件,可以参考Spring Boot预置组件在自己的Starters中进行配置。

        通过Spring Core提供的SpringFactoriesLoader类可以读取spring.factories文件中注册的类。可以通过AutoConfigurationImportSelector类中getCondidateConfigurations方法读取spring.factories文件中注册的类。

        	protected List getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
        		List 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;
        	}
        	protected Class getSpringFactoriesLoaderFactoryClass() {
        		return EnableAutoConfiguration.class;
        	}
        

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

         public final class SpringFactoriesLoader {
         	//该类加载文件的路径,可能存在多个
            public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
          ......
          	//加载所有的META-INF/spring.factories文件,封装成Map,并从中获取指定类名的列表
            public static List loadFactoryNames(Class factoryType, @Nullable ClassLoader classLoader) {
                String factoryTypeName = factoryType.getName();
                return (List)loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());
            }
        	//加载所有的META-INF/spring.factories文件,封装成Map,key为接口的全类名,value对应配置值的List集合
            private static Map> loadSpringFactories(@Nullable ClassLoader classLoader) {
                MultiValueMap result = (MultiValueMap)cache.get(classLoader);
                if (result != null) {
                    return result;
                } else {
                    try {
                        Enumeration urls = classLoader != null ? classLoader.getResources("META-INF/spring.factories") : ClassLoader.getSystemResources("META-INF/spring.factories");
                        MultiValueMap result = new LinkedMultiValueMap();
                        while(urls.hasMoreElements()) {
                            URL url = (URL)urls.nextElement();
                            UrlResource resource = new UrlResource(url);
                            Properties properties = PropertiesLoaderUtils.loadProperties(resource);
                            Iterator var6 = properties.entrySet().iterator();
                            while(var6.hasNext()) {
                                Map.Entry entry = (Map.Entry)var6.next();
                                String factoryTypeName = ((String)entry.getKey()).trim();
                                String[] var9 = StringUtils.commaDelimitedListToStringArray((String)entry.getValue());
                                int var10 = var9.length;
                                for(int var11 = 0; var11 < var10; ++var11) {
                                    String factoryImplementationName = var9[var11];
                                    result.add(factoryTypeName, factoryImplementationName.trim());
                                }
                            }
                        }
                        cache.put(classLoader, result);
                        return result;
                    } catch (IOException var13) {
                        throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", var13);
                    }
                }
            }
          ......
        }
        

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

        Map中的内容:

        # Failure Analyzers
        org.springframework.boot.diagnostics.FailureAnalyzer=\
        org.springframework.boot.diagnostics.analyzer.BeanCurrentlyInCreationFailureAnalyzer,\
        org.springframework.boot.diagnostics.analyzer.BeanDefinitionOverrideFailureAnalyzer,\
        org.springframework.boot.diagnostics.analyzer.BeanNotOfRequiredTypeFailureAnalyzer,\
        org.springframework.boot.diagnostics.analyzer.BindFailureAnalyzer,\
        org.springframework.boot.diagnostics.analyzer.BindValidationFailureAnalyzer,\
        org.springframework.boot.diagnostics.analyzer.UnboundConfigurationPropertyFailureAnalyzer,\
        org.springframework.boot.diagnostics.analyzer.ConnectorStartFailureAnalyzer,\
        org.springframework.boot.diagnostics.analyzer.NoSuchMethodFailureAnalyzer,\
        org.springframework.boot.diagnostics.analyzer.NoUniqueBeanDefinitionFailureAnalyzer,\
        org.springframework.boot.diagnostics.analyzer.PortInUseFailureAnalyzer,\
        org.springframework.boot.diagnostics.analyzer.ValidationExceptionFailureAnalyzer,\
        org.springframework.boot.diagnostics.analyzer.InvalidConfigurationPropertyNameFailureAnalyzer,\
        org.springframework.boot.diagnostics.analyzer.InvalidConfigurationPropertyValueFailureAnalyzer
        # FailureAnalysisReporters
        org.springframework.boot.diagnostics.FailureAnalysisReporter=\
        org.springframework.boot.diagnostics.LoggingFailureAnalysisReporter
        

        @EnableAutoConfiguration排除指定组件

        在上面的加载自动配置组件中获取了spring.factories文件中注册的自动加载组件,有时候我们并不需要某个组件,可以通过@EnableAutoConfiguration的注解属性exclude或excludeName进行有针对性的排除,当然也可以铜鼓配置文件进行排除,下面我们来下如何排除组件的功能。

        protected Set getExclusions(AnnotationMetadata metadata, AnnotationAttributes attributes) {
        		Set excluded = new LinkedHashSet<>();
        		excluded.addAll(asList(attributes, "exclude"));
        		excluded.addAll(Arrays.asList(attributes.getStringArray("excludeName")));
        		excluded.addAll(getExcludeAutoConfigurationsProperty());
        		return excluded;
        	}
        	private List getExcludeAutoConfigurationsProperty() {
        		if (getEnvironment() instanceof ConfigurableEnvironment) {
        			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();
        	}
        

        AutoConfigurationImportSelector中调用getExclusions方法来获取被排除的集合。它会收集@EnableAutoConfiguration注解中配置的exclude属性值,excludeName属性值,并通过getExcludeAutoConfigutationsProperties获取在配置文件中key为spring.autoconfigure.exclude的配置值。以排除自动配置DataSourceAutoConfiguration为例,配置文件的形式如下:

        spring.autoconfiguration.exclude=org.springframework.boot.autoconfiguration.jdbc.DataSourceAutoConfiguration
        

        获取到被排除的组件之后,然后会对待排除类进行检查操作:

        	private void checkExcludedClasses(List configurations, Set exclusions) {
        		List invalidExcludes = new ArrayList<>(exclusions.size());
        		//遍历并判断是否存在对应的配置类
        		for (String exclusion : exclusions) {
        			if (ClassUtils.isPresent(exclusion, getClass().getClassLoader()) && !configurations.contains(exclusion)) {
        				invalidExcludes.add(exclusion);
        			}
        		}
        		//如果不为空,进行处理
        		if (!invalidExcludes.isEmpty()) {
        			handleInvalidExcludes(invalidExcludes);
        		}
        	}
        	//抛出指定异常
        	protected void handleInvalidExcludes(List invalidExcludes) {
        		StringBuilder message = new StringBuilder();
        		for (String exclude : invalidExcludes) {
        			message.append("\t- ").append(exclude).append(String.format("%n"));
        		}
        		throw new IllegalStateException(String.format(
        				"The following classes could not be excluded because they are not auto-configuration classes:%n%s",
        				message));
        	}
        

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

        如果满足条件,调用configuration.removeAll(exclusions)方法从自动配置集合中移除被排除集合的类。

        @EnableAutoConfiguration过滤自动配置组件

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

        private List filter(List configurations, AutoConfigurationMetadata autoConfigurationMetadata) {
        		long startTime = System.nanoTime();
        		String[] candidates = StringUtils.toStringArray(configurations);
        		boolean[] skip = new boolean[candidates.length];
        		boolean skipped = false;
        		for (AutoConfigurationImportFilter filter : getAutoConfigurationImportFilters()) {
        			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;
        		}
        		List result = new ArrayList<>(candidates.length);
        		for (int i = 0; i < candidates.length; i++) {
        			if (!skip[i]) {
        				result.add(candidates[i]);
        			}
        		}
        		if (logger.isTraceEnabled()) {
        			int 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 getAutoConfigurationImportFilters() {
        		return SpringFactoriesLoader.loadFactories(AutoConfigurationImportFilter.class, this.beanClassLoader);
        	}
        
        • configurations:经过初次过滤之后的自动配置组件列表。
        • autoConfigurationMetadata:AutoConfigurationMetadata,元数据文件META-INF/spring-autoconfiguration-metadata.properties中配置对应实体类。
        • List< AutoConfigurationImportFilter > :META-INF/spring.factories中配置key为ConfigurationImportFilters列表。

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

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

        明白以上信息之后,该filter方法的过滤功能简化如下:对自动配置组件列表进行再次过滤,过滤条件为该列表中自动配置类的注解得包含OnBeanCondition,OnClassCondition和OnWeApplicationCondition中指定的注解,依次包含@ConditionOnBean、@ConditionOnClass、@ConditionOnWebApplication。

        那么在实现过程中,AutoConfigurationMetadata对应的元数据和AutoConfigurationImportFilter接口及其实现类是如何进行具体筛选的呢?

        下面来看下 AutoConfigurationImportFilter接口及其相关实现类的结构功能:

        在这里插入图片描述

        相关源码及步骤的分析,我们已经知道AutoConfigurationImportFilter接口可以在spring.factories中注册过过滤器,用来过滤自动配置类,在实例化之前快速排除不需要的自动配置,代码如下:

        @FunctionalInterface
        public interface AutoConfigurationImportFilter {
        	boolean[] match(String[] autoConfigurationClasses, AutoConfigurationMetadata autoConfigurationMetadata);
        }
        

        match方法接收两个参数,一个是待过滤的自动配置类数组,另一个是自动配置的元数据信息。match返回的结果为匹配过滤后的结果布尔数组,数组的大小与String[]autoConfigurationClasses一致,如果需排除,设置对应值为false。

        上图中已显示AutoConfigurationImportFilter接口的match方法主要在其抽象子类中实现,而抽象子类FilteringSpringBootCondition在实现match方法的同时又定义了新的抽象方法getOutcomes,集成该抽象类的其他3个子类均实现了getOutcomes方法,代码如下:

        abstract class FilteringSpringBootCondition extends SpringBootCondition
        		implements AutoConfigurationImportFilter, BeanFactoryAware, BeanClassLoaderAware {
        	private BeanFactory beanFactory;
        	private ClassLoader beanClassLoader;
        	@Override
        	public boolean[] match(String[] autoConfigurationClasses, AutoConfigurationMetadata autoConfigurationMetadata) {
        		ConditionEvaluationReport report = ConditionEvaluationReport.find(this.beanFactory);
        		ConditionOutcome[] outcomes = getOutcomes(autoConfigurationClasses, autoConfigurationMetadata);
        		boolean[] match = new boolean[outcomes.length];
        		for (int i = 0; i < outcomes.length; i++) {
        			match[i] = (outcomes[i] == null || outcomes[i].isMatch());
        			if (!match[i] && outcomes[i] != null) {
        				logOutcome(autoConfigurationClasses[i], outcomes[i]);
        				if (report != null) {
        					report.recordConditionEvaluation(autoConfigurationClasses[i], this, outcomes[i]);
        				}
        			}
        		}
        		return match;
        	}
        	//过滤核心功能,有其子类实现
        	protected abstract ConditionOutcome[] getOutcomes(String[] autoConfigurationClasses,
        			AutoConfigurationMetadata autoConfigurationMetadata);
        

        通过上述源码可以看出,match方法在抽象类FilteringSpringBootCondition中主要是调用getOutcomes方法,并将其返回的结果转换成布尔数组。而相关的过滤核心功能由其子类实现的getOutcomes方法来实现。以OnClassCondition来说明,代码如下:

        @Order(Ordered.HIGHEST_PRECEDENCE)
        class OnClassCondition extends FilteringSpringBootCondition {
           @Override
           protected final ConditionOutcome[] getOutcomes(String[] autoConfigurationClasses,
           		AutoConfigurationMetadata autoConfigurationMetadata) {
           	// Split the work and perform half in a background thread if more than one
           	// processor is available. Using a single additional thread seems to offer the
           	// best performance. More threads make things worse.
           	if (Runtime.getRuntime().availableProcessors() > 1) {
           		return resolveOutcomesThreaded(autoConfigurationClasses, autoConfigurationMetadata);
           	}
           	else {
           		OutcomesResolver outcomesResolver = new StandardOutcomesResolver(autoConfigurationClasses, 0,
           				autoConfigurationClasses.length, autoConfigurationMetadata, getBeanClassLoader());
           		return outcomesResolver.resolveOutcomes();
           	}
           }
        

        Spring Boot当前版本对getOutcomes方法进行了优化,根据处理器的情况不同采用了不同的方式进行操作。如果使用了多个处理器,采用后台线程处理。否则,getOutcomes直接创建StandardOutcomesResolver来处理。

        在resolveOutcomesThreaded方法中主要采用了分半处理的方法来提升处理效率,而核心功能都是在内部类StandardOutcomesResolver的resolveOutcomes方法中实现。

        resolveOutcomesThreaded的分半处理实现代码如下:

        	private ConditionOutcome[] resolveOutcomesThreaded(String[] autoConfigurationClasses,
        			AutoConfigurationMetadata autoConfigurationMetadata) {
        		int split = autoConfigurationClasses.length / 2;
        		OutcomesResolver firstHalfResolver = createOutcomesResolver(autoConfigurationClasses, 0, split,
        				autoConfigurationMetadata);
        		OutcomesResolver secondHalfResolver = new StandardOutcomesResolver(autoConfigurationClasses, split,
        				autoConfigurationClasses.length, autoConfigurationMetadata, getBeanClassLoader());
        		ConditionOutcome[] secondHalf = secondHalfResolver.resolveOutcomes();
        		ConditionOutcome[] firstHalf = firstHalfResolver.resolveOutcomes();
        		ConditionOutcome[] outcomes = new ConditionOutcome[autoConfigurationClasses.length];
        		System.arraycopy(firstHalf, 0, outcomes, 0, firstHalf.length);
        		System.arraycopy(secondHalf, 0, outcomes, split, secondHalf.length);
        		return outcomes;
        	}
        

        内部类StandardOutcomesResolver的源码重点关注getOutcomes方法的实现,它实现了获取元数据中的指定配置,间接调用了getOutcome(String className,ClassLoader classLoader)方法来判断该类是否符合条件,部分源码如下:

        private final class StandardOutcomesResolver implements OutcomesResolver {
        		......
        		@Override
        		public ConditionOutcome[] resolveOutcomes() {
        			return getOutcomes(this.autoConfigurationClasses, this.start, this.end, this.autoConfigurationMetadata);
        		}
        		private ConditionOutcome[] getOutcomes(String[] autoConfigurationClasses, int start, int end,
        				AutoConfigurationMetadata autoConfigurationMetadata) {
        			ConditionOutcome[] outcomes = new ConditionOutcome[end - start];
        			for (int i = start; i < end; i++) {
        				String autoConfigurationClass = autoConfigurationClasses[i];
        				if (autoConfigurationClass != null) {
        					String candidates = autoConfigurationMetadata.get(autoConfigurationClass, "ConditionalOnClass");
        					if (candidates != null) {
        						outcomes[i - start] = getOutcome(candidates);
        					}
        				}
        			}
        			return outcomes;
        		}
        		//判断该类是否符合条件
        		private ConditionOutcome getOutcome(String candidates) {
        			try {
        				if (!candidates.contains(",")) {
        					return getOutcome(candidates, this.beanClassLoader);
        				}
        				for (String candidate : StringUtils.commaDelimitedListToStringArray(candidates)) {
        					ConditionOutcome outcome = getOutcome(candidate, this.beanClassLoader);
        					if (outcome != null) {
        						return outcome;
        					}
        				}
        			}
        			catch (Exception ex) {
        				// We'll get another chance later
        			}
        			return null;
        		}
        		private ConditionOutcome getOutcome(String className, ClassLoader classLoader) {
        			if (ClassNameFilter.MISSING.matches(className, classLoader)) {
        				return ConditionOutcome.noMatch(ConditionMessage.forCondition(ConditionalOnClass.class)
        						.didNotFind("required class").items(Style.QUOTE, className));
        			}
        			return null;
        		}
        	}
        

        在获取元数据指定配置的功能时用到了AutoConfigurationMetadata接口的get(String className,String key)方法,而该方法由类AutoConfigurationMetadataLoader来实现。该类在前面已经说过,它会加载META-INF/spring-autoconfigure-metadata.properties中的配置。

        final class AutoConfigurationMetadataLoader {
        	protected static final String PATH = "META-INF/spring-autoconfigure-metadata.properties";
        	......
        	/**
        	 * {@link AutoConfigurationMetadata} implementation backed by a properties file.
        	 */
        	private static class PropertiesAutoConfigurationMetadata implements AutoConfigurationMetadata {
        		private final Properties properties;
        		PropertiesAutoConfigurationMetadata(Properties properties) {
        			this.properties = properties;
        		}
        		......
        		@Override
        		public String get(String className, String key) {
        			return get(className, key, null);
        		}
        		@Override
        		public String get(String className, String key, String defaultValue) {
        			String value = this.properties.getProperty(className + "." + key);
        			return (value != null) ? value : defaultValue;
        		}
        	}
        }
        

        AutoConfigurationMetadataLoader的内部类PropertiesAutoConfigurationMetadata实现了AutoConfigurationMetadata接口的具体方法,其中包含了我们用到的get(String className,String key)方法。

        根据get方法实现过程,在getOutcomes方法中获取到的candidates其实就是META-INF/spring-autoconfigure-metadata.properties文件中配置的key为自动加载注解类+‘.’+'ConditionalOnClass’的字符串,而value为其获得的值。以数据源的自动配置为例,寻找到的对应元数据配置如下:

        org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration.ConditionalOnClass=javax.sql.DataSource,org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType
        

        key为自动加载组件org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration.加上“.”,再加上当前过滤条件中指定的ConditionalOnClass。然后根据key获得的value值为javax.sql.DataSource,org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType。

        当获取到对应的candidates值之后,最终会调用getOutcomes(String className,ClassLoader classLoader)方法,并在其中使用枚举类ClassNameFilter.MISSING的matches方法来判断candidates值是否匹配。而枚举类ClassNameFilter位于OnClassCondition集成的抽象类FilteringSpringBootCondition中。

        protected enum ClassNameFilter {
        		PRESENT {
        			@Override
        			public boolean matches(String className, ClassLoader classLoader) {
        				return isPresent(className, classLoader);
        			}
        		},
        		MISSING {
        			@Override
        			public boolean matches(String className, ClassLoader classLoader) {
        				return !isPresent(className, classLoader);
        			}
        		};
        		abstract boolean matches(String className, ClassLoader classLoader);
        		//通过类加载是否抛出异常来判断该类是否存在
        		static boolean isPresent(String className, ClassLoader classLoader) {
        			if (classLoader == null) {
        				classLoader = ClassUtils.getDefaultClassLoader();
        			}
        			try {
        				resolve(className, classLoader);
        				return true;
        			}
        			catch (Throwable ex) {
        				return false;
        			}
        		}
        	}
        	//进行类加载操作
            @CallerSensitive
            public static Class forName(String className)
                        throws ClassNotFoundException {
                Class caller = Reflection.getCallerClass();
                return forName0(className, true, ClassLoader.getClassLoader(caller), caller);
            }
        

        ClassNameFilter的匹配原则很简单,就是通过类加载器取加载指定的类,如果指定的类加载成功,既没有抛出异常,说明ClassNameFilter匹配成功。如果抛出异常,说明ClassNameFilter加载失败。

        至此,整个过滤过程的核心部分已经完成了。下面再来过一下整个过滤过程:

        在这里插入图片描述

        @EnableAutoConfiguration事件注册

        在完成了以上步骤的过滤,筛选后,我们最终获得了要进行自动配置的类的集合,在将改集合返回之前,在AutoConfigurationImportSelector类中完成的最后一步操作就是相关事件的封装和广播。相关代码如下:

        private void fireAutoConfigurationImportEvents(List configurations, Set exclusions) {
        		List listeners = getAutoConfigurationImportListeners();
        		if (!listeners.isEmpty()) {
        			AutoConfigurationImportEvent event = new AutoConfigurationImportEvent(this, configurations, exclusions);
        			for (AutoConfigurationImportListener listener : listeners) {
        				invokeAwareMethods(listener);
        				listener.onAutoConfigurationImportEvent(event);
        			}
        		}
        	}
        	protected List getAutoConfigurationImportListeners() {
        		return SpringFactoriesLoader.loadFactories(AutoConfigurationImportListener.class, this.beanClassLoader);
        	}
        

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

        到此@EnableAutoConfiguration注解接分析完毕了,后续我会继续分析其他注解,如果觉得笔者写的能让你学到点东西,麻烦点个关注,感激不尽。喜欢看微信公众号的也可以搜索微信公众号liflyCode那里也有收录