SpringBoot源码解析系列(3) SpringBoot的ConfigurationParser#parse方法

承接上文 :  ConfigurationParser#parse(Set<BeanDefinitionHolder> configCandidates)

进入到之后 看到会有几个选择 无非是通过@Configuration 还是通过xml方式什么定义的Configuration 由于现在绝大部分都是以注解形式 我直接就直接进入注解 其他的都是大差不差 基本上都差不太多 区别在于拿属性的时候方式不一致

进入到 ConfigurationParser#processConfigurationClass

	protected void processConfigurationClass(ConfigurationClass configClass) throws IOException {
	// 判断是否跳过 这个是跟@Conditional接口相关
	//@ConditionalOnBean(仅仅在当前上下文中存在某个对象时,才会实例化一个Bean)
    //@ConditionalOnClass(某个class位于类路径上,才会实例化一个Bean)
    //@ConditionalOnExpression(当表达式为true的时候,才会实例化一个Bean)
    //@ConditionalOnMissingBean(仅仅在当前上下文中不存在某个对象时,才会实例化一个Bean)
    //@ConditionalOnMissingClass(某个class类路径上不存在的时候,才会实例化一个Bean)
    //@ConditionalOnNotWebApplication(不是web应用)
		if (this.conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.PARSE_CONFIGURATION)) {
			return;
		}
		// ... 省略掉不重要代码
		do {
		//此方法很重要
			sourceClass = doProcessConfigurationClass(configClass, sourceClass);
		}
		while (sourceClass != null);

		this.configurationClasses.put(configClass, configClass);
	}
	protected final SourceClass doProcessConfigurationClass(ConfigurationClass configClass, SourceClass sourceClass)
			throws IOException {
		// ...
		// 如果配置类 被@PropertySource修饰 则将配置的文件导入进environment
		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");
			}
		}

		//  处理扫描 会根据 你再@CompScan里面配置的路径拿到所有class文件
		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
				// 扫描类路径下 class文件 封装成包装类BeanDefinitionHolder
				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();
					}
					// 判断当前类是否是@Configuration 如果是的话 则当成@Configuration递归处理 
					if (ConfigurationClassUtils.checkConfigurationClassCandidate(bdCand, this.metadataReaderFactory)) {
						parse(bdCand.getBeanClassName(), holder.getBeanName());
					}
				}
			}
		}

		// 此方法很重要单独讲解
		processImports(configClass, sourceClass, getImports(sourceClass), true);

		// 支持配置文件 新增beanDefination
		AnnotationAttributes importResource =
				AnnotationConfigUtils.attributesFor(sourceClass.getMetadata(), ImportResource.class);
		// ...		

		// 处理@Configuration的@Bean注解 生成BeanDefination
		Set<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(sourceClass);
		for (MethodMetadata methodMetadata : beanMethods) {
			configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));
		}

		//处理接口中所有添加@Bean注解的方法,内部通过遍历所有接口,解析得到@Bean注解方法,并添加到configClass配置类信息中
		processInterfaces(configClass, sourceClass);

		// 处理该类的超类
		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;
	}

我把重要方法过程简单标注一下 : 首先 针对扫描 我们需要先了解一下 注解的别名机制 : 例如 @SpringBootApplication里面有String[] scanBasePackages() default {}属性; 

同时该属性上有@AliasFor(annotation = ComponentScan.class, attribute = "basePackages")这个一段代码 意思是 将SpringBootApplication的scanBasePackages映射到ComponentScan的basePackages属性上 (!!!!!!!!!! 前提是SpringBootApplication得被ComponentScan修饰)

如果扫描代码一直跟进去 挥发现进入到ClassPathScanningCandidateComponentProvider#scanCandidateComponents 我就不贴代码了 直接给出结论 这个方法会拼接一个classpath下的满足条件(basePackages路径)的 .class文件地址(编译时期生成的)   会拿到这个文件此时是二进制 我们需要ClassReader类来读取二进制流 生成beanDefination并生成包装类

第二个重点 !!!!!       此方法专门处理@Import注解    processImports(configClass, sourceClass, getImports(sourceClass), true);  就是此方法存在 实现了我们很多Spring相关匪夷所思的事情 (有感而发 : 其实客观讲 时至今日我看Spring代码好久了 给我最大感受 不是设计模式 或者调用一些api等等很厉害(随着实践这些都是真的不是很难)  (当然了我并没有说在这些方面Spring做的不好)  最让我感慨感觉到厉害的是 Spring作者在抽象事物上的能力 抽象事物 大家肯定的都说 但是这是一个看起来很简单 但你真正接触起来发现 这个要做的好 做的张弛有度 真的很难很难很难)

	private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass,
			Collection<SourceClass> importCandidates, boolean checkForCircularImports) {
			// ...
			// 增加缓存
			this.importStack.push(configClass);
			try {
				for (SourceClass candidate : importCandidates) {
					// 判断接口 是否是ImportSelector子类
					if (candidate.isAssignable(ImportSelector.class)) {
						// ...
						//将各种aware接口赋值 比如实现了BeanFactoryAware 会自动将当前容器注入进去
						ParserStrategyUtils.invokeAwareMethods(
								selector, this.environment, this.resourceLoader, this.registry);
                        //如果这个实现类也实现了DeferredImportSelector接口
						//,就被加入到集合deferredImportSelectors中 !!!!!! 此事没有没有调用selectImports
						if (selector instanceof DeferredImportSelector) {
							this.deferredImportSelectorHandler.handle(
									configClass, (DeferredImportSelector) selector);
						}
						else {
							// 调用不是DeferredImportSelector的ImportSelector接口
							String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());
							Collection<SourceClass> importSourceClasses = asSourceClasses(importClassNames);
							processImports(configClass, currentSourceClass, importSourceClasses, false);
						}
					}
					// 如果导入的类是ImportBeanDefinitionRegistrar 把它加进全局唯一的ConfigurationClass里面
					else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) {
						// Candidate class is an ImportBeanDefinitionRegistrar ->
						// delegate to it to register additional bean definitions
						Class<?> candidateClass = candidate.loadClass();
						ImportBeanDefinitionRegistrar registrar =
								BeanUtils.instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.class);
						ParserStrategyUtils.invokeAwareMethods(
								registrar, this.environment, this.resourceLoader, this.registry);
						configClass.addImportBeanDefinitionRegistrar(registrar, currentSourceClass.getMetadata());
					}
					else {
						//否则当成@Configuration类处理 递归操作
						this.importStack.registerImport(
								currentSourceClass.getMetadata(), candidate.getMetadata().getClassName());
						processConfigurationClass(candidate.asConfigClass(configClass));
					}
				}
			}
		}
	}

首先针对几个接口 进行讲解 

1 . @ImportSelector : 入参为注解元数据 会根据@Configuration类上的所有注解传入进行生成我们想要导入的包路径类名 !!! 注意 此时是没完全处理完@Configuration

2 . @DeferredImportSelector : 跟@ImportSelector相比 他是在处理完所有@Configuration内的方法 包括@ImportResource 以及@Bean等 同时支持分组 , 组内排序执行顺序

3 . @ImportBeanDefinitionRegistrar : 允许往BeanDefinationRegistry里面注册BeanDefination (!!!!!!!!! 比如apollo就注入了很多处理程序 等等 很重要的接口)

讲到这基本上parse方法就ok了 没什么 注册BeanDefination到这基本上就差不多了

 

下一节 SpringBoot源码解析系列(4) SpringBoot的自动扫描功能: https://blog.csdn.net/weixin_44669461/article/details/116135443?spm=1001.2014.3001.5501

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值