注:本文源码基于spring-boot 2.2.2.RELEASE

个人理解:要谈自动装配,首先得有组件自动扫描;自动扫描可以看我这一篇【Springboot自动扫描包路径来龙去脉】https://blog.csdn.net/Aqu415/article/details/111455366https://blog.csdn.net/Aqu415/article/details/111455366

入口

在用SpringBoot开发Spring应用的时候,引导类上我们一般会加上  @SpringBootApplication这个注解;

@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
		@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication

这个注解是三个注解的组合注解,这里我们主要研究一下自动装配核心标签@EnableAutoConfiguration里一部分逻辑;见名知意,其意思就是自动装配;

我理解自动装配就是spring boot根据我们引用的jar包、配置文件配置项值、环境变量值、操作系统等等自定义可变量,自动将对应的组件(对象)注入到我们的应用中;

比如我们在pom里依赖了spring-boot-starter-web,那么Springboot就会将嵌入式Tomcat作为web容器并启动一个容器;或者我在yaml中配置了一个redis的地址就可以直接在应用里直接用RedisTemplate等等...

Springboot框架自带提供了多种组件的自动装配,具体可以看以下截图

Springboot提供了这么多的选项,那么加载的入口在哪里呢?我们看看 @EnableAutoConfiguration 这个标签定义

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {

	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 {};

}

可以看出这个注解是  @Import 标签的复合注解(可以认为@EnableAutoConfiguration具有@Import 一样的功能,这是由spring内部提供的一套注解解析api支撑),被引入的配置类是: 

org.springframework.boot.autoconfigure.AutoConfigurationImportSelector 这个类就是自动装配的入口了;熟悉Spring的应该知道,被Import的配置类org.springframework.boot.autoconfigure.AutoConfigurationImportSelector#selectImports方法会在Spring容器生命周期的 invokeBeanFactoryPostProcessors 阶段(bean工厂后置处理器)调用,调用栈如下图

 我们直接来到核心方法

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

@1:从配置文件 spring-autoconfigure-metadata.properties 获得自动装配过滤规则元数据

@2:通过spring-boot的SPI机制,获得所有自动装配配置主类信息

spring-autoconfigure-metadata.properties

首先说明下,这个 spring-autoconfigure-metadata.properties 文件存储的是”待自动装配候选类“过滤的计算规则,这个信息很重要,框架会根据里面的规则逐一对候选类进行计算看是否需要被自动装配进容器,并不是全部加载;

spring-autoconfigure-metadata.properties 内容格式 (自动配置的类全名.条件Condition=值):

org.springframework.boot.autoconfigure.amqp.RabbitAnnotationDrivenConfiguration.ConditionalOnClass=
org.springframework.amqp.rabbit.annotation.EnableRabbit

--------------------------------------------------------------------------------------------------------------------------------------------------

我们看看第一个核心代码 @1

AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);

这行代码主要逻辑是从 spring-autoconfigure-metadata.properties 里获得自动装配过滤相关的元数据信息。

这些元数据的获得是通过org.springframework.boot.autoconfigure.AutoConfigurationMetadataLoader#loadMetadata(java.lang.ClassLoader, java.lang.String)获得,这里的path是一个固定路径:

protected static final String PATH = "META-INF/spring-autoconfigure-metadata.properties";

静态方法 

	static AutoConfigurationMetadata loadMetadata(ClassLoader classLoader, String path) {
		try {
			Enumeration<URL> urls = (classLoader != null) ? classLoader.getResources(path)
					: ClassLoader.getSystemResources(path);
			Properties properties = new Properties();
			while (urls.hasMoreElements()) {
				properties.putAll(PropertiesLoaderUtils.loadProperties(new UrlResource(urls.nextElement())));
			}
			return loadMetadata(properties);
		}
		catch (IOException ex) {
			throw new IllegalArgumentException("Unable to load @ConditionalOnClass location [" + path + "]", ex);
		}
	}

spring.factories

代码来到上面的 @2  org.springframework.boot.autoconfigure.AutoConfigurationImportSelector#getAutoConfigurationEntry

说明下:spring.factories 这个文件存储了spring-boot所有默认支持的待自动装配候选类

	protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata,
			AnnotationMetadata annotationMetadata) {
		if (!isEnabled(annotationMetadata)) {
			return EMPTY_ENTRY;
		}
// NO.1
		AnnotationAttributes attributes = getAttributes(annotationMetadata);

// NO.2
		List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
		configurations = removeDuplicates(configurations);
		Set<String> exclusions = getExclusions(annotationMetadata, attributes);
		checkExcludedClasses(configurations, exclusions);
		configurations.removeAll(exclusions);
// NO.3
		configurations = filter(configurations, autoConfigurationMetadata);
		fireAutoConfigurationImportEvents(configurations, exclusions);
		return new AutoConfigurationEntry(configurations, exclusions);
	}

NO.1:获得 @EnableAutoConfiguration注解标签上所有的属性值

NO.2:从spring.factories文件里获得 EnableAutoConfiguration key对应的所有自动装配引导类,并去掉一些重复的(因为有可能用户自定义引入了一些重复的类)

             排除需要排除的类,具体操作是通过@SpringBootApplication 注解中的 exclude、excludeName、环境属性中的 spring.autoconfigure.exclude配置

NO.3:根据  spring-autoconfigure-metadata.properties 中配置的规则过虑掉一部分引导类 

接下来我们看看spring.factories文件格式,其本质也是key-value(key和value之间没有接口与实现的关系,就是简单的key-value)格式文件

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\
org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,\
org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration,\
org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration,\
org.springframework.boot.autoconfigure.cloud.CloudServiceConnectorsAutoConfiguration,\

