Springboot的bean注册机制(二)

上一篇文章介绍了PostProcessorRegistrationDelegate这个委托类在高层次上对于bean工厂后置处理器的调用分析,得到了一个基本结论,自定义的bean组件都是在ConfigurationClassPostProcessor这个类处理的,我们这次来看看这个类是如何处理bean的扫描、解析与注册的。


ConfigurationClassPostProcessor类分析

/**
 * Derive further bean definitions from the configuration classes in the registry.
 * 从registry中的配置类导出(衍生出)更多的beanDef
 */
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
	int registryId = System.identityHashCode(registry);
	if (this.registriesPostProcessed.contains(registryId)) {
		throw new IllegalStateException(
				"postProcessBeanDefinitionRegistry already called on this post-processor against " + registry);
	}
	if (this.factoriesPostProcessed.contains(registryId)) {
		throw new IllegalStateException(
				"postProcessBeanFactory already called on this post-processor against " + registry);
	}
	this.registriesPostProcessed.add(registryId);

	processConfigBeanDefinitions(registry);
}

这个方法是外部调用该类的一个极其重要的方法,旨在通过registry获取更多的bean。方法内部最后调用了processConfigBeanDefinitions(registry),我们看一下

/**
 * Build and validate a configuration model based on the registry of
 * {@link Configuration} classes.
 */
/**
 * 该方法在springboot的启动过程中只会被调用一次。
 * 最小配置下此时registry已经有7个内部配置类 + 1个springboot启动类
 * @param registry
 */
