2023-09-16  阅读(2)
原文作者:王伟王胖胖 原文地址: https://blog.csdn.net/wangwei19871103/article/details/104988954

解析基本流程图

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

202309162311315191.png

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配置类。

202309162311320242.png

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

比如我这样,不过一般不会有人这么写吧,不过写了就被检测出来报异常啦:

202309162311324893.png

202309162311329074.png
于是就会报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();
    			}
    		}

至此解析自定义的配置类基本完成。

202309162311333615.png
其实我们这样的定义,也是算配置类的,一样可以解析:

202309162311338586.png

202309162311343797.png

比如有bean方法的:

202309162311348998.png

202309162311353059.png
其实就是前面说的Component,ComponentScan,Import,ImportResource注解和Bean方法注解就是配置类,当然也包括Service,Controller,Repository注解。

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

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


Java 面试宝典是大明哥全力打造的 Java 精品面试题,它是一份靠谱、强大、详细、经典的 Java 后端面试宝典。它不仅仅只是一道道面试题,而是一套完整的 Java 知识体系,一套你 Java 知识点的扫盲贴。

它的内容包括:

  • 大厂真题:Java 面试宝典里面的题目都是最近几年的高频的大厂面试真题。
  • 原创内容:Java 面试宝典内容全部都是大明哥原创,内容全面且通俗易懂,回答部分可以直接作为面试回答内容。
  • 持续更新:一次购买,永久有效。大明哥会持续更新 3+ 年,累计更新 1000+,宝典会不断迭代更新,保证最新、最全面。
  • 覆盖全面:本宝典累计更新 1000+,从 Java 入门到 Java 架构的高频面试题,实现 360° 全覆盖。
  • 不止面试:内容包含面试题解析、内容详解、知识扩展,它不仅仅只是一份面试题,更是一套完整的 Java 知识体系。
  • 宝典详情:https://www.yuque.com/chenssy/sike-java/xvlo920axlp7sf4k
  • 宝典总览:https://www.yuque.com/chenssy/sike-java/yogsehzntzgp4ly1
  • 宝典进展:https://www.yuque.com/chenssy/sike-java/en9ned7loo47z5aw

目前 Java 面试宝典累计更新 400+ 道,总字数 42w+。大明哥还在持续更新中,下图是大明哥在 2024-12 月份的更新情况:

想了解详情的小伙伴,扫描下面二维码加大明哥微信【daming091】咨询

同时,大明哥也整理一套目前市面最常见的热点面试题。微信搜[大明哥聊 Java]或扫描下方二维码关注大明哥的原创公众号[大明哥聊 Java] ,回复【面试题】 即可免费领取。

阅读全文