自动配置:根据我们添加的 jar 包依赖,会自动将一些配置类的 bean 注册进 ioc 容器,我们可以需要的地方使用 @autowired 或者 @resource等注解来使用它。 那么Spring Boot 到底是如何进行自动配置的,都把哪些组件进行了自动配置?以下主要从四个注解入手进行源码层面的深入剖析。



一. @SpringBootApplication

Spring Boot应用的启动入口是@SpringBootApplication注解标注类中的main()方法,


@SpringBootApplication : SpringBoot 应用标注在某个类上说明这个类是 SpringBoot 的主配置


类, SpringBoot 就应该运行这个类的 main() 方法启动 SpringBoot 应用。



下面,查看 @SpringBootApplication 内部源码进行分析 ,核心代码具体如下:




package org.springframework.boot.autoconfigure;

import org.springframework.boot.SpringBootConfiguration;
import org.springframework.boot.context.TypeExcludeFilter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.ComponentScan.Filter;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;
import org.springframework.core.annotation.AliasFor;
import org.springframework.data.repository.Repository;

import java.lang.annotation.*;

@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 @interface SpringBootApplication {


	// 根据class来排除特定的类,使其不能加入spring容器,传入参数value类型是class类型。
	@AliasFor(annotation = EnableAutoConfiguration.class)
	Class<?>[] exclude() default {};


	// 根据classname 来排除特定的类,使其不能加入spring容器,传入参数value类型是class的全类名字符串数组
	@AliasFor(annotation = EnableAutoConfiguration.class)
	String[] excludeName() default {};


	// 指定扫描包,参数是包名的字符串数组。
	@AliasFor(annotation = ComponentScan.class, attribute = "basePackages")
	String[] scanBasePackages() default {};


	// 扫描特定的包,参数类似是Class类型数组。
	@AliasFor(annotation = ComponentScan.class, attribute = "basePackageClasses")
	Class<?>[] scanBasePackageClasses() default {};


	@AliasFor(annotation = Configuration.class)
	boolean proxyBeanMethods() default true;

}



从上述源码可以看出, @SpringBootApplication 注解是一个组合注解,前面 4 个是注解的元数据信



息, 我们主要看后面 3 个注解: @SpringBootConfiguration 、 @EnableAutoConfiguration 、



@ComponentScan 三个核心注解。




二. @SpringBootConfiguration



@SpringBootConfiguration : SpringBoot 的配置类,标注在某个类上,表示这是一个 SpringBoot



的配置类。



查看 @SpringBootConfiguration 注解源码,核心代码具体如下:



package org.springframework.boot;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.AliasFor;

import java.lang.annotation.*;


@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented

@Configuration // 配置类的作用等同于配置文件,配置类也是容器中的一个对象
public @interface SpringBootConfiguration {


	@AliasFor(annotation = Configuration.class)
	boolean proxyBeanMethods() default true;

}



从上述源码可以看出, @SpringBootConfiguration 注解内部有一个核心注解 @Configuration ,该注解是 Spring 框架提供的,表示当前类为一个配置类( XML 配置文件的注解表现形式),并可以被组件扫描 器扫描。由此可见, @SpringBootConfiguration 注解的作用与 @Configuration 注解相同,都是标识一 个可以被组件扫描器扫描的配置类,只不过 @SpringBootConfiguration 是被 Spring Boot 进行了重新封 装命名而已。




三. @EnableAutoConfiguration

package org.springframework.boot.autoconfigure;

import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
import org.springframework.boot.web.servlet.server.ServletWebServerFactory;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.core.io.support.SpringFactoriesLoader;

import java.lang.annotation.*;


@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited

// 自动配置包
@AutoConfigurationPackage 
// Spring的底层注解@Import,给容器中导入一个组件;
//导入的组件是AutoConfigurationPackages.Registrar.class
@Import(AutoConfigurationImportSelector.class) 
// 告诉SpringBoot开启自动配置功能,这样自动配置才能生效。
public @interface EnableAutoConfiguration {

	String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";

    // 返回不会被导入到 Spring 容器中的类
	Class<?>[] exclude() default {};

    // 返回不会被导入到 Spring 容器中的类名
	String[] excludeName() default {};

}



Spring 中有很多以 Enable 开头的注解,其作用就是借助 @Import 来收集并注册特定场景相关的



