Spring注解-2-@Configuration的解析之ConfigurationClassParser&ConfigurationClassPostProcessor

一、背景

ConfigurationClassParser是解析@Configuration注解的具体实现者。
ConfigurationClassPostProcessor是调用ConfigurationClassParser的类。
调用点在这个方法中:
org.springframework.context.annotation.ConfigurationClassPostProcessor#processConfigBeanDefinitions(...)

public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {

    ......
	// 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 {
		parser.parse(candidates);
		parser.validate();
            ......
	}
	while (!candidates.isEmpty());
    ......

}

二、作用

org.springframework.context.annotation.ConfigurationClassParser
始自Spring 3.0
官方的解释是:
解析一个Configuration定义的类,生成一批org.springframework.context.annotation.ConfigurationClass实例(解析单个Configuration类将会产生任意个数的ConfigurationClass实例,因为一个Configuration类可以使用@Import导入另一个)。这个类分离了同一内容上解析Configuration类和注册BeanDefinition的关注点(@ComponentScan类除外,他需要立即被注册)。这种基于ASM实现的避免了反射和急加载,以便于在Spring AppilcationContext中有效的与懒加载的类进行交互。

org.springframework.context.annotation.ConfigurationClassPostProcessor
始自Spring 3.0
官方解释是:
这个post processor是按优先级排序的,这是非常重要的,因为在声明@Configuration的类中的任意声明@Bean的方法,会注册相应的bean definitions,且在其他任意BeanFactoryPostProcessor执行之前执行。

三、源码

这个类里的org.springframework.context.annotation.ConfigurationClassParser#parse()方法是对外的方法,会在Spring启动时被调用。
这个方法执行后,所有配置类都会被Spring处理,也就是说BeanDefinition就全部进入到容器中了。这个方法在SpringBoot启动时,只会被执行一次,执行的效果是,SpringBoot配置的所有BeanDefinition全部被注册。显然这个过程是比较复杂的,因为Spring提供的注解配置注解是很多样的。
下面我们来看看他的逻辑,入参是配置类,也就是包括lite模式和full模式的配置类,启动时,入参一般就是启动类。这里对配置类的BeanDefinition进行了判断,说明BeanDefinition是有多种可能的。如果是启动类,走的分支是第一个case,也即启动类BeanDefinition是AnnotatedBeanDefinition类型。
这里有4个parse方法,第一个给外部调用,后面3个是内部使用,其中第二个parse(String, String)是一个被递归的方法,在处理@ComponentScan注解时会调用这个方法。

public void parse(Set<BeanDefinitionHolder> configCandidates) {
	for (BeanDefinitionHolder holder : configCandidates) {
		BeanDefinition bd = holder.getBeanDefinition();
		try {
			if (bd instanceof AnnotatedBeanDefinition) {
				parse(((AnnotatedBeanDefinition) bd).getMetadata(), holder.getBeanName());
			}
			else if (bd instanceof AbstractBeanDefinition && ((AbstractBeanDefinition) bd).hasBeanClass()) {
				parse(((AbstractBeanDefinition) bd).getBeanClass(), holder.getBeanName());
			}
			else {
				parse(bd.getBeanClassName(), holder.getBeanName());
			}
		}
		catch (BeanDefinitionStoreException ex) {
			throw ex;
		}
		catch (Throwable ex) {
			throw new BeanDefinitionStoreException(
					"Failed to parse configuration class [" + bd.getBeanClassName() + "]", ex);
		}
	}

	this.deferredImportSelectorHandler.process();
}

protected final void parse(@Nullable String className, String beanName) throws IOException {
	Assert.notNull(className, "No bean class name for configuration class bean definition");
	MetadataReader reader = this.metadataReaderFactory.getMetadataReader(className);
	processConfigurationClass(new ConfigurationClass(reader, beanName), DEFAULT_EXCLUSION_FILTER);
}

protected final void parse(Class<?> clazz, String beanName) throws IOException {
	processConfigurationClass(new ConfigurationClass(clazz, beanName), DEFAULT_EXCLUSION_FILTER);
}

protected final void parse(AnnotationMetadata metadata, String beanName) throws IOException {
	processConfigurationClass(new ConfigurationClass(metadata, beanName), DEFAULT_EXCLUSION_FILTER);
}

启动类配置类接下来的逻辑是:
org.springframework.context.annotation.ConfigurationClassParser#parse(metadata, beanName)
入参metadata包含了启动类的信息,比如添加的注解
beanName就是启动类的名字,取名规则是Spring给bean取名的规则。

protected final void parse(AnnotationMetadata metadata, String beanName) throws IOException {
	processConfigurationClass(new ConfigurationClass(metadata, beanName), DEFAULT_EXCLUSION_FILTER);
}

不同类型的BeanDefinition最中都会调用同一个逻辑的:
org.springframework.context.annotation.ConfigurationClassParser#processConfigurationClass(...)
这个方法是一个递归方法,他里面会调用processConfigurationClass()本身,也就是说这个方法会被调用多次。这里递归的原因是要处理内部类这种情况,当遇到内部类是,需要重新调用自身。
这个方法里还有一个循环
每次的入参都是一个配置类,Spring对配置类的定义参考这篇文章《Spring配置类之full和lite模式》,配置类的定义比较广泛,这么看来几乎每一个被Spring管理的类都会进一次这个方法。所以要站在这个高度来理解这个方法。
每一个配置类都会被封装为org.springframework.context.annotation.ConfigurationClass。
ConfigurationClassParser中有一个成员变量
configurationClasses用来存储当前实例已经解析过的配置类,已经解析过的逻辑和没有解析过的逻辑是不一样的。解析已经存在配置类的逻辑有点绕:
如果已经解析过了,且当前配置类有@Import,那就不解析了
如果已经解析过了,且当前配置类没有@Import,那就删除解析过的,重新解析覆盖。
这里出现了SourceClass这个类型,是对类型的又一个封装,提供了特定的方法。

