深入剖析Spring Boot(二)自动化配置的扫描和解析

前言

上篇SpringBoot 启动原理中提到,SpringBoot应用还是去调用ApplicationContext的refresh()方法实现应用的整体配置,不同的是SpringBoot一般没有xml的配置文件,而是自动化配置,那么其bean的扫描过程和注册又有什么不同呢,下面来详细分析。

refresh()

refresh()是Spring应用启动的核心方法,采用了模板模式,不同的ApplicationContext实现差别较大,基于xml配置的Spring应用会在

ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

时就完成了bean的定位,解析和注册过程,而SpringBoot或者说基于注解配置的Spring应用则是通过扩展的方式来的(后续在写一篇介绍Spring各种扩展和一些框架是怎么适配Spring的)。其具体的解析是在
invokeBeanFactoryPostProcessors(beanFactory); 时发生,下面来详细分析Bean的扫描和注册过程,部分代码会忽略掉。

for (BeanFactoryPostProcessor postProcessor : beanFactoryPostProcessors) {
	if (postProcessor instanceof BeanDefinitionRegistryPostProcessor) {
			BeanDefinitionRegistryPostProcessor registryProcessor =
							(BeanDefinitionRegistryPostProcessor) postProcessor;
			registryProcessor.postProcessBeanDefinitionRegistry(registry);
			registryProcessors.add(registryProcessor);
		}
	else {
			regularPostProcessors.add(postProcessor);
	}
}

可以看到BeanDefinitionRegistryPostProcessor 是直接处理的,其优先级是高于BeanFactoryPostProcessor的,这里我们关注的一个扩展类是ConfigurationClassPostProcessor,在其实现
postProcessBeanDefinitionRegistry()方法中我们可以看到processConfigBeanDefinitions(registry)方法,这个是整个解析的核心:

List<BeanDefinitionHolder> configCandidates = new ArrayList<>();
		String[] candidateNames = registry.getBeanDefinitionNames();
		
		// 所有注册的组件,扫描时除了spring自身的组件我们关注的就是我们的应用启动入口类
		for (String beanName : candidateNames) {
			BeanDefinition beanDef = registry.getBeanDefinition(beanName);
			if (ConfigurationClassUtils.isFullConfigurationClass(beanDef) ||
					ConfigurationClassUtils.isLiteConfigurationClass(beanDef)) {
				if (logger.isDebugEnabled()) {
					logger.debug("Bean definition has already been processed as a configuration class: " + beanDef);
				}
			}
			else if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) {
				configCandidates.add(new BeanDefinitionHolder(beanDef, beanName));
			}
		}

		// Return immediately if no @Configuration classes were found
		if (configCandidates.isEmpty()) {
			return;
		}

		// Sort by previously determined @Order value, if applicable
		configCandidates.sort((bd1, bd2) -> {
			int i1 = ConfigurationClassUtils.getOrder(bd1.getBeanDefinition());
			int i2 = ConfigurationClassUtils.getOrder(bd2.getBeanDefinition());
			return (i1 < i2) ? -1 : (i1 > i2) ? 1 : 0;
		});

		// Detect any custom bean name generation strategy supplied through the enclosing application context
		SingletonBeanRegistry sbr = null;
		if (registry instanceof SingletonBeanRegistry) {
			sbr = (SingletonBeanRegistry) registry;
			if (!this.localBeanNameGeneratorSet) {
				BeanNameGenerator generator = (BeanNameGenerator) sbr.getSingleton(CONFIGURATION_BEAN_NAME_GENERATOR);
				if (generator != null) {
					this.componentScanBeanNameGenerator = generator;
					this.importBeanNameGenerator = generator;
				}
			}
		}

		if (this.environment == null) {
			this.environment = new StandardEnvironment();
		}

		// Parse each @Configuration class
		ConfigurationClassParser parser = new ConfigurationClassParser(
				this.metadataReaderFactory, this.problemReporter, this.environment,
				this.resourceLoader, this.componentScanBeanNameGenerator, registry);

		Set<BeanDefinitionHolder> candidates = new LinkedHashSet<>(configCandidates);
		Set<ConfigurationClass> alreadyParsed = new HashSet<>(configCandidates.size());
		do {
			// 经过过滤后candidates 只包含我们启动入口类
			**parser.parse(candidates);**
			parser.validate();

			Set<ConfigurationClass> configClasses = new LinkedHashSet<>(parser.getConfigurationClasses());
			configClasses.removeAll(alreadyParsed);

			// Read the model and create bean definitions based on its content
			if (this.reader == null) {
				this.reader = new ConfigurationClassBeanDefinitionReader(
						registry, this.sourceExtractor, this.resourceLoader, this.environment,
						this.importBeanNameGenerator, parser.getImportRegistry());
			}
			//注册所有扫描的bean
			this.reader.loadBeanDefinitions(configClasses);
			alreadyParsed.addAll(configClasses);

parse方法是真正解析基于注解配置的方法,Springboot 的自动化配置也是在这一步就完成了的,去掉部分代码,核心部分是下面两句:

if (this.conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.PARSE_CONFIGURATION)) {
			return;
		}
		// Recursively process the configuration class and its superclass hierarchy.
		SourceClass sourceClass = asSourceClass(configClass);
		do {
			sourceClass = doProcessConfigurationClass(configClass, sourceClass);
		}
		while (sourceClass != null);

		this.configurationClasses.put(configClass, configClass);