Bean ,并加载到 IOC 容器。




@EnableAutoConfiguration 就是借助 @Import 来收集所有符合自动配置条件的 bean 定义,并加载到IoC 容器



下面对@EnableAutoConfiguration注解上标注的两个注解进行剖析

(一)@AutoConfigurationPackage

package org.springframework.boot.autoconfigure;

import org.springframework.context.annotation.Import;

import java.lang.annotation.*;

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited

	// Spring的底层注解@Import,给容器中导入一个组件;
	// 导入的组件是AutoConfigurationPackages.Registrar.class
@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {

}



@AutoConfigurationPackage :自动配置包,它也是一个组合注解,其中最重要的注解是



@Import(AutoConfigurationPackages.Registrar.class) ,它是 Spring 框架的底层注解,它的作



用就是给容器中导入某个组件类,例如



@Import(AutoConfigurationPackages.Registrar.class) ,它就是将 Registrar 这个组件类导入



到容器中,可查看 Registrar 类中 registerBeanDefinitions 方法:





springboot main 方法加载日志配置 springboot加载配置文件源码_springboot自动配置


 


我们对 new PackageImport(metadata).getPackageName() 进行检索,看看其结果是什么?


springboot main 方法加载日志配置 springboot加载配置文件源码_springboot自动配置_02

 这个com.lagou正好是我们主类所在的包名。


再看下 register 方法 :


public static void register(BeanDefinitionRegistry registry, String... packageNames) {
		// 这里参数 packageNames 缺省情况下就是一个字符串,是使用了注解
		// @SpringBootApplication 的 Spring Boot 应用程序入口类所在的包

		if (registry.containsBeanDefinition(BEAN)) {
			// 如果该bean已经注册,则将要注册包名称添加进去
			BeanDefinition beanDefinition = registry.getBeanDefinition(BEAN);
			ConstructorArgumentValues constructorArguments = beanDefinition.getConstructorArgumentValues();
			constructorArguments.addIndexedArgumentValue(0, addBasePackages(constructorArguments, packageNames));
		}
		else {
			//如果该bean尚未注册,则注册该bean,参数中提供的包名称会被设置到bean定义中去
			GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
			beanDefinition.setBeanClass(BasePackages.class);
			beanDefinition.getConstructorArgumentValues().addIndexedArgumentValue(0, packageNames);
			beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
			registry.registerBeanDefinition(BEAN, beanDefinition);
		}
	}

由上可知,AutoConfigurationPackages.Registrar这个类就干一个事,注册一个 Bean ,这个 Bean 就是 org.springframework.boot.autoconfigure.AutoConfigurationPackages.BasePackages ,它有 一个参数,这个参数是使用了 @AutoConfigurationPackage 这个注解的类所在的包路径,保存自动配置 类以供之后的使用,比如给 JPA entity 扫描器用来扫描开发人员通过注解 @Entity 定义的 entity 类。


(二)Import(AutoConfigurationImportSelector.class)


@Import({AutoConfigurationImportSelector.class}) :将


AutoConfigurationImportSelector 这个类导入到 Spring 容器中。


AutoConfigurationImportSelector 可以帮助 Springboot 应用将所有符合条件的 @Configuration


配置都加载到当前 SpringBoot 创建并使用的 IOC 容器 ( ApplicationContext ) 中。


AutoConfigurationImportSelector 重点是实现了 DeferredImportSelector 接口和各种


Aware 接口,然后 DeferredImportSelector 接口又继承了 ImportSelector 接口。


其不光实现了 ImportSelector 接口,还实现了很多其它的 Aware 接口,分别表示在某个时机会被回调。


三. @ComponentScan注解

@ComponentScan使用

package org.springframework.context.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import org.springframework.beans.factory.support.BeanNameGenerator;
import org.springframework.core.annotation.AliasFor;
import org.springframework.core.type.filter.TypeFilter;


@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Repeatable(ComponentScans.class)
public @interface ComponentScan {


	@AliasFor("basePackages")
	String[] value() default {};


	@AliasFor("value")
	String[] basePackages() default {};


	Class<?>[] basePackageClasses() default {};


	Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;


	Class<? extends ScopeMetadataResolver> scopeResolver() default AnnotationScopeMetadataResolver.class;


	ScopedProxyMode scopedProxy() default ScopedProxyMode.DEFAULT;


