Spring源码解析之ConfigurationClassPostProcessor(三)

在上一章笔者介绍了ConfigurationClassParser.doProcessConfigurationClass(...)方法,在这个方法里调用了processImports(...)方法处理配置类的@Import注解,getImports(sourceClass)能从一个配置类上获取@Import注解配置的所有类形成一个集合,如果集合不为空则会在下面代码的<1>处开始遍历处理。

如果一个类是ImportSelector接口的实现类,会进入<2>处的分支,并在<3>处创建实现类的实例,在<4>处调用实例的selectImports(...)方法获取配置类列表,之后在<5>处以递归的方式调用doProcessConfigurationClass(...)将获取到的配置类传入。

如果一个类是ImportBeanDefinitionRegistrar接口的实现类,则会进入<6>处的分支,这里依旧生成一个实现类的实例,只是这里不立马回调该接口的方法,而是暂存在配置类中,待从ConfigurationClassParser.processImports(...)逐层返回到ConfigurationClassPostProcessor.processConfigBeanDefinitions(...)的do-while循环后会执行ImportBeanDefinitionRegistrar实例的回调方法。

如果一个类仅仅是平平无奇的配置类,即不是ImportSelector的实现类,也不是ImportBeanDefinitionRegistrar的实现类,则会进入<7>处的分支,这里会将配置类传给processConfigurationClass(...)方法,这里我们也可以认为是递归调用,这个方法笔者之前讲过,会将配置类上的@ComponentScans、@ComponentScan注解指定的类路径解析出来,根据类路径扫描BeanDefinition,再判断扫描出来的类是否有资格成为配置类,如果可以的话会再进一步解析。

class ConfigurationClassParser {
	……
	protected final SourceClass doProcessConfigurationClass(
			ConfigurationClass configClass, SourceClass sourceClass, Predicate<String> filter)
			throws IOException {
		……
		processImports(configClass, sourceClass, getImports(sourceClass), filter, true);
		……
	}
	private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass,
			Collection<SourceClass> importCandidates, Predicate<String> exclusionFilter,
			boolean checkForCircularImports) {

		if (importCandidates.isEmpty()) {
			return;
		}

		if (checkForCircularImports && isChainedImportOnStack(configClass)) {
			this.problemReporter.error(new CircularImportProblem(configClass, this.importStack));
		}
		else {
			this.importStack.push(configClass);
			try {
				for (SourceClass candidate : importCandidates) {//<1>
					if (candidate.isAssignable(ImportSelector.class)) {//<2>
						// Candidate class is an ImportSelector -> delegate to it to determine imports
						Class<?> candidateClass = candidate.loadClass();
						ImportSelector selector = ParserStrategyUtils.instantiateClass(candidateClass, ImportSelector.class,
								this.environment, this.resourceLoader, this.registry);//<3>
						Predicate<String> selectorFilter = selector.getExclusionFilter();
						if (selectorFilter != null) {
							exclusionFilter = exclusionFilter.or(selectorFilter);
						}
						if (selector instanceof DeferredImportSelector) {
							this.deferredImportSelectorHandler.handle(configClass, (DeferredImportSelector) selector);
						}
						else {
							String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());//<4>
							Collection<SourceClass> importSourceClasses = asSourceClasses(importClassNames, exclusionFilter);
							processImports(configClass, currentSourceClass, importSourceClasses, exclusionFilter, false);//<5>
						}
					}
					else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) {//<6>
						// Candidate class is an ImportBeanDefinitionRegistrar ->
						// delegate to it to register additional bean definitions
						Class<?> candidateClass = candidate.loadClass();
						ImportBeanDefinitionRegistrar registrar =
								ParserStrategyUtils.instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.class,
										this.environment, this.resourceLoader, this.registry);
						configClass.addImportBeanDefinitionRegistrar(registrar, currentSourceClass.getMetadata());
					}
					else {//<7>
						// Candidate class not an ImportSelector or ImportBeanDefinitionRegistrar ->
						// process it as an @Configuration class
						this.importStack.registerImport(
								currentSourceClass.getMetadata(), candidate.getMetadata().getClassName());
						processConfigurationClass(candidate.asConfigClass(configClass), exclusionFilter);
					}
				}
			}
			catch (BeanDefinitionStoreException ex) {
				throw ex;
			}
			catch (Throwable ex) {
				throw new BeanDefinitionStoreException(
						"Failed to process import candidates for configuration class [" +
						configClass.getMetadata().getClassName() + "]", ex);
			}
			finally {
				this.importStack.pop();
			}
		}
	}
	……
}

  