为了说明shouldSkip 的作用这里拿DataSourceAutoConfiguration 举个例子

@Configuration
@ConditionalOnClass({DataSource.class, EmbeddedDatabaseType.class})
@EnableConfigurationProperties({DataSourceProperties.class})
@Import({DataSourcePoolMetadataProvidersConfiguration.class, DataSourceInitializationConfiguration.class})

@ConditionalOnClass这个注解说明当前classPath下包含DataSource, EmbeddedDatabaseType 时才会生效,@Conditional 系列注解是在Spring 4.0时推出的,目的是条件话配置,可以指定在一定条件下配置才会生效,spring 主要提供了以下几种条件配置:

@ConditionalOnBean(仅仅在当前上下文中存在某个对象时,才会实例化一个Bean)
@ConditionalOnClass(某个class位于类路径上,才会实例化一个Bean)
@ConditionalOnExpression(当表达式为true的时候,才会实例化一个Bean)
@ConditionalOnMissingBean(仅仅在当前上下文中不存在某个对象时,才会实例化一个Bean)
@ConditionalOnMissingClass(某个class类路径上不存在的时候,才会实例化一个Bean)
@ConditionalOnNotWebApplication(不是web应用)

shouldSkip的作用就是在扫描时检查这些条件决定是否过滤还是解析这些配置类。上面我们说过,正常情况下第一次到parse 调用 processConfigurationClass时,只包含入口类,假设其满足条件配置(否则应用配置将无法扫描),下一步就是while循环并递归解析配置类

SourceClass sourceClass = asSourceClass(configClass);
do {
	sourceClass = doProcessConfigurationClass(configClass, sourceClass);
}
while (sourceClass != null);

递归是在doProcessConfigurationClass 中进行的

protected final SourceClass doProcessConfigurationClass(ConfigurationClass configClass, SourceClass sourceClass)
			throws IOException {

		// Recursively process any member (nested) classes first
		processMemberClasses(configClass, sourceClass);

	

首先是解析嵌套类,在自动化配置时,嵌套类十分常见,但是这里暂时先不考虑各种自动化配置的解析,还是上面所说,第一次执行到这里来时只有我们的入口类,我们自己的配置和自动化配置都还未扫描到,继续往下看:

		// 处理所有的 @ComponentScan 注解
		Set<AnnotationAttributes> componentScans = AnnotationConfigUtils.attributesForRepeatable(
				sourceClass.getMetadata(), ComponentScans.class, ComponentScan.class);
		if (!componentScans.isEmpty() &&
				!this.conditionEvaluator.shouldSkip(sourceClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN)) {
			for (AnnotationAttributes componentScan : componentScans) {
				// 扫描指定路径下的所有Spring组件
				Set<BeanDefinitionHolder> scannedBeanDefinitions =
						this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());
				// Check the set of scanned definitions for any further config classes and parse recursively if needed
				for (BeanDefinitionHolder holder : scannedBeanDefinitions) {
					if (ConfigurationClassUtils.checkConfigurationClassCandidate(
							holder.getBeanDefinition(), this.metadataReaderFactory)) {
						parse(holder.getBeanDefinition().getBeanClassName(), holder.getBeanName());
					}
				}
			}
		}