	String resourcePattern() default ClassPathScanningCandidateComponentProvider.DEFAULT_RESOURCE_PATTERN;


	boolean useDefaultFilters() default true;


	Filter[] includeFilters() default {};


	Filter[] excludeFilters() default {};


	boolean lazyInit() default false;



	@Retention(RetentionPolicy.RUNTIME)
	@Target({})
	@interface Filter {


		FilterType type() default FilterType.ANNOTATION;


		@AliasFor("classes")
		Class<?>[] value() default {};


		@AliasFor("value")
		Class<?>[] classes() default {};


		String[] pattern() default {};

	}

}


主要是从定义的扫描路径中,找出标识了需要装配的类自动装配到 spring 的 bean 容器中。


常用属性如下:


  • basePackages、value:指定扫描路径,如果为空则以@ComponentScan注解的类所在的包为基本的扫描路径
  • basePackageClasses:指定具体扫描的类
  • includeFilters:指定满足Filter条件的类
  • excludeFilters:指定排除Filter条件的类

includeFilters 和 excludeFilters 的 FilterType 可选: ANNOTATION= 注解类型 默认、


ASSIGNABLE_TYPE( 指定固定类 ) 、 ASPECTJ(ASPECTJ 类型 ) 、 REGEX( 正则表达式 ) 、 CUSTOM( 自定义类型 ) ,自定义的 Filter 需要实现 TypeFilter 接口。


@ComponentScan 的配置如下:


@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = 
      
TypeExcludeFilter.class), 
      
@Filter(type = FilterType.CUSTOM, classes = 
      
AutoConfigurationExcludeFilter.class) })


借助 excludeFilters 将 TypeExcludeFillter 及 FilterType 这两个类进行排除


当前 @ComponentScan 注解没有标注 basePackages 及 value ,所以扫描路径默认为 @ComponentScan注解的类所在的包为基本的扫描路径(也就是标注了 @SpringBootApplication 注解的项目启动类所在的路径)


综上所述:


@EnableAutoConfiguration注解是通过@Import注解加载了自动配置固定的bean


@ComponentScan注解自动进行注解扫描


那么真正根据包扫描,把组件类生成实例对象存到 IOC 容器中,又是怎么来完成的?接下来剖析


四.SpringBoot的自动配置源码剖析


跟自动配置逻辑相关的入口方法在 DeferredImportSelectorGrouping 类的 getImports 方法处,


因此我们就从 DeferredImportSelectorGrouping 类的 getImports 方法来开始分析 SpringBoot 的自


动配置源码好了。


getImports 方法代码:


public Iterable<Group.Entry> getImports() {
            // 遍历DeferredImportSelectorHolder对象集合deferredImports,deferredImports集    合 装了各种ImportSelector,当然这里装的是AutoConfigurationImportSelector
			for (DeferredImportSelectorHolder deferredImport : this.deferredImports) {
                // 【1】,利用AutoConfigurationGroup的process方法来处理自动配置的相关逻辑,决定导入哪些配置类(这个是我们分析的重点,自动配置的逻辑全在这了)
				this.group.process(deferredImport.getConfigurationClass().getMetadata(),
						deferredImport.getImportSelector());
			}
            // 【2】,经过上面的处理后,然后再进行选择导入哪些配置类
			return this.group.selectImports();
		}


标 【 1 】 处的的代码是我们分析的 重中之重 ,自动配置的相关的绝大部分逻辑全在这里了。那么


this.group.process(deferredImport.getConfigurationClass().getMetadata(),


deferredImport.getImportSelector()) ;主要做的事情就是在 this.group


AutoConfigurationGroup 对象的 process 方法中,传入的 AutoConfigurationImportSelector


对象来选择一些符合条件的自动配置类,过滤掉一些不符合条件的自动配置类,就是这么个事情。



注:

AutoConfigurationGroup :是 AutoConfigurationImportSelector 的内部类,主要用来处理自动配置相关的逻辑,拥有 process 和 selectImports 方法,然后拥有 entries 和

autoConfigurationEntries 集合属性,这两个集合分别存储被处理后的符合条件的自动配置类,我们知道这些就足够了;

AutoConfigurationImportSelector :承担自动配置的绝大部分逻辑,负责选择一些符合条件的自动配置类;

metadata: 标注在 SpringBoot 启动类上的 @SpringBootApplication 注解元数据 。