那么我们来总结一下ConfigurationClassParser.processImports(...)完成的工作,这个方法主要用于处理配置类上的@Import注解,@Import注解允许我们引入多个配置类,在这个方法会遍历处理这些配置类,如果配置类是ImportSelector的实现类,执行实现类的回调方法能立马拿到待引入的配置类列表,我们可以递归调用processImports(...)方法将拿到的配置类列表传入,在这个方法会处理来自上层传入的配置类。如果配置类是ImportBeanDefinitionRegistrar接口的实现类,这里仅仅是实例化这个类的实例并暂存到配置类,并不立即调用实现类的回调方法,因为spring不能肯定开发者会往传入的registrar对象注册多少个配置类,甚至有的开发者一个都不会注册。如果一个类不是ImportSelector或ImportBeanDefinitionRegistrar接口的实现类,会调用processConfigurationClass(...)方法将配置类传入,我们可以认为这里是递归调用,因为processConfigurationClass(...)方法最终会执行到processImports(...)方法。processConfigurationClass(...)方法会解析配置类指定的类路径,并将可以成为BeanDefinition的类注册进spring容器,在尝试将扫描出来的类作为配置类进行解析。

在了解ConfigurationClassParser.parse(...)方法完成的工作后,我们来整体过一下ConfigurationClassPostProcessor.processConfigBeanDefinitions(...)方法,这个方法在最开始的时候会获取spring容器所有的beanName,遍历这些beanName拿到开发者传给容器的配置类,因为开发者可能传入多个配置类,所以这里会行程一个配置类列表,如果列表为空则退出该方法,如果列表不为空,则对列表中的配置类进行排序。

在对配置类列表排序完毕后,会进入一个do-while循环解析配置类。解析配置类是一项非常复杂的工作,解析配置类的时候不但会扫描配置类指定的类路径,还会尝试将扫描出来的BeanDefinition当做一个配置类再次进行解析,如果扫描出来的BeanDefinition可以作为配置类会再次进行二次解析,这里又会重复之前所说的解析工作,在解析完毕后,才会处理@Import注解。

在了解完spring是如何解析配置类后,我们来再来整体过下ConfigurationClassPostProcessor.processConfigBeanDefinitions(...)的逻辑,在<1>处会先用candidateNames这个数组存放spring容器目前所有的beanName。

candidateNames有以下两点作用:

  1. spring会在<2>处先根据candidateNames中存放的beanName过滤出可以成为配置类的的BeanDefinition。
  2. candidateNames可以用于判断解析配置类时是否有新的BeanDefinition被注册进spring容器,可以认为candidateNames是解析配置类前的快照,如果解析完配置类后发现spring容器的BeanDefinition数量大于快照中beanName的数量,表示在解析的时候有引入新的BeanDefinition。

在<2>处完成遍历后会将配置类的BeanDefinition和beanName包装成一个BeanDefinitionHolder对象存进configCandidates列表,之后再<3>处会根据configCandidates列表生成一个配置类集合candidates,<4>处同样会根据配置类的长度生成一个集合alreadyParsed,这个集合会存放已经解析的配置类。

之后会进入一个do-while循环,在循环中会解析、校验配置类。<5>处会获取所有解析出来的配置类,这里笔者所说的不单单是我们平常用@Configuration注解标记的配置类,在解析方法里会把类路径下描到的BeanDefinition尝试作为配置类进行解析,所以<5>处的方法还会将类路径下可以作为配置类的BeanDefinition返回。我们可以在配置类上用@Import注解引入一个ImportBeanDefinitionRegistrar实现类,也可以用@ImportResource注解引入一个XML配置文件,ImportBeanDefinitionRegistrar实现类和XML文件在解析的时候会暂存在配置类中,等到从解析方法回到do-while循环时就会在<7>处配置类中暂存的ImportBeanDefinitionRegistrar实例和XML文件,这里可能会向spring容器注册新的BeanDefinition。

在执行完<7>处的代码后,配置类才算完全被解析,在<8>会将已经被完全解析的配置类存放进alreadyParsed集合,同时我们往回看,每次执行do-while循环时,<5>处总会把目前获取到的配置类返回,configClasses可能存在已经被完全解析的配置类,所以在<6>处会把已经完全解析的配置类移除,<7>处仅处理目前需要处理尚未回调ImportBeanDefinitionRegistrar接口的实例和XML文件。