private final Map<ConfigurationClass, ConfigurationClass> configurationClasses = new LinkedHashMap<>();

protected void processConfigurationClass(ConfigurationClass configClass, Predicate<String> filter) throws IOException {
	if (this.conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.PARSE_CONFIGURATION)) {
		return;
	}

	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, filter);
	do {
		sourceClass = doProcessConfigurationClass(configClass, sourceClass, filter);
	}
	while (sourceClass != null);

	this.configurationClasses.put(configClass, configClass);
}

真正工作的是:
org.springframework.context.annotation.ConfigurationClassParser#doProcessConfigurationClass(...)
方法入参是调用者传进来的,还返回一个配置类,返回的配置类是传入配置类的父类,当然如果没有就返回null,看来Spring还会处理这种继承关系的配置。这里既有递归又有循环,需要多回味下这个逻辑,循环是用来处理父类,递归是用来处理内部类,内部类是一个全新的类,需要重新进入processConfigurationClass,而父类不用。
这个方法第一步就是产生递归的源头,传入的参数configClass是启动类,sourceClass也是启动类,这个方法的每一段功能都是一段值得深究的功能,其中处理@ComponentScan注解的方法会涉及类扫描。

@Nullable
protected final SourceClass doProcessConfigurationClass(
		ConfigurationClass configClass, SourceClass sourceClass, Predicate<String> filter)
		throws IOException {

	if (configClass.getMetadata().isAnnotated(Component.class.getName())) {
		// Recursively process any member (nested) classes first,这里发生递归
		processMemberClasses(configClass, sourceClass, filter);
	}

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

	// Process any @ComponentScan annotations
	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
			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)) {
					parse(bdCand.getBeanClassName(), holder.getBeanName());
				}
			}
		}
	}

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

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

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

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

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

org.springframework.context.annotation.ConfigurationClassParser#processMemberClasses()
首先获取内部类,内部类为空时什么什么都不做,

private void processMemberClasses(ConfigurationClass configClass, SourceClass sourceClass,
		Predicate<String> filter) throws IOException {
	// 通过反射获取内部类
	Collection<SourceClass> memberClasses = sourceClass.getMemberClasses();
	if (!memberClasses.isEmpty()) {
		List<SourceClass> candidates = new ArrayList<>(memberClasses.size());
		for (SourceClass memberClass : memberClasses) {
			// 判断是否是配置类
			if (ConfigurationClassUtils.isConfigurationCandidate(memberClass.getMetadata()) &&
					!memberClass.getMetadata().getClassName().equals(configClass.getMetadata().getClassName())) {
				candidates.add(memberClass);
			}
		}
		OrderComparator.sort(candidates);
		 // 递归处理
		for (SourceClass candidate : candidates) {
			if (this.importStack.contains(configClass)) {
				this.problemReporter.error(new CircularImportProblem(configClass, this.importStack));
			}
			else {
				this.importStack.push(configClass);
				try {
					processConfigurationClass(candidate.asConfigClass(configClass), filter);
				}
				finally {
					this.importStack.pop();
				}
			}
		}
	}
}

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Spring Boot中,@Conditional注解和@AutoConfigureAfter注解是非常常用的注解,下面我来给你详细解析一下这两个注解。 ## @Conditional注解 @Conditional注解Spring Boot中非常重要的一个注解,在Spring Boot中,很多自动配置都是通过@Conditional注解来实现的。 @Conditional注解可以根据满足某些条件来决定是否创建一个bean。比如,我们可以根据某个类是否存在来决定是否创建一个bean,具体示例如下: ```java @Configuration @Conditional(ExistClassCondition.class) public class MyConfiguration { @Bean public MyBean myBean() { return new MyBean(); } } public class ExistClassCondition implements Condition { @Override public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { try { Class.forName("com.example.MyClass"); return true; } catch (ClassNotFoundException e) { return false; } } } ``` 上面的代码中,我们定义了一个MyConfiguration类,并且在该类上加了@Conditional注解,该注解的参数是一个Condition的实现类ExistClassCondition。ExistClassCondition类中的matches方法返回true的条件是com.example.MyClass类存在。 这样,当com.example.MyClass类存在的时候,MyBean这个bean才会被创建。否则,MyBean这个bean不会被创建。 ## @AutoConfigureAfter注解 @AutoConfigureAfter注解也是Spring Boot中比较常用的注解之一,它可以用来控制自动配置的顺序。 比如,我们可以通过@AutoConfigureAfter注解来控制某个自动配置类在另一个自动配置类之后加载,具体示例如下: ```java @Configuration @AutoConfigureAfter(MyAutoConfiguration.class) public class MyAnotherAutoConfiguration { // ... } ``` 上面的代码中,我们定义了一个MyAnotherAutoConfiguration类,并且在该类上加了@AutoConfigureAfter注解,该注解的参数是MyAutoConfiguration.class。这样,在Spring Boot启动时,MyAutoConfiguration这个自动配置类会先于MyAnotherAutoConfiguration这个自动配置类被加载。 总结:@Conditional注解和@AutoConfigureAfter注解都是Spring Boot中非常实用的注解。通过@Conditional注解可以实现根据满足某些条件来决定是否创建一个bean,通过@AutoConfigureAfter注解可以控制自动配置类的加载顺序,这些都是我们在实际开发中非常常用的功能。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值