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

作者简介:大家好,我是smart哥,前中兴通讯、美团架构师,现某互联网公司CTO

联系qq:184480602,加我进群,大家一起学习,一起进步,一起对抗互联网寒冬

学习必须往深处挖,挖的越深,基础越扎实!

阶段1、深入多线程

阶段2、深入多线程设计模式

阶段3、深入juc源码解析


阶段4、深入jdk其余源码解析


阶段5、深入jvm源码解析

码哥源码部分

码哥讲源码-原理源码篇【2024年最新大厂关于线程池使用的场景题】

码哥讲源码【炸雷啦!炸雷啦!黄光头他终于跑路啦!】

码哥讲源码-【jvm课程前置知识及c/c++调试环境搭建】

​​​​​​码哥讲源码-原理源码篇【揭秘join方法的唤醒本质上决定于jvm的底层析构函数】

码哥源码-原理源码篇【Doug Lea为什么要将成员变量赋值给局部变量后再操作?】

码哥讲源码【你水不是你的错,但是你胡说八道就是你不对了!】

码哥讲源码【谁再说Spring不支持多线程事务,你给我抽他!】

终结B站没人能讲清楚红黑树的历史,不服等你来踢馆!

打脸系列【020-3小时讲解MESI协议和volatile之间的关系,那些将x86下的验证结果当作最终结果的水货们请闭嘴】

解析基本流程图

先看下本篇的基本流程图:

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注解。

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

  • 19
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值