public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
	List<BeanDefinitionHolder> configCandidates = new ArrayList<>();
	String[] candidateNames = registry.getBeanDefinitionNames();

	for (String beanName : candidateNames) {
		BeanDefinition beanDef = registry.getBeanDefinition(beanName);
		//判断beanDef是否已被当做configuration class处理过
		// 标记一下 ConfigurationClassPostProcessor
		// AutowiredAnnotationBeanPostProcessor
		// RequiredAnnotationBeanPostProcessor
		// CommonAnnotationBeanPostProcessor
		// EventListenerMethodProcessor
		// DefaultEventListenerFactory
		// SharedMetadataReaderFactoryContextInitializer
		// 这里统一称之为A,另外springboot启动类称之为B
		// A,B都不会进入该if分支
		if (ConfigurationClassUtils.isFullConfigurationClass(beanDef) ||
				ConfigurationClassUtils.isLiteConfigurationClass(beanDef)) {
			if (logger.isDebugEnabled()) {
				logger.debug("Bean definition has already been processed as a configuration class: " + beanDef);
			}
		}
		// B会进入该分支,beanDef被判断为合格的候选组件,且被设置isFullConfigurationCandidate
		else if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) {
			configCandidates.add(new BeanDefinitionHolder(beanDef, beanName));
		}
	}

	// Return immediately if no @Configuration classes were found
	// springboot最小配置下,只有springboot启动类一个候选配置类
	if (configCandidates.isEmpty()) {
		return;
	}

	// Sort by previously determined @Order value, if applicable
	// 按照@Order的值进行升序排序
	configCandidates.sort((bd1, bd2) -> {
		int i1 = ConfigurationClassUtils.getOrder(bd1.getBeanDefinition());
		int i2 = ConfigurationClassUtils.getOrder(bd2.getBeanDefinition());
		return Integer.compare(i1, i2);
	});

	// Detect any custom bean name generation strategy supplied through the enclosing application context
	SingletonBeanRegistry sbr = null;
	// registry是DefaultListableBeanFactory类型是SingletonBeanRegistry接口的实现类
	if (registry instanceof SingletonBeanRegistry) {
		sbr = (SingletonBeanRegistry) registry;
		if (!this.localBeanNameGeneratorSet) {
			// 获取注册中心的org.springframework.context.annotation.internalConfigurationBeanNameGenerator
			// 因为该类目前只是一个BeanDef,还未被实例化,则返回null
			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
	// 构造Configuration类解析器
	ConfigurationClassParser parser = new ConfigurationClassParser(
			this.metadataReaderFactory, this.problemReporter, this.environment,
			this.resourceLoader, this.componentScanBeanNameGenerator, registry);

	// 目前的候选配置类只有springboot启动类一个配置类
	Set<BeanDefinitionHolder> candidates = new LinkedHashSet<>(configCandidates);
	Set<ConfigurationClass> alreadyParsed = new HashSet<>(configCandidates.size());
	do {
		// 这句代码很重要,它会解析候选配置类candidates,极有可能会扫描出多个bean并注册到registry
		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());
		}
		// 一般的Configuration配置类中会有@Bean注解的方法,其返回值也会被当做一个BeanDef,是在此处处理的
		// 这句代码也很重要,会扫描出多个bean并注册到registry
		this.reader.loadBeanDefinitions(configClasses);
		// 已处理的配置类集合增加元素
		alreadyParsed.addAll(configClasses);

		// 因为基于当前的候选配置类找到的一堆配置类已经注册到registry中,所以candidate是要清空
		candidates.clear();
		// 方法开头已经说过springboot启动模式最小配置下candidateNames含8个元素
		// 当条件满足时,说明扫描出了更多的bean组件,这些组件当然有可能再衍生出更多的bean
		if (registry.getBeanDefinitionCount() > candidateNames.length) {
			// registry注册中心上的beanDef被当做新的候选配置类,newCandidateNames个数一般大于alreadyParsed的个数
			String[] newCandidateNames = registry.getBeanDefinitionNames();
			Set<String> oldCandidateNames = new HashSet<>(Arrays.asList(candidateNames));

			//通过循环整理出已解析的配置类的类名
			Set<String> alreadyParsedClasses = new HashSet<>();
			for (ConfigurationClass configurationClass : alreadyParsed) {
				alreadyParsedClasses.add(configurationClass.getMetadata().getClassName());
			}
			for (String candidateName : newCandidateNames) {
				//旧的候选配置类不包括新的候选配置类,则继续处理
				if (!oldCandidateNames.contains(candidateName)) {
					BeanDefinition bd = registry.getBeanDefinition(candidateName);
					// 当bd符合Configuration验证且已解析配置类不包含bd时,bd才可以被当做下一个候选配置类继续被解析
					// while循环才会继续
					if (ConfigurationClassUtils.checkConfigurationClassCandidate(bd, this.metadataReaderFactory) &&
							!alreadyParsedClasses.contains(bd.getBeanClassName())) {
						candidates.add(new BeanDefinitionHolder(bd, candidateName));
					}
				}
			}
			candidateNames = newCandidateNames;
		}
	}
	while (!candidates.isEmpty());

	// Register the ImportRegistry as a bean in order to support ImportAware @Configuration classes
	// 注册 org.springframework.context.annotation.ConfigurationClassPostProcessor.importRegistry
	if (sbr != null && !sbr.containsSingleton(IMPORT_REGISTRY_BEAN_NAME)) {
		sbr.registerSingleton(IMPORT_REGISTRY_BEAN_NAME, parser.getImportRegistry());
	}

	//springboot启动时metadataReaderFactory属于ConcurrentReferenceCachingMetadataReaderFactory,不进入该分支
	if (this.metadataReaderFactory instanceof CachingMetadataReaderFactory) {
		// Clear cache in externally provided MetadataReaderFactory; this is a no-op
		// for a shared cache since it'll be cleared by the ApplicationContext.
		((CachingMetadataReaderFactory) this.metadataReaderFactory).clearCache();
	}
}

代码中的注释已经非常清楚,bean组件的扫描发现注册是以springboot启动类为起始点的。最关键的两处代码如下

parser.parse(candidates);
this.reader.loadBeanDefinitions(configClasses);

下面将通过第二节和第三节分别讲述这两行代码的作用

ConfigurationClassParser解析候选组件

parser.parse(candidates)这行代码最终会调用到configurationClassParser.processConfigurationClass(ConfigurationClass configClass)这里,如下

	/**
	 * springboot启动时第一次调用该方法时configClass指代的是springboot启动类
 	 */
	protected void processConfigurationClass(ConfigurationClass configClass) throws IOException {
		// 判断configClass是否应该跳过处理
		if (this.conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.PARSE_CONFIGURATION)) {
			return;
		}

		// springboot启动第一次进入此处时,根据configClass获取到的existingClass
		ConfigurationClass existingClass = this.configurationClasses.get(configClass);
		if (existingClass != null) {
			if (configClass.isImported()) {
				if (existingClass.isImported()) {
					existingClass.mergeImportedBy(configClass);
				}
				// Otherwise ignore new imported config class; existing non-imported class overrides it.
				return;
			}
			else {
				// Explicit bean definition found, probably replacing an import.
				// Let's remove the old one and go with the new one.
				this.configurationClasses.remove(configClass);
				this.knownSuperclasses.values().removeIf(configClass::equals);
			}
		}

		// Recursively process the configuration class and its superclass hierarchy.
		// 递归处理配置类及其父类
		SourceClass sourceClass = asSourceClass(configClass);
		do {
			sourceClass = doProcessConfigurationClass(configClass, sourceClass);
		}
		while (sourceClass != null);

		// doProcessConfigurationClass方法有非常大的可能会再次递归到该方法
		this.configurationClasses.put(configClass, configClass);
	}