在<8>处将完全处理完毕的配置类放到alreadyParsed集合后,会清空candidates集合,如果在<9>处判断目前spring容器拥有的BeanDefinition数量大于解析配置类前beanName的数量,会进入分支<9>看看是否有需要再解析的配置类,如果有配置类添加到candidates集合则开启新的一轮的循环。如果<9>处判断解析完配置类后spring容器的BeanDefinition数量等于原先解析前beanName的数量,则candidates集合为空退出do-while循环。

那么在分支<9>中又是如何判断是否应该往candidates集合添加新的配置类呢?在分支<9>内会先把spring容器目前所有的beanName存放到newCandidateNames这个数组中,oldCandidateNames用于存放解析前的beanName,alreadyParsedClasses用于存放解析后的所有配置类。之后会在<10>处遍历spring容器目前所有的beanName,如果beanName在oldCandidateNames集合中,表示其BeanDefinition在本次循环或之前的循环已经被解析,这里不会进入<11>处的分支。如果beanName不在oldCandidateNames集合中,那么这个beanName对应的BeanDefinition有可能是在解析时注册进spring容器,也可能是解析配置类完毕后在<7>处注册进spring容器的,如果是在解析时引入,那么alreadyParsedClasses会包含这个配置类,这里不会进入<12>处的分支。如果是在<7>处处理暂存在配置类的ImportBeanDefinitionRegistrar实例和XML文件,那么alreadyParsedClasses不会包含BeanDefinition对应的类,这里只要ConfigurationClassUtils.checkConfigurationClassCandidate(...)判断BeanDefinition对应的类是配置类就会把BeanDefinition和beanName包装成BeanDefinitionHolder对象存进candidates。最后在<13>处将spring目前所有的beanName存进数组candidateNames,作为下一轮循环解析前的快照。如果下一轮循环在<9>处判断spring容器中的BeanDefinition数量等于上一轮的快照数量,就会退出循环。另外进入了<9>处的分支并不意味着一定会有下个循环,do-while退出循环的条件是candidates为空,只要新引入的BeanDefinition都是解析出来的配置类,或者新引入的BeanDefinition都不是配置类,则不会进入分支<12>,则candidates为空,会退出do-while循环。

public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPostProcessor,
		PriorityOrdered, ResourceLoaderAware, BeanClassLoaderAware, EnvironmentAware {
	……
	public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
		List<BeanDefinitionHolder> configCandidates = new ArrayList<>();
		String[] candidateNames = registry.getBeanDefinitionNames();//<1>

		for (String beanName : candidateNames) {//<2>
			BeanDefinition beanDef = registry.getBeanDefinition(beanName);
			if (beanDef.getAttribute(ConfigurationClassUtils.CONFIGURATION_CLASS_ATTRIBUTE) != null) {
				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));
			}
		}
		……
		// 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);//<3>
		Set<ConfigurationClass> alreadyParsed = new HashSet<>(configCandidates.size());//<4>
		do {
			parser.parse(candidates);
			parser.validate();

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

			// 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());
			}
			this.reader.loadBeanDefinitions(configClasses);//<7>
			alreadyParsed.addAll(configClasses);//<8>

			candidates.clear();
			if (registry.getBeanDefinitionCount() > candidateNames.length) {//<9>
				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) {//<10>
					if (!oldCandidateNames.contains(candidateName)) {//<11>
						BeanDefinition bd = registry.getBeanDefinition(candidateName);
						if (ConfigurationClassUtils.checkConfigurationClassCandidate(bd, this.metadataReaderFactory) &&
								!alreadyParsedClasses.contains(bd.getBeanClassName())) {//<12>
							candidates.add(new BeanDefinitionHolder(bd, candidateName));
						}
					}
				}
				candidateNames = newCandidateNames;//<13>
			}
		}
		while (!candidates.isEmpty());
		……
	}
	……
}

  

至此,我们了解在执行ConfigurationClassPostProcessor.processConfigBeanDefinitions(...)的时候是如何完成扫描类路径,笔者再介绍在上面的方法是如何完成配置类的校验,如何执行配置类中暂存的ImportBeanDefinitionRegistrar实例引入新的BeanDefinition,就结束本章对ConfigurationClassPostProcessor的介绍。

首先我们来看配置类是如何完成校验的,在执行ConfigurationClassParser.validate()方法的时候会遍历已经获取到的所有配置类ConfigurationClass.validate(...)方法,在配置类校验的时候,会检查如果配置类是full模式(有标记@Configuration注解且proxyBeanMethods为true),这个full模式的配置类是否有使用fina修饰符,如果是full模式的配置类,那么类中是否有同时使用了@Bean注解和fina修饰符的方法?之所以做这样的校验:是因为一个full模式的配置类是要使用cglib技术代理的,如果配置类本身使用了final修饰符是无法被继承的,则不能通过校验。如果full模式的配置类有用@Bean注解标记的方法,方法本身也使用了final修饰符,这个方法是不能被重写的,就不允许通过校验。