回头想一下入口类上@SpringBootApplication的组成,其中是包含了@ComponentScan 注解的,这意味着我们的注解配置是再这个时候扫描并解析的,配置类的扫描是基于 ClassPathBeanDefinitionScanner doScan完成的:

protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
		Assert.notEmpty(basePackages, "At least one base package must be specified");
		Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>();
		for (String basePackage : basePackages) {
			Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
			for (BeanDefinition candidate : candidates) {
				ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);
				candidate.setScope(scopeMetadata.getScopeName());
				String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);
				if (candidate instanceof AbstractBeanDefinition) {
					postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);
				}
				if (candidate instanceof AnnotatedBeanDefinition) {
					AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);
				}
				if (checkCandidate(beanName, candidate)) {
					BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
					definitionHolder =
							AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
					beanDefinitions.add(definitionHolder);
					registerBeanDefinition(definitionHolder, this.registry);
				}
			}
		}
		return beanDefinitions;
	}

核心方法有两个,一个是:**findCandidateComponents(basePackage)用于扫描所有的spring组件,一个是registerBeanDefinition(definitionHolder, this.registry)**用于注册扫描到的组件,直到这里到这里还是没有看到关于自动化配置的处理,再往下我们看到

processImports(configClass, sourceClass, getImports(sourceClass), true);

这里才是spring自动化配置的关键,@EnableAutoConfiguration中我们需要注意@Import(AutoConfigurationImportSelector.class) 这个注解,上面的方法正是处理所有Imports 的配置类的,在处理时会发现一个有意思的变量 importStack ,每次处理前会调用

this.importStack.push(configClass);

处理后:

finally {
	this.importStack.pop();
}

其实作用也很简单,是为了处理这么一种情况:配置之间相互@Import,当spring检测到这种情况时会抛出异常(这种方式在spring检测bean之间的循环依赖时也有相似的原理):

if (checkForCircularImports && isChainedImportOnStack(configClass)) {
			this.problemReporter.error(new CircularImportProblem(configClass, this.importStack));
		}

在处理@Imports时分为了三种情况:

if (candidate.isAssignable(ImportSelector.class)) {
						// Candidate class is an ImportSelector -> delegate to it to determine imports
						Class<?> candidateClass = candidate.loadClass();
						ImportSelector selector = BeanUtils.instantiateClass(candidateClass, ImportSelector.class);
						ParserStrategyUtils.invokeAwareMethods(
								selector, this.environment, this.resourceLoader, this.registry);
						if (this.deferredImportSelectors != null && selector instanceof DeferredImportSelector) {
							this.deferredImportSelectors.add(
									new DeferredImportSelectorHolder(configClass, (DeferredImportSelector) selector));
						}
						else {
							String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());
							Collection<SourceClass> importSourceClasses = asSourceClasses(importClassNames);
							processImports(configClass, currentSourceClass, importSourceClasses, false);
						}
					}

首先我们还是从入口配置类开始(因为这些的扫描,解析几乎都是递归),开始时会进入上述方法,注意
String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());,这个selector 我们是可以从入口类查得到的:AutoConfigurationImportSelector,下面这个方法才是整个自动化配置开始的地方:

@Override
	public String[] selectImports(AnnotationMetadata annotationMetadata) {
		// 通过 EnableAutoConfiguration 判断是否开始自动化配置
		if (!isEnabled(annotationMetadata)) {
			return NO_IMPORTS;
		}
		try {
			AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
					.loadMetadata(this.beanClassLoader);
			AnnotationAttributes attributes = getAttributes(annotationMetadata);
			List<String> configurations = getCandidateConfigurations(annotationMetadata,
					attributes);
			configurations = removeDuplicates(configurations);//去重
			configurations = sort(configurations, autoConfigurationMetadata);//排序(实现order接口的配置和各种@@Condition等条件配置)
			Set<String> exclusions = getExclusions(annotationMetadata, attributes);//(根据配置选择需要排除的部分自动化配置)
			checkExcludedClasses(configurations, exclusions);
			configurations.removeAll(exclusions);//移除被排除的自动化配置
			configurations = filter(configurations, autoConfigurationMetadata);//(根据各自动化配置提供的过滤器过滤)
			fireAutoConfigurationImportEvents(configurations, exclusions);//(触发事件(给各自动化配置框架提供的listener))
			return StringUtils.toStringArray(configurations);//(返回自动化配置的完整类名)
		}
		catch (IOException ex) {
			throw new IllegalStateException(ex);
		}
	}

这里简单介绍一下spring如何寻找需要自动化配置的类的,在其autoconfigure jar包的META-INF 下有spring.factories这样一个文件,截取部分配置如下:

# Application Listeners
org.springframework.context.ApplicationListener=\
org.springframework.boot.autoconfigure.BackgroundPreinitializer

# Auto Configuration Import Listeners
org.springframework.boot.autoconfigure.AutoConfigurationImportListener=\
org.springframework.boot.autoconfigure.condition.ConditionEvaluationReportAutoConfigurationImportListener

# Auto Configuration Import Filters
org.springframework.boot.autoconfigure.AutoConfigurationImportFilter=\
org.springframework.boot.autoconfigure.condition.OnClassCondition

# Auto Configure
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,\

spring正是通过扫描该文件获取需要自动化配置的类,再经过上述去重,排序,过滤等操作得到要解析注册的配置类进行bean的各种配置的,这里,我们可以再我们自己的jar里按照spring的规则放置我们自己的自动化配置类,在引入我们自己jar包,就可以达到自动配置的效果。

上述方法仅仅是得到了自动化配置的类名数组,下一步spring递归调用:

Collection<SourceClass> importSourceClasses = asSourceClasses(importClassNames);
processImports(configClass, currentSourceClass, importSourceClasses, false);

接下来我们先看第三种情况(第二种情况会在spring扩展时说明)

else {
// Candidate class not an ImportSelector or ImportBeanDefinitionRegistrar ->
// process it as an @Configuration class
this.importStack.registerImport(urrentSourceClass.getMetadata(),candidate.getMetadata().getClassName());
processConfigurationClass(candidate.asConfigClass(configClass));
}

又回到了processConfigurationClass方法,这里就开始了自动化配置的解析的过程了,接着processConfigurationClass 方法,解析完@Import之后,接下来就是解析@ImportResource,@Bean 等以及解析完后bean的注册相对比较简单,本文就不详细分析了。

总结

SpringBoot 的自动化配置过程绝大部分都是基于Spring注解配置完成的,只是再其基础上通过@Enable系列注解和spring.factories把自动化配置类加入解析。
整体回顾一下扫描注册的过程:
1:通过refresh() 方法 调用了 BeanDefinitionRegistryPostProcessor 扩展,这里是ConfigurationClassPostProcessor,通过掉用parse 方法, 从入口类开始,通过@ComponentScan注解扫描我们自己的配置类(调用processConfigurationClass解析)和spring组件;
2:处理@Import注解 ,通过入口类上@ EnableAutoConfiguration注解开始调用AutoConfigurationImportSelector 的selectImports方法拿到处理后的自动化配置数组并调用processConfigurationClass解析
3:解析@ImportResource 和@Bean等注解
4:注册到BeanFatory。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值