Spring 5.x 源码之旅七ConfigurationClassParser解析配置类

解析基本流程图

先看下本篇的基本流程图:
在这里插入图片描述

ConfigurationClassParser的parse

上次讲到要创建一个ConfigurationClassParser解析配置类集合,我们来看看他是怎么解析的。
遍历配置类集合,先判断是否是注解类型的,然后是有Class对象的,最后是只有名字的。然后获取相应的数据进行解析。

	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()) {//有class对象的
					parse(((AbstractBeanDefinition) bd).getBeanClass(), holder.getBeanName());
				}
				else {//一般的,只有name
					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();//处理延迟导入
	}

其实内部都是封装成ConfigurationClass对象的:

//根据className和beanName解析配置文件,需要去URL加载字节码,所以有读取元数据
	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));
	}
	//根据Class和beanName解析配置文件,有Class对象
	protected final void parse(Class<?> clazz, String beanName) throws IOException {
		processConfigurationClass(new ConfigurationClass(clazz, beanName));
	}
	//根据注解元数据和beanName解析配置文件,有注解元数据
	protected final void parse(AnnotationMetadata metadata, String beanName) throws IOException {
		processConfigurationClass(new ConfigurationClass(metadata, beanName));//封装成一个ConfigurationClass进行解析
	}

ConfigurationClass配置类

里面存放着配置相关的注解元数据,被哪个ConfigurationClassimport进来的集合importedBybean注解方法信息,ImportBeanDefinitionRegistrar接口信息等,其实就是来描述配置类的,把我们自定义的配置类解析成ConfigurationClass配置类。
在这里插入图片描述

ConfigurationClassParser的processConfigurationClass

首先会获取有没有ConfigurationClass 存在,如果有的话就看新的是不是import注解进来的,如果不是就直接把老的删了,如果是,就看老的是不是import注解进来的,是的话就跟老的合并,不是的话就忽略新的,返回。然后将ConfigurationClass包装下,里面有原始Class对象和元数据。然后再进行处理,这里有个循环,递归处理ConfigurationClass以及其父类,会一直处理父类,返回的父类sourceClass又会当做新sourceClass 传进去,直到最后是JAVA内部的父类才停止,最后将ConfigurationClass放入集合里。

protected void processConfigurationClass(ConfigurationClass configClass) throws IOException {
		if (this.conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.PARSE_CONFIGURATION)) {
			return;//有条件注解不满足的返回
		}
		//获取链表中存在的ConfigurationClass
		ConfigurationClass existingClass = this.configurationClasses.get(configClass);
		if (existingClass != null) {
			if (configClass.isImported()) {//如果老的和新的都是import的,就合并
				if (existingClass.isImported()) {
					existingClass.mergeImportedBy(configClass);
				}
				// Otherwise ignore new imported config class; existing non-imported class overrides it.
				return;
			}
			else {
				this.configurationClasses.remove(configClass);//删除老的
				this.knownSuperclasses.values().removeIf(configClass::equals);
			}
		}
		//获取configClass源类,包装原始的类和元数据
		// Recursively process the configuration class and its superclass hierarchy.
		SourceClass sourceClass = asSourceClass(configClass);
		do {
			sourceClass = doProcessConfigurationClass(configClass, sourceClass);
		}
		while (sourceClass != null);//处理配置类,如果有父类(不是java开头的类),继续处理,直到没有父类为止

		this.configurationClasses.put(configClass, configClass);//放入集合
	}

ConfigurationClassParser的doProcessConfigurationClass之处理内部类

如果注解了Component,会处理内部类。

//是否注解了Component
		if (configClass.getMetadata().isAnnotated(Component.class.getName())) {
			// Recursively process any member (nested) classes first
			processMemberClasses(configClass, sourceClass);//处理嵌套
		}
ConfigurationClassParser的processMemberClasses以及循环import

获取内部类,如果存在就把候选配置类取出来,然后进行解析,这里有个关键栈importStack,他会存放要解析的内部类,防止内部类之间循环import。比如A,B两个内部类,Aimport注解,导入的是B,同样B有import注解,导入的是A,这样如果在处理的时候发现存在A了,那就说明是循环import了。