这个方法充满了递归思想,processConfigurationClass ===> doProcessConfigurationClass ===>… ===>processConfigurationClass,在解析配置的时候,可能会递归再执行到本方法。

最终,执行到最底层(栈的底部)时,configurationClasses记录着已经处理过的配置类,在上一章的示例中,parser.parse(candidates)执行完后configurationClasses如下:
在这里插入图片描述

想要着重说明的两个类是AppConfig.java和StaffService.java

AppConfig.java是一个配置类,内部定义了一个@Bean方法,返回User。图中可以看到User并没有被当做configClasses的一员。而是作为appConfig这个configClass内部的要素。

StaffService.java是一个@Service类,通过@Import引入了Staff这个类,图中显示其目前是一个没有名字的configClass

这个时候,我们看看registry已经注册了哪些bean?如下:
在这里插入图片描述
可以明显地看出除了 User和Staff这两个bean,其他的都已经被注册了。这两个bean的注册在下一节讲。而图中方框中的bean毫无疑问是在doProcessConfigurationClass(configClass, sourceClass)注册的。

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

	// Recursively process any member (nested) classes first
	// 1 处理内部成员类
	processMemberClasses(configClass, sourceClass);

	// Process any @PropertySource annotations
	// 2 处理@PropertySource注解
	for (AnnotationAttributes propertySource : AnnotationConfigUtils.attributesForRepeatable(
			sourceClass.getMetadata(), PropertySources.class,
			org.springframework.context.annotation.PropertySource.class)) {
		if (this.environment instanceof ConfigurableEnvironment) {
			processPropertySource(propertySource);
		}
		else {
			logger.warn("Ignoring @PropertySource annotation on [" + sourceClass.getMetadata().getClassName() +
					"]. Reason: Environment must implement ConfigurableEnvironment");
		}
	}

	// Process any @ComponentScan annotations
	// 3 处理@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) {
			// The config class is annotated with @ComponentScan -> perform the scan immediately
			// 3a 获取扫描到的Configuration,Component组件,注意这些组件在方法执行过程中
			// 已注册到registry
			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) {
				BeanDefinition bdCand = holder.getBeanDefinition().getOriginatingBeanDefinition();
				if (bdCand == null) {
					bdCand = holder.getBeanDefinition();
				}
				if (ConfigurationClassUtils.checkConfigurationClassCandidate(bdCand, this.metadataReaderFactory)) {
					// 3b 该方法经过一些流程会再次进入doProcessConfigurationClass方法,递归点在这里
					parse(bdCand.getBeanClassName(), holder.getBeanName());
				}
			}
		}
	}

	// 4 Process any @Import annotations
	processImports(configClass, sourceClass, getImports(sourceClass), true);

	// 5 Process any @ImportResource annotations
	AnnotationAttributes importResource =
			AnnotationConfigUtils.attributesFor(sourceClass.getMetadata(), ImportResource.class);
	if (importResource != null) {
		String[] resources = importResource.getStringArray("locations");
		Class<? extends BeanDefinitionReader> readerClass = importResource.getClass("reader");
		for (String resource : resources) {
			String resolvedResource = this.environment.resolveRequiredPlaceholders(resource);
			configClass.addImportedResource(resolvedResource, readerClass);
		}
	}

	// 6 Process individual @Bean methods
	Set<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(sourceClass);
	for (MethodMetadata methodMetadata : beanMethods) {
		configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));
	}

	// 7 Process default methods on interfaces
	processInterfaces(configClass, sourceClass);

	// 8 Process superclass, if any
	if (sourceClass.getMetadata().hasSuperClass()) {
		String superclass = sourceClass.getMetadata().getSuperClassName();
		if (superclass != null && !superclass.startsWith("java") &&
				!this.knownSuperclasses.containsKey(superclass)) {
			this.knownSuperclasses.put(superclass, configClass);
			// Superclass found, return its annotation metadata and recurse
			return sourceClass.getSuperClass();
		}
	}

	// No superclass -> processing is complete
	return null;
}