但如果大家将@Configuration、@Bean和final修饰符搭配使用,会发现java会出现编译错误,既然连编译这关都过不了,为何还要校验呢?spring这里是防止编译出来的字节码被篡改,有人特意在编译后的full模式配置类字节码加上final修饰符,在在这个方法里再次对配置类进行校验。

class ConfigurationClassParser {
	……
	public void validate() {
		for (ConfigurationClass configClass : this.configurationClasses.keySet()) {
			configClass.validate(this.problemReporter);
		}
	}
	……
}

final class ConfigurationClass {
	……
	public void validate(ProblemReporter problemReporter) {
		// A configuration class may not be final (CGLIB limitation) unless it declares proxyBeanMethods=false
		Map<String, Object> attributes = this.metadata.getAnnotationAttributes(Configuration.class.getName());
		if (attributes != null && (Boolean) attributes.get("proxyBeanMethods")) {
			if (this.metadata.isFinal()) {//校验full模式的配置类是否使用final修饰符
				problemReporter.error(new FinalConfigurationProblem());
			}
			for (BeanMethod beanMethod : this.beanMethods) {//校验full模式配置类的方法
				beanMethod.validate(problemReporter);
			}
		}
	}
	……
}

final class BeanMethod extends ConfigurationMethod {
	……
	public void validate(ProblemReporter problemReporter) {
		if (getMetadata().isStatic()) {
			// static @Bean methods have no constraints to validate -> return immediately
			return;
		}

		if (this.configurationClass.getMetadata().isAnnotated(Configuration.class.getName())) {
			if (!getMetadata().isOverridable()) {//校验方法是否允许被重写
				// instance @Bean methods within @Configuration classes must be overridable to accommodate CGLIB
				problemReporter.error(new NonOverridableMethodError());
			}
		}
	}
	……
}

  

最后,我们来了解下ConfigurationClassBeanDefinitionReader.loadBeanDefinitions(...)是如何在解析完配置类后,又从配置类加载新的BeanDefinition到spring容器里。这个方法会调用loadBeanDefinitionsForConfigurationClass(...)方法遍历传入的配置类,在代码<1>处会遍历配置类中加了@Bean注解的方法,这里会调用loadBeanDefinitionsForBeanMethod(...)将@Bean方法包装成一个BeanDefinition注册进spring容器,因为我们知道spring会根据@Bean方法实例化一个bean对象,而BeanDefinition是生产bean对象的原料。

在根据@Bean方法生成BeanDefinition时,会先在<2>处获取方法名,然后在<3>处获取bean对象的别名,并在<4>处建立beanName和别名的映射。之后会在<5>处根据配置类、元数据和方法名创建一个ConfigurationClassBeanDefinition对象,创建bean对象的方法是否是静态决定了控制流是进入<6>处还是<7>处的分支。如果是用静态方法来创建bean对象,在<6>处就设置静态方法对应的类以及方法名。如果是实例方法创建bean对象则进入<7>处的分支,在<7>处的分支会设置可以创建当前bean对象的工厂beanName,即配置类的beanName,再设置创建bean对象的方法名。

在<8>处会设置@Bean方法的BeanDefinition的默认自动装配模型为AbstractBeanDefinition.AUTOWIRE_CONSTRUCTOR,但@Bean注解默认的自动装配模型为Autowire.NO,在<9>处会获取@Bean注解配置的自动装配模型并存放到BeanDefinition。在<10>处会获取@Bean注解设置的是否自动参与候选,默认为true。之后在<11>处获取初始化方法,在<12>处获取销毁方法,如果这两个字段有设置的话。最后在<13>处将beanName和BeanDefinition的映射注册进spring容器中。这里我们又看到一个BeanDefinition的实现类型。

在<1>处的方法遍历完所有的beanMethod将其BeanDefinition注册进spring容器后,会分别在<14>和<15>处处理暂存在配置类的XML配置文件和ImportBeanDefinitionRegistrar实例。在<15>处loadBeanDefinitionsFromRegistrars(...)方法的实现我们也看到在这个方法内会遍历配置类中所有的ImportBeanDefinitionRegistrar实例,回调其registerBeanDefinitions(...)方法,在这个方法中开发者可以向spring容器注册新的BeanDefinition。