标【 2 】的 this.group.selectImports 的方法主要是针对前面的 process 方法处理后的自动配置类再进一步有选择的选择导入



再进入到 AutoConfigurationImportSelector$AutoConfigurationGroup 的 pross 方法:


springboot main 方法加载日志配置 springboot加载配置文件源码_java_03


通过图中我们可以看到,跟自动配置逻辑相关的入口方法在process方法中。

(一)分析自动配置的主要逻辑

// 这里用来处理自动配置类,比如过滤掉不符合匹配条件的自动配置类
		@Override
		public void process(AnnotationMetadata annotationMetadata, DeferredImportSelector deferredImportSelector) {
			Assert.state(deferredImportSelector instanceof AutoConfigurationImportSelector,
					() -> String.format("Only %s implementations are supported, got %s",
							AutoConfigurationImportSelector.class.getSimpleName(),
							deferredImportSelector.getClass().getName()));

			// 【1】,调用getAutoConfigurationEntry方法得到自动配置类放入autoConfigurationEntry对象中
			AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector) deferredImportSelector)
					.getAutoConfigurationEntry(getAutoConfigurationMetadata(), annotationMetadata);

			// 【2】,又将封装了自动配置类的autoConfigurationEntry对象装进autoConfigurationEntries集合
			this.autoConfigurationEntries.add(autoConfigurationEntry);
			// 【3】,遍历刚获取的自动配置类
			for (String importClassName : autoConfigurationEntry.getConfigurations()) {
				// 这里符合条件的自动配置类作为key,annotationMetadata作为值放进entries集合
				this.entries.putIfAbsent(importClassName, annotationMetadata);
			}
		}


上面代码中我们再来看标 【 1 】 的方法 getAutoConfigurationEntry ,这个方法主要是用来获取自动配置类有关,承担了自动配置的主要逻辑。直接上代码:


protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata,
			AnnotationMetadata annotationMetadata) {
		// 获取是否有配置spring.boot.enableautoconfiguration属性,默认返回true
		if (!isEnabled(annotationMetadata)) {
			return EMPTY_ENTRY;
		}
		AnnotationAttributes attributes = getAttributes(annotationMetadata);

		// 【1】得到spring.factories文件配置的所有自动配置类
		List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);

		// 利用LinkedHashSet移除重复的配置类
		configurations = removeDuplicates(configurations);

		// 得到要排除的自动配置类,比如注解属性exclude的配置类
		// 比如:@SpringBootApplication(exclude = FreeMarkerAutoConfiguration.class)
		// 将会获取到exclude = FreeMarkerAutoConfiguration.class的注解数据
		Set<String> exclusions = getExclusions(annotationMetadata, attributes);

		// 检查要被排除的配置类,因为有些不是自动配置类,故要抛出异常
		checkExcludedClasses(configurations, exclusions);

		// 【2】将要排除的配置类移除
		configurations.removeAll(exclusions);

		// 【3】因为从spring.factories文件获取的自动配置类太多,如果有些不必要的自动配置类都加载进内存,会造成内存浪费,因此这里需要进行过滤
		configurations = filter(configurations, autoConfigurationMetadata);

		// 【4】获取了符合条件的自动配置类后,此时触发AutoConfigurationImportEvent事件,
		// 目的是告诉ConditionEvaluationReport条件评估报告器对象来记录符合条件的自动配置类
		fireAutoConfigurationImportEvents(configurations, exclusions);

		// 【5】将符合条件和要排除的自动配置类封装进AutoConfigurationEntry对象,并返回
		return new AutoConfigurationEntry(configurations, exclusions);
	}


深入【1】中的getCandidateConfigurations 方法


protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
		// 这个方法需要传入两个参数getSpringFactoriesLoaderFactoryClass()和getBeanClassLoader()
		// getSpringFactoriesLoaderFactoryClass()这个方法返回的是EnableAutoConfiguration.class
		// getBeanClassLoader()这个方法返回的是beanClassLoader(类加载器)
		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;
	}


这个方法中有一个重要方法 loadFactoryNames ,这个方法是让 SpringFactoryLoader 去加载一些组件的名字。


继续点开 loadFactoryNames方法