private void processMemberClasses(ConfigurationClass configClass, SourceClass sourceClass) 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)) {//防止循环import
					this.problemReporter.error(new CircularImportProblem(configClass, this.importStack));
				}
				else {
					this.importStack.push(configClass);
					try {
						processConfigurationClass(candidate.asConfigClass(configClass));
					}
					finally {
						this.importStack.pop();
					}
				}
			}
		}
	}

比如我这样,不过一般不会有人这么写吧,不过写了就被检测出来报异常啦:
在这里插入图片描述
在这里插入图片描述
于是就会报CircularImportProblem异常啦啦。

org.springframework.beans.factory.parsing.BeanDefinitionParsingException: Configuration problem: A circular @Import has been detected: Illegal attempt by @Configuration class 'MyConfig.T1' to import class 'MyConfig.T2' as 'MyConfig.T2' is already present in the current import stack [MyConfig.T1->MyConfig.T2->MyConfig]
Offending resource: com.ww.config.MyConfig$T1

ConfigurationClassParser的doProcessConfigurationClass之处理PropertySources

这个是跟我们的环境配置文件属性相关的,暂时不是重点,知道就好。

	// Process any @PropertySource annotations处理PropertySources
		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");
			}
		}

ConfigurationClassParser的doProcessConfigurationClass之处理ComponentScans

获取ComponentScan注解,然后解析成bean定义,最后递归处理配置类,具体细节后面会说。

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

ConfigurationClassParser的doProcessConfigurationClass之处理Import

这里包括Import,ImportSelector注解和ImportBeanDefinitionRegistrar接口实现类,会将解析出来的都添加到ConfigurationClass里,具体代码先不展开,后面会详细说,不然很长了。

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

		// Process any @ImportResource annotations处理ImportResource
		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);
			}
		}

ConfigurationClassParser的doProcessConfigurationClass之处理bean注解方法

bean注解的方法添加到ConfigurationClass中。

		Set<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(sourceClass);
		for (MethodMetadata methodMetadata : beanMethods) {
			configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));//添加bean注解方法到configClass
		}

ConfigurationClassParser的doProcessConfigurationClass之处理接口的默认实现方法

处理接口的默认实现方法,也是进行接口的递归检查。

processInterfaces(configClass, sourceClass);

	private void processInterfaces(ConfigurationClass configClass, SourceClass sourceClass) throws IOException {
		for (SourceClass ifc : sourceClass.getInterfaces()) {
			Set<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(ifc);
			for (MethodMetadata methodMetadata : beanMethods) {
				if (!methodMetadata.isAbstract()) {
					// A default method or other concrete method on a Java 8+ interface...
					configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));
				}
			}
			processInterfaces(configClass, ifc);
		}
	}

ConfigurationClassParser的doProcessConfigurationClass之处理父类

因为有可能父类还有注解定义,所以要寻找父类,直到Java开头的父类,也就是要递归处理自定义的父类,把父类返回,然后外面继续处理父类。:

// Process superclass, if any如果有父类,且不是java开头的,也是未知的父类,就返回父类
		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();
			}
		}

至此解析自定义的配置类基本完成。
在这里插入图片描述
其实我们这样的定义,也是算配置类的,一样可以解析:
在这里插入图片描述
在这里插入图片描述

比如有bean方法的:
在这里插入图片描述
在这里插入图片描述
其实就是前面说的Component,ComponentScan,Import,ImportResource注解和Bean方法注解就是配置类,当然也包括Service,Controller,Repository注解。

中间有很多细节,深入下去需要更多的预备知识,而且篇幅很长,可能会陷了很深,所以没有深入下去,我们还是先把大致的原理搞清楚,细节后面再研究比较好,先观其大略,然后各个击破吧。

好了,今天就到这里了,希望对学习理解有帮助,大神看见勿喷,仅为自己的学习理解,能力有限,请多包涵。

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值