class ConfigurationClassBeanDefinitionReader {
	……
	public void loadBeanDefinitions(Set<ConfigurationClass> configurationModel) {
		TrackedConditionEvaluator trackedConditionEvaluator = new TrackedConditionEvaluator();
		for (ConfigurationClass configClass : configurationModel) {
			loadBeanDefinitionsForConfigurationClass(configClass, trackedConditionEvaluator);
		}
	}
	private void loadBeanDefinitionsForConfigurationClass(
			ConfigurationClass configClass, TrackedConditionEvaluator trackedConditionEvaluator) {
		……
		for (BeanMethod beanMethod : configClass.getBeanMethods()) {//<1>
			loadBeanDefinitionsForBeanMethod(beanMethod);
		}

		loadBeanDefinitionsFromImportedResources(configClass.getImportedResources());//<14>
		loadBeanDefinitionsFromRegistrars(configClass.getImportBeanDefinitionRegistrars());//<15>
	}
	……
	private void loadBeanDefinitionsForBeanMethod(BeanMethod beanMethod) {
		ConfigurationClass configClass = beanMethod.getConfigurationClass();
		MethodMetadata metadata = beanMethod.getMetadata();
		String methodName = metadata.getMethodName();//<2>
		……
		AnnotationAttributes bean = AnnotationConfigUtils.attributesFor(metadata, Bean.class);
		Assert.state(bean != null, "No @Bean annotation attributes");

		// Consider name and any aliases
		List<String> names = new ArrayList<>(Arrays.asList(bean.getStringArray("name")));//<3>
		String beanName = (!names.isEmpty() ? names.remove(0) : methodName);

		// Register aliases even when overridden
		for (String alias : names) {//<4>
			this.registry.registerAlias(beanName, alias);
		}

		……
		ConfigurationClassBeanDefinition beanDef = new ConfigurationClassBeanDefinition(configClass, metadata, beanName);//<5>
		beanDef.setSource(this.sourceExtractor.extractSource(metadata, configClass.getResource()));

		if (metadata.isStatic()) {//<6>
			// static @Bean method
			if (configClass.getMetadata() instanceof StandardAnnotationMetadata) {
				beanDef.setBeanClass(((StandardAnnotationMetadata) configClass.getMetadata()).getIntrospectedClass());
			}
			else {
				beanDef.setBeanClassName(configClass.getMetadata().getClassName());
			}
			beanDef.setUniqueFactoryMethodName(methodName);
		}
		else {//<7>
			// instance @Bean method
			beanDef.setFactoryBeanName(configClass.getBeanName());
			beanDef.setUniqueFactoryMethodName(methodName);
		}

		if (metadata instanceof StandardMethodMetadata) {
			beanDef.setResolvedFactoryMethod(((StandardMethodMetadata) metadata).getIntrospectedMethod());
		}

		beanDef.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_CONSTRUCTOR);//<8>
		beanDef.setAttribute(org.springframework.beans.factory.annotation.RequiredAnnotationBeanPostProcessor.
				SKIP_REQUIRED_CHECK_ATTRIBUTE, Boolean.TRUE);

		AnnotationConfigUtils.processCommonDefinitionAnnotations(beanDef, metadata);

		Autowire autowire = bean.getEnum("autowire");//<9>
		if (autowire.isAutowire()) {
			beanDef.setAutowireMode(autowire.value());
		}

		boolean autowireCandidate = bean.getBoolean("autowireCandidate");//<10>
		if (!autowireCandidate) {
			beanDef.setAutowireCandidate(false);
		}

		String initMethodName = bean.getString("initMethod");//<11>
		if (StringUtils.hasText(initMethodName)) {
			beanDef.setInitMethodName(initMethodName);
		}

		String destroyMethodName = bean.getString("destroyMethod");//<12>
		beanDef.setDestroyMethodName(destroyMethodName);
		……
		BeanDefinition beanDefToRegister = beanDef;
		……
		this.registry.registerBeanDefinition(beanName, beanDefToRegister);//<13>
	}
	……
	private void loadBeanDefinitionsFromRegistrars(Map<ImportBeanDefinitionRegistrar, AnnotationMetadata> registrars) {
		registrars.forEach((registrar, metadata) ->
				registrar.registerBeanDefinitions(metadata, this.registry, this.importBeanNameGenerator));
	}
	……
}

  

posted @ 2021-08-28 22:06  北洛  阅读(306)  评论(0编辑  收藏  举报