public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
        //获取传入的键        
        String factoryTypeName = factoryType.getName();
        return (List)loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());
    }

    private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
        MultiValueMap<String, String> result = (MultiValueMap)cache.get(classLoader);
        if (result != null) {
            return result;
        } else {
            try {
                //如果类加载器不为null,则加载类路径下spring.factories文件,将其中设置的 配置类的全路径信息封装 为Enumeration类对象
                Enumeration<URL> urls = classLoader != null ? classLoader.getResources("META-INF/spring.factories") : ClassLoader.getSystemResources("META-INF/spring.factories");
                LinkedMultiValueMap result = new LinkedMultiValueMap();

                //循环Enumeration类对象,根据相应的节点信息生成Properties对象,通过传入 的键获取值,在将值切割为一个个小的字符串转化为Array,返回result集合中
                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()) {
                        Entry<?, ?> entry = (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);
            }
        }
    }


从代码中我们可以知道,在这个方法中会遍历整个 ClassLoader 中所有 jar 包下的 spring.factories 文件。


spring.factories 里面保存着 springboot 的默认提供的自动配置类。


META-INF/spring.factories


springboot main 方法加载日志配置 springboot加载配置文件源码_spring boot_04


 


AutoConfigurationEntry 方法主要做的事情就是获取符合条件的自动配置类,避免加载不必要的自


动配置类从而造成内存浪费。我们下面总结下 AutoConfigurationEntry 方法主要做的事情:


【 1 】从 spring.factories 配置文件中加载 EnableAutoConfiguration 自动配置类) , 获取的自动配


置类如图所示。


【2 】若 @EnableAutoConfiguration 等注解标有要 exclude 的自动配置类,那么再将这个自动配置类 排除掉;


【3 】排除掉要 exclude 的自动配置类后,然后再调用 filter 方法进行进一步的过滤,再次排除一些


不符合条件的自动配置类;


【4 】经过重重过滤后,此时再触发 AutoConfigurationImportEvent 事件,告诉


ConditionEvaluationReport 条件评估报告器对象来记录符合条件的自动配置类;


【 5 】 最后再将符合条件的自动配置类返回。


总结了 AutoConfigurationEntry 方法主要的逻辑后,我们再来细看一下


AutoConfigurationImportSelector 的 filter 方法:


springboot main 方法加载日志配置 springboot加载配置文件源码_springboot自动配置_05


 


private List<String> filter(List<String> configurations, AutoConfigurationMetadata autoConfigurationMetadata) {
		long startTime = System.nanoTime();
		// 将从spring.factories中获取的自动配置类转出字符串数组
		String[] candidates = StringUtils.toStringArray(configurations);
		// 定义skip数组,是否需要跳过。注意skip数组与candidates数组顺序一一对应
		boolean[] skip = new boolean[candidates.length];
		// getAutoConfigurationImportFilters方法:拿到OnBeanCondition,OnClassCondition和OnWebApplicationCondition
		// 然后遍历这三个条件类去过滤从spring.factories加载的大量配置类
		boolean skipped = false;
		for (AutoConfigurationImportFilter filter : getAutoConfigurationImportFilters()) {
			// 调用各种aware方法,将beanClassLoader,beanFactory等注入到filter对象中,
			// 这里的filter对象即OnBeanCondition,OnClassCondition或OnWebApplicationCondition
			invokeAwareMethods(filter);
			// 判断各种filter来判断每个candidate(这里实质要通过candidate(自动配置类)拿到其标注的
			// @ConditionalOnClass,@ConditionalOnBean和@ConditionalOnWebApplication里面的注解值)是否匹配,
			// 注意candidates数组与match数组一一对应
			/******************************************************/
			boolean[] match = filter.match(candidates, autoConfigurationMetadata);
			// 遍历match数组,注意match顺序跟candidates的自动配置类一一对应
			for (int i = 0; i < match.length; i++) {
				// 若有不匹配的话
				if (!match[i]) {
					// 不匹配的将记录在skip数组,标志skip[i]为true,也与candidates数组一一对应
					skip[i] = true;
					// 因为不匹配,将相应的自动配置类置空
					candidates[i] = null;
					// 标注skipped为true
					skipped = true;
				}
			}
		}
		if (!skipped) {
			return configurations;
		}
		List<String> result = new ArrayList<>(candidates.length);
		for (int i = 0; i < candidates.length; i++) {
			// 这里表示若所有自动配置类经过OnBeanCondition,OnClassCondition和OnWebApplicationCondition过滤后,全部都匹配的话,则全部原样返回
			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);
	}


AutoConfigurationImportSelector 的 filter 方法主要做的事情就是调用


AutoConfigurationImportFilter 接口的 match 方法来判断每一个自动配置类上的条件注解(若有的话) @ConditionalOnClass , @ConditionalOnBean 或 @ConditionalOnWebApplication 是否满足条件,若满足,则返回true,说明匹配,若不满足,则返回false说明不匹配。


(二)有选择的导入自动配置类

this.group.selectImports 方法是如何进一步有选择的导入自动配置类的。直接看代码:

public Iterable<Entry> selectImports() {
			if (this.autoConfigurationEntries.isEmpty()) {
				return Collections.emptyList();
			}
			// 这里得到所有要排除的自动配置类的set集合
			Set<String> allExclusions = this.autoConfigurationEntries.stream()
					.map(AutoConfigurationEntry::getExclusions).flatMap(Collection::stream).collect(Collectors.toSet());
			// 这里得到经过滤后所有符合条件的自动配置类的set集合
			Set<String> processedConfigurations = this.autoConfigurationEntries.stream()
					.map(AutoConfigurationEntry::getConfigurations).flatMap(Collection::stream)
					.collect(Collectors.toCollection(LinkedHashSet::new));
			// 移除掉要排除的自动配置类
			processedConfigurations.removeAll(allExclusions);

			// 对标注有@Order注解的自动配置类进行排序,
			return sortAutoConfigurations(processedConfigurations, getAutoConfigurationMetadata()).stream()
					.map((importClassName) -> new Entry(this.entries.get(importClassName), importClassName))
					.collect(Collectors.toList());
		}


可以看到, selectImports 方法主要是针对经过排除掉 exclude 的和被


AutoConfigurationImportFilter 接口过滤后的满足条件的自动配置 类再进一步排除 exclude 的自


动配置类 ,然后再排序。


(三)关于条件注解的讲解


@Conditional 是 Spring4 新提供的注解,它的作用是按照一定的条件进行判断,满足条件给容器注册bean 。


  • @ConditionalOnBean:仅仅在当前上下文中存在某个对象时,才会实例化一个Bean。
  • @ConditionalOnClass:某个class位于类路径上,才会实例化一个Bean。
  • @ConditionalOnExpression:当表达式为true的时候,才会实例化一个Bean。基于SpEL表达式的条件判断。
  • @ConditionalOnMissingBean:仅仅在当前上下文中不存在某个对象时,才会实例化一个Bean。
  • @ConditionalOnMissingClass:某个class类路径上不存在的时候,才会实例化一个Bean。
  • @ConditionalOnNotWebApplication:不是web应用,才会实例化一个Bean。
  • @ConditionalOnWebApplication:当项目是一个Web项目时进行实例化。
  • @ConditionalOnNotWebApplication:当项目不是一个Web项目时进行实例化。
  • @ConditionalOnProperty:当指定的属性有指定的值时进行实例化。
  • @ConditionalOnJava:当JVM版本为指定的版本范围时触发实例化。
  • @ConditionalOnResource:当类路径下有指定的资源时触发实例化。
  • @ConditionalOnJndi:在JNDI存在的条件下触发实例化。
  • @ConditionalOnSingleCandidate:当指定的Bean在容器中只有一个,或者有多个但是指定了首选的Bean时触发实例化。


五. 总结SpringBoot自动配置的原理

(一)主要做了以下事情:

1. spring.factories配置文件中加载自动配置类;

2. 加载的自动配置类中排除掉 @EnableAutoConfiguration 注解的 exclude 属性指定的自动配置类;

3. 然后再用 AutoConfigurationImportFilter 接口去过滤自动配置类是否符合其标注注解(若有标注的话) @ConditionalOnClass , @ConditionalOnBean @ConditionalOnWebApplication 的条件,若都符合的话则返回匹配结果;

4. 然后触发 AutoConfigurationImportEvent 事件,告诉 ConditionEvaluationReport 条件评估报告器对象来分别记录符合条件和 exclude 的自动配置类。

5. 最后spring再将最后筛选后的自动配置类导入IOC容器中

六.以 HttpEncodingAutoConfiguration ( Http 编码自动配置)为例解释自动配置原理