这里每一个配置项都是被  @Configuration 标注的类,一般也会带上 @Import注解、@AutoConfigureAfter或者 @Conditional 衍生注解(反正能够引发一些自定义的逻辑执行,这是spring boot开发套路),最终向容器返回候选者集合并由容器执行后续逻辑。

filter 过滤候选类逻辑

接下来我们看看springboot是如何决定是否要初始化(过滤掉)某些类的。核心方法:

org.springframework.boot.autoconfigure.AutoConfigurationImportSelector#filter

.核心代码

private List<String> filter(List<String> configurations, AutoConfigurationMetadata autoConfigurationMetadata) {
		long startTime = System.nanoTime();
		//@A
		String[] candidates = StringUtils.toStringArray(configurations);
		boolean[] skip = new boolean[candidates.length];
		boolean skipped = false; // 表示当前的候选类是否要被跳过即不被自动装配
		//@B
		for (AutoConfigurationImportFilter filter : getAutoConfigurationImportFilters()) {
			invokeAwareMethods(filter);
			
			//@C
			boolean[] match = filter.match(candidates, autoConfigurationMetadata);
			for (int i = 0; i < match.length; i++) {
			
			//@D
				if (!match[i]) {
					skip[i] = true;
					candidates[i] = null;
					skipped = true;  //当前候选类需要被跳过,即不被自动装配
				}
			}
		}
		
		//@E
		if (!skipped) {  // 没有一个需要跳过,即全部候选类都会被装配
			return configurations;
		}
		List<String> result = new ArrayList<>(candidates.length);
		for (int i = 0; i < candidates.length; i++) {
		
		//@F
			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);
	}

@A:将所有的待处理类(所有jar包里META-INFO/spring.factories里key为org.springframework.boot.autoconfigure.EnableAutoConfiguration的配置)从List转成数组
@B:getAutoConfigurationImportFilters()从spring.factories里获得key为 org.springframework.boot.autoconfigure.AutoConfigurationImportFilter 的配置类,获得的类都是实现了AutoConfigurationImportFilter接口;
    有:OnWebApplicationCondition,OnBeanCondition,OnClassCondition三个类
@C:分别调用3个类的match方法返回boolean[] 数组;
@D:数组中元素为true表示该org.springframework.boot.autoconfigure.EnableAutoConfiguration类合法,为false则表示不合法,需要标记为跳过。
@E:如果其中有一个filter处理结果为“所有类不应该跳过”则直接返回候选类全集
@F:组装返回值,不需要跳过的候选类才加入返回值中

上面代码直接执行 @E 返回的机会较小,只有表示全部候选类都需要被装配才会走这个return 分支    

核心过滤代码是在 @C,即以过滤器为外循环,对所有的候选类计算候选类是否要被跳过

核心:这里我们跟一下 OnClassCondition这个类的match方法

首先调用父类的方法org.springframework.boot.autoconfigure.condition.FilteringSpringBootCondition#match这个方法主要调用子类的 ConditionOutcome[] getOutcomes(String[] autoConfigurationClasses,AutoConfigurationMetadata autoConfigurationMetadata)方法返回一个ConditionOutcome数组,代表每一个候选自动装配类是否应该被忽略

	@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();
		}
	}

这里为了提高效率判断了是否多核cpu,如果是多核cpu则将候选者数组调用resolveOutcomesThreaded方法一分为2处理

		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;
	}

核心方法是 resolveOutcomes->org.springframework.boot.autoconfigure.condition.OnClassCondition.StandardOutcomesResolver#getOutcomes
    ->org.springframework.boot.autoconfigure.condition.OnClassCondition.StandardOutcomesResolver#getOutcome(java.lang.String)
    ->org.springframework.boot.autoconfigure.condition.OnClassCondition.StandardOutcomesResolver#getOutcome(java.lang.String, java.lang.ClassLoader)

    
            private ConditionOutcome getOutcome(String className, ClassLoader classLoader) {
            // ClassNameFilter.MISSING主要通过Class.forName校验类是否存在
            if (ClassNameFilter.MISSING.matches(className, classLoader)) {
                return ConditionOutcome.noMatch(ConditionMessage.forCondition(ConditionalOnClass.class).didNotFind("required class").items(Style.QUOTE, className));
            }
            return null;
        }
        
        这个方法主要意思是properties中xxx.ConditionalOnClass=value类不存在,则返回一个“required class 不存在”的ConditionOutcome(候选的自动装配类会被忽略),否则返回null(候选的自动装配类会被加载)

自定义逻辑

待 xxxAutoConfiguration 类被加入到容器后,就可以根据自己的场景向容器中注入所需的 Bean(spring编程模式大部分是:在xxxAutoConfiguration类上用@Import进行注解,引入需要的类),这样就可以大展身手(自定义扩展)了;例 RabbitAutoConfiguration 代码如下:

@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ RabbitTemplate.class, Channel.class })
@EnableConfigurationProperties(RabbitProperties.class)
@Import(RabbitAnnotationDrivenConfiguration.class) // 引入配置类,该类中有很多@Bean注解的方法,各种@Conditionalxxx
public class RabbitAutoConfiguration {
}

扩展开发

如果你想依赖spring-boot自动装配扩展点扩展,那么这就是你需要做的工作

1、在新建spring.factories文件放在自己工程下,结构如下:

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
x.xx.xxAutoConfiguration,\
x.xxx.xxxAutoConfiguration

2、然后 xxAutoConfiguration 这个类里就可以配合 

@ConditionalOnProperty、@ConditionalOnClass等注入我们能所需的类了

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