图片中bean都是在代码区的3a标记处注册的。关于componentScanAnnotationParser.parse(AnnotationAttributes componentScan, final String declaringClass)本篇文章暂时不讲。

bean的扫描处理在doProcessConfigurationClass方法中,如代码区所示,主要体现在8个方面
1)处理内部成员类
2)处理@PropertySource注解
3)处理@ComponentScan注解覆盖到的类
4)处理@Import注解
5)处理@ImportResource注解
6)处理@Bean方法注解
7)处理接口上的默认方法
8)处理父类

着重注意这个方法可能递归调用到自己

ConfigurationClassBeanDefinitionReader解读配置

回过头来,我们再看this.reader.loadBeanDefinitions(configClasses);这行代码,刚才说过User和Staff这两个bean这两个bean就是在此处注册的。参数configClasses在上面的图片中已经展示过。

/**
 * Read {@code configurationModel}, registering bean definitions
 * with the registry based on its contents.
 * 通过configurationModel的内部内容去注册beanDef
 */
public void loadBeanDefinitions(Set<ConfigurationClass> configurationModel) {
	TrackedConditionEvaluator trackedConditionEvaluator = new TrackedConditionEvaluator();
	for (ConfigurationClass configClass : configurationModel) {
		loadBeanDefinitionsForConfigurationClass(configClass, trackedConditionEvaluator);
	}
}

loadBeanDefinitions通过configurationModel的内部内容去注册beanDef,方法内部调用了loadBeanDefinitionsForConfigurationClass方法

	/**
	 * Read a particular {@link ConfigurationClass}, registering bean definitions
	 * for the class itself and all of its {@link Bean} methods.
	 */
	private void loadBeanDefinitionsForConfigurationClass(
			ConfigurationClass configClass, TrackedConditionEvaluator trackedConditionEvaluator) {

		if (trackedConditionEvaluator.shouldSkip(configClass)) {
			String beanName = configClass.getBeanName();
			if (StringUtils.hasLength(beanName) && this.registry.containsBeanDefinition(beanName)) {
				this.registry.removeBeanDefinition(beanName);
			}
			this.importRegistry.removeImportingClass(configClass.getMetadata().getClassName());
			return;
		}

		// 1 如果配置类是被引入的,如配置类是@Import注解的值,则会注册这个configClass
		if (configClass.isImported()) {
			registerBeanDefinitionForImportedConfigurationClass(configClass);
		}
		// 2 如果配置类内部有@Bean方法,要去注册这个方法返回的bean
		for (BeanMethod beanMethod : configClass.getBeanMethods()) {
			loadBeanDefinitionsForBeanMethod(beanMethod);
		}

		// 3 如果配置类上又通过@importResource引入了配置文件,则配置文件中的bean要在这里被注册
		loadBeanDefinitionsFromImportedResources(configClass.getImportedResources());
		// 4 处理配置类上通过@Import引入的实现了ImportBeanDefinitionRegistrar接口的类,这样的类被@Import引入
		// 后,并不会被注册为bean。它跟registry类似,它是一个可以接收bean注册的类,像是一个bean注册中心
		// 在这个接口中有一个方法registerBeanDefinitions,听名字就应该知道是干什么的了,这个方法内部可以注册
		// bean到注册中心上去
		loadBeanDefinitionsFromRegistrars(configClass.getImportBeanDefinitionRegistrars());
	}

这个方法内部对configClass的内容进行分析,对于不同的情况主要有四种不同的处理,代码区的注释已经非常清楚。对于第4点,我们可以参考这篇博客 使用@import导入实现了ImportBeanDefinitionRegistrar接口的类,不能被注册为bean,你会有更深的理解。

总结与思考

bean解析注册流程中最关键的步骤及相关类在下面的时序图有展示
在这里插入图片描述

ConfigurationClassPostProcessor可以看做是整个bean扫描与注册的中枢大脑。它的两个主要过程parser.parse(candidate)reader.loadBeanDefinitions(configClasses)是bean扫描注册的关键点。

读完整篇文章,你应该知道User和Staff这两个bean是在时序图的蓝线处注册的。而一般的@Service,@Controller注解的类是在绿线处注册的。

参考文章

1 使用@import导入实现了ImportBeanDefinitionRegistrar接口的类,不能被注册为bean

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值