springboot main 方法加载日志配置 springboot加载配置文件源码_springboot自动配置_06

 

/*
 * Copyright 2012-2019 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.boot.autoconfigure.web.servlet;

import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.autoconfigure.http.HttpProperties;
import org.springframework.boot.autoconfigure.http.HttpProperties.Encoding.Type;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.web.server.WebServerFactoryCustomizer;
import org.springframework.boot.web.servlet.filter.OrderedCharacterEncodingFilter;
import org.springframework.boot.web.servlet.server.ConfigurableServletWebServerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.web.filter.CharacterEncodingFilter;

/**
 * {@link EnableAutoConfiguration Auto-configuration} for configuring the encoding to use
 * in web applications.
 *
 * @author Stephane Nicoll
 * @author Brian Clozel
 * @since 2.0.0
 */
// 表示这是一个配置类,和以前编写的配置文件一样,也可以给容器中添加组件
@Configuration(proxyBeanMethods = false)

// 启动指定类的ConfigurationProperties功能;将配置文件中对应的值和HttpEncodingProperties绑定起来;
@EnableConfigurationProperties(HttpProperties.class)

// Spring底层@Conditional注解,根据不同的条件,如果满足指定的条件,整个配置类里面的配置就会生效。
// 判断当前应用是否是web应用,如果是,当前配置类生效
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)

// 判断当前项目有没有这个CharacterEncodingFilter : SpringMVC中进行乱码解决的过滤器
@ConditionalOnClass(CharacterEncodingFilter.class)

// 判断配置文件中是否存在某个配置 spring.http.encoding.enabled 如果不存在,判断也是成立的
// matchIfMissing = true 表示即使我们配置文件中不配置spring.http.encoding.enabled=true,也是默认生效的
@ConditionalOnProperty(prefix = "spring.http.encoding", value = "enabled", matchIfMissing = true)
public class HttpEncodingAutoConfiguration {

	// 它已经和SpringBoot配置文件中的值进行映射了
	private final HttpProperties.Encoding properties;

	// 只有一个有参构造器的情况下,参数的值就会从容器中拿
	public HttpEncodingAutoConfiguration(HttpProperties properties) {
		this.properties = properties.getEncoding();
	}

	@Bean  //给容器中添加一个组件,这个组件中的某些值需要从properties中获取
	@ConditionalOnMissingBean  //判断容器中没有这个组件
	public CharacterEncodingFilter characterEncodingFilter() {
		CharacterEncodingFilter filter = new OrderedCharacterEncodingFilter();
		filter.setEncoding(this.properties.getCharset().name());
		filter.setForceRequestEncoding(this.properties.shouldForce(Type.REQUEST));
		filter.setForceResponseEncoding(this.properties.shouldForce(Type.RESPONSE));
		return filter;
	}
}


根据当前不同的条件判断,决定这个配置类是否生效。


一旦这个配置类生效,这个配置类就会给容器中添加各种组件;这些组件的属性是从对应的


properties 类中获取的,这些类里面的每一个属性又是和配置文件绑定的。



# 我们能配置的属性都是来源于这个功能的 properties 类

spring.http.encoding.enabled = true

spring.http.encoding.charset = utf-8

spring.http.encoding.force = true



所有在配置文件中能配置的属性都是在 xxxProperties 类中封装着,配置文件能配置什么就可以参照某个功能对应的这个属性类。



// 从配置文件中获取指定的值和 bean 的属性进行绑定

@ConfigurationProperties ( prefix = "spring.http.encoding" )

public class HttpEncodingProperties {

public static final Charset DEFAULT_CHARSET = Charset . forName ( "UTF-8" );


精髓


1. SpringBoot 启动会加载大量的自动配置类


2. 我们看我们需要实现的功能有没有 SpringBoot 默认写好的自动配置类


3. 我们再来看这个自动配置类中到底配置了哪些组件;(只要我们有我们要用的组件,我们就不需要再来配置了)


4. 给容器中自动配置类添加组件的时候,会从 properties 类中获取某些属性,我们就可以在配置文件中指定这些属性的值。


xxxAutoConfiguration :自动配置类,用于给容器中添加组件从而代替之前我们手动完成大量繁


琐的配置。


xxxProperties : 封装了对应自动配置类的默认属性值,如果我们需要自定义属性值,只需要根据


xxxProperties 寻找相关属性在配置文件设值即可。