1、SpringBoot解析启动类之源码分析

3 篇文章 0 订阅

解析启动类流程主要分为三大块:解析类路径下用户自定义的所有类、解析SPI机制从spring.factories文件中加载获取到的类、以及创建所有候选类的BeanDefinitions「用户自定义的类在扫描包路径时触发BeanDefinitions的初始化、spring.factories文件中的候选类以及其间接加载的候选类单独触发BeanDefinitions的初始化」。

  1. 先注册用户自定义类其BeanDefinitions至bean 工厂中。
  2. 再次通过ImportSelector延迟类型之DeferredImportSelector利用SPI手段加载得到全部spring.factories文件中配置的全部候选类。
  3. 遍历步骤2每一个候选类,依次分析其存在的@Component、@Import、@Bean等相关注解再次导入的候选类。
  4. 注册步骤2、步骤3的候选类其BeanDefinitions至bean 工厂中。


public class ConfigurationClassPostProcessor{

	public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {

		...
		do {
	        //ConfigurationClassParser#parse
			parser.parse(candidates);
			parser.validate();
			...
		}
        ...
        // ===================== 注册自动装配候选类以及其@bean注解对应BeanDefinition至bean工厂中
        // 参考第7节
        this.reader.loadBeanDefinitions(configClasses);
		while (!candidates.isEmpty());
	}
}

AnnotatedGenericBeanDefinition类型的BeanDefinition。这种类型的BeanDefinition提供基于元数据、元注解分析的StandardAnnotationMetadata。

由于SpringBoot启动过程中存在诸多元数据、元注解的解析,其中AnnotatedGenericBeanDefinition类型的BeanDefinition初始化了ClassMetadata、AnnotatedTypeMetadata两种类型解析器StandardAnnotationMetadata

ImportSelector延迟类型之DeferredImportSelector:加载类路径下自动装配的全部候选类,并且解析每一个候选类是否存在@Component、@ComponentScans、@Import等相关注解。

class ConfigurationClassParser {

	private final DeferredImportSelectorHandler deferredImportSelectorHandler = new DeferredImportSelectorHandler();


	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());
				}
				...
			}
		}
        //【自定义类处理完毕】===========================延迟类型的ImportSelector处理器
        this.deferredImportSelectorHandler.process();
	}

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


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

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

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

		// Process any @PropertySource annotations
		...
		// Process any @ComponentScan annotations
		AnnotationMetadata am = sourceClass.getMetadata();
		Set<AnnotationAttributes> componentScans = AnnotationConfigUtils.attributesForRepeatable(am, ComponentScans.class, ComponentScan.class);
		if (!componentScans.isEmpty() && !conditionEvaluator.shouldSkip(am, ConfigurationPhase.REGISTER_BEAN)) {
			for (AnnotationAttributes componentScan : componentScans) {
				String className = sourceClass.getMetadata().getClassName();
				//==========================================将扫描类路径下得到的用户自定义类的BeanDefinition注册到 bean工厂中
				Set<BeanDefinitionHolder> scannedBeanDefinitions = componentScanParser.parse(componentScan, className);
				for (BeanDefinitionHolder holder : scannedBeanDefinitions) {
					BeanDefinition bdCand = holder.getBeanDefinition().getOriginatingBeanDefinition();
					if (bdCand == null) {
						bdCand = holder.getBeanDefinition();
					}
					if (ConfigurationClassUtils.checkConfigurationClassCandidate(bdCand, this.metadataReaderFactory)) {
					    // 解析每个自定义类存在的@Component、@PropertySource、@ComponentScan...等注解
						parse(bdCand.getBeanClassName(), holder.getBeanName());
					}
				}
			}
		}

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

		// Process any @ImportResource annotations

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

}

configCandidates:AnnotatedBeanDefinition类型的启动类。

shouldSkip:判断当前候选类是否存在Conditional注解。正常情况下启动类不存在的。

最终结果:符合条件的类最终都会添加到Map类型的集合ConfigurationClasses中。

涉及的注解解析流程如下:

  1. 解析@Conditional注解。
  2. 解析@Component注解。
  3. 解析@PropertySources注解。
  4. 解析@ComponentScans注解。
  5. 解析@Import注解。
  6. 解析@ImportResource注解。
  7. 解析@Bean注解。

SpringBoot中解析任何一个类「包括上述注解解析到的类」都会执行ConfigurationClassParser#processConfigurationClass方法。 就是因为每个候选类最终都会作为Map类型的集合ConfigurationClasses中元素。

deferredImportSelectorHandler.process:解析核心类AutoConfigurationImportSelector过程中利用SPI机制加载spring.factories文件中配置的候选的装配类。 这些装配类的解析同样会使用当前文的全文逻辑。具体参考


1、解析@Conditional注解

 该接口的实现类【OnBeanCondition、OnClassCondition、OnPropertyCondition】等都存在一个用于排序的注解`@Order`。

@Order注解其value取值越大对应排序时的优先级越低。

使用方式之直接方式即`自定义实现接口Condition的子类 | 直接@Conditional`,间接方式之@Conditional

  • @ConditionalOnBean【@Conditional(OnBeanCondition.class)】。
  • @ConditionalOnMissingBean【@Conditional(OnBeanCondition.class)】。
  •  @ConditionalOnMissingClass【@Conditional(OnClassCondition.class)】。
  •  @ConditionalOnClass【@Conditional(OnClassCondition.class)】。
  •  @ConditionalOnProperty【@Conditional(OnPropertyCondition.class)】。

通过组合注解方式使用时其@Conditional注解属性class【OnBeanCondition、OnClassCondition、OnPropertyCondition】都会对应的赋值实现接口Condition的子类。

作用:主要是利用接口Condition的功能判断【直接方式、间接方式】配置类是否满足条件。

主要解析的是当前类的元注解信息「AnnotatedTypeMetadata」。

  • 如果当前类不存在@Conditional相关的注解,则返回false。
  • 如果存在则根据Condition接口相关判定条件裁决当前类是否允许添加到Map类型的集合ConfigurationClasses中。

如果返回true,当前类则不会添加到集合ConfigurationClasses中,也即Spring IOC容器中更不会存在。

如果这些注解存在于类内部的方法上【通常是协同@Bean注解】,符合条件的会将@Bean的bean定义信息添加到beanDefinitionMap。后续用于实例并初始化。

class ConditionEvaluator {

	public boolean shouldSkip(@Nullable AnnotatedTypeMetadata metadata, @Nullable ConfigurationPhase phase) {
		if (metadata == null || !metadata.isAnnotated(Conditional.class.getName())) {
			return false;
		}
		...
		List<Condition> conditions = new ArrayList<>();
		for (String[] conditionClasses : getConditionClasses(metadata)) {
			for (String conditionClass : conditionClasses) {
				Condition condition = getCondition(conditionClass, this.context.getClassLoader());
				conditions.add(condition);
			}
		}

		AnnotationAwareOrderComparator.sort(conditions);
		for (Condition condition : conditions) {
			ConfigurationPhase requiredPhase = null;
			if (condition instanceof ConfigurationCondition) {
				requiredPhase = ((ConfigurationCondition) condition).getConfigurationPhase();
			}
			if ((requiredPhase == null || requiredPhase == phase) && !condition.matches(this.context, metadata)) {
				return true;
			}
		}
		return false;
	}
}
  1. getConditionClasses:获取当前类所有@Conditional「直接方式、间接方式」其value属性设置的所有condition接口的子类。
  2. condition.matches:匹配condition接口每个子类的matches方法,只要存在一个false则表明当前类则不会添加到集合ConfigurationClasses中。更不会添加到IOC容器中。

2.解析@Component注解

如果当前类存在的组合注解中存在@Component注解都会触发。

memberClasses:获取当前类中的所有内部类。集合中所有内部类按照位置顺序从后往前依次添加到集合中。

  1. 排除掉非「配置类~Full模式&Lite模式」的候选类。
  2. 根据order接口进行排序「值越大优先级越低」。
	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) {
                ...
				processConfigurationClass(candidate.asConfigClass(configClass));
			}
		}
	}
}
public static boolean isConfigurationCandidate(AnnotationMetadata metadata) {
	if (metadata.isInterface()) {
		return false;
	}
	for (String indicator : candidateIndicators) {//4种类型注解:Import、ImportResuource、Component、ComponentScan
		if (metadata.isAnnotated(indicator)) {
			return true;
		}
	}
	return metadata.hasAnnotatedMethods(Bean.class.getName());
}

 3.解析@ComponentScan注解

  1. componentScanParser.parse:扫描包路径「basePackage~启动类所在的包路径」下用户自定义的所有类。
  2. 遍历步骤1中的每个类。
  3. 最终递归方式回调ConfigurationClassParser#processConfigurationClass。
public class ClassPathScanningCandidateComponentProvider{
	static final String DEFAULT_RESOURCE_PATTERN = "**/*.class";
	String CLASSPATH_ALL_URL_PREFIX = "classpath*:";
	private final List<TypeFilter> excludeFilters = new ArrayList<>();
	private final List<TypeFilter> includeFilters = new ArrayList<>();
	private Set<BeanDefinition> scanCandidateComponents(String basePackage) {
		Set<BeanDefinition> candidates = new LinkedHashSet<>();
		// 获取到类路径下全部的类
		String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
				resolveBasePackage(basePackage) + '/' + this.resourcePattern;
		Resource[] resources = getResourcePatternResolver().getResources(packageSearchPath);
		for (Resource resource : resources) {
			MetadataReader metadataReader = getMetadataReaderFactory().getMetadataReader(resource);
			// 遍历每一个类,判断是否满足候选类特性
			if (isCandidateComponent(metadataReader)) {
				ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);
				sbd.setSource(resource);
				if (isCandidateComponent(sbd)) {
					candidates.add(sbd);
				}
			}
		}
		return candidates;
	}

	protected boolean isCandidateComponent(MetadataReader metadataReader) throws IOException {
		for (TypeFilter tf : this.excludeFilters) {
			if (tf.match(metadataReader, getMetadataReaderFactory())) {
				return false;
			}
		}
		for (TypeFilter tf : this.includeFilters) {
			if (tf.match(metadataReader, getMetadataReaderFactory())) {
				return isConditionMatch(metadataReader);
			}
		}
		return false;
	}
}

默认情况下excludeFilters主要包含:AutoConfigurationExcludeFilter、TypeExcludeFilter。其中前者主要判断当前类是否存在注解@Configuration,后者应该是方便使用方自行扩展类的Filter。

默认情况下includeFilters主要包含:AnnotationTypeFilter,主要判断当前类是否存在注解@Component。

通常情况下候选类只要存在@Configuration、@Component任意一个注解都会被IOC容器自行管理的类。


4.解析@Import注解

通过@Import注解注入Bean的方式有如下三种:

  • 基于Configuration Class。例如:@Import({ NoOpMeterRegistryConfiguration.class}),导入的候选类只是普通的配置类。
  • 基于ImportSelector接口。
  • 基于ImportBeanDefinitionRegistrar接【AutoConfigurationPackages.Registrar.class】。特例参考AOP、事务相关功能。
class ConfigurationClassParser {

    DeferredImportSelectorHandler deferredImportSelectorHandler = new DeferredImportSelectorHandler();

	private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass,
			Collection<SourceClass> importCandidates, boolean checkForCircularImports) {

		for (SourceClass candidate : importCandidates) {// 当前类通过import注解导入的全部候选类
		    // 候选类 是接口ImportSelector的子类
			if (candidate.isAssignable(ImportSelector.class)) {
				Class<?> candidateClass = candidate.loadClass();
				ImportSelector selector = BeanUtils.instantiateClass(candidateClass, ImportSelector.class);
				ParserStrategyUtils.invokeAwareMethods(selector, this.environment, this.resourceLoader, this.registry);
				// 候选类 同时也是 接口DeferredImportSelector的子类 -- 启动类注解import导入的AutoConfigurationImportSelector
				if (selector instanceof DeferredImportSelector) {
					this.deferredImportSelectorHandler.handle(configClass, (DeferredImportSelector) selector);
				}else {
				    // 直接调用候选类核心方法之selectImports,通过该方法再次得到需要加载到IOC容器的相关新的候选类
					String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());
					Collection<SourceClass> importSourceClasses = asSourceClasses(importClassNames);
					// 递归调用分析新的候选类是否为ImportSelector类型 or 普通的配置类
					processImports(configClass, currentSourceClass, importSourceClasses, false);
				}
			// 候选类 是接口ImportBeanDefinitionRegistrar的子类
			}else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) {
				Class<?> candidateClass = candidate.loadClass();
				ImportBeanDefinitionRegistrar registrar = BeanUtils.instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.class);
				ParserStrategyUtils.invokeAwareMethods(egistrar, this.environment, this.resourceLoader, this.registry);
				// 候选类添加到 当前配置类configClass其importBeanDefinitionRegistrars属性中
				configClass.addImportBeanDefinitionRegistrar(registrar, currentSourceClass.getMetadata());
			}else {
			    // 候选类 是普通的配置类
				String className = candidate.getMetadata().getClassName();
				this.importStack.registerImport(currentSourceClass.getMetadata(), className);
				processConfigurationClass(candidate.asConfigClass(configClass));
			}
		}
	}
}

DeferredImportSelectorHandler:延迟加载接口ImportSelector导入候选类的handler。通过spi机制加载spring.factories文件下的目标候选类。

延迟是提现在何处呢?

  • 如果当前配置类利用@Import导入的候选类是接口ImportSelector的子类但不是接口DeferredImportSelector的子类,则执行核心方法【selectImports----spi机制加载spring.factories文件下的目标候选类】时机如上文所示。
  • 如果当前配置类利用@Import导入的候选类只是接口ImportSelector的子类则马上通过selectImports方法再次获取该子类利用@Import导入的候选类。


 5.解析@Bean注解

protected final SourceClass doProcessConfigurationClass(ConfigurationClass configClass, SourceClass sourceClass)
			throws IOException {
	...
	Set<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(sourceClass);
	for (MethodMetadata methodMetadata : beanMethods) {
		configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));
	}
	...
}

获取当前ConfigurationClass类通过@Bean注解注入的实例集合。


6.SourceClass

为啥多此一举需要把候选类封装为SourceClass?

因为在SpringBoot中候选类的加载分为两部分:JVM & ASM。

  1. 如果候选类已经通过反射方式获取到.class文件【启动类】,则直接将该候选类赋值为StandardAnnotationMetadata内部的内省类【候选类】。
  2. 如果候选类尚为编译后的.class文件,则通过类名判断是否为"java."包下的类:
  • 是,其实就是启动类加载器加载的类,直接通过反射获取。
  • 否,则通过ASM【SimpleMetadataReader】方式将候选类作为资源加载。
private class SourceClass implements Ordered {

		public SourceClass(Object source) {
			this.source = source;
			if (source instanceof Class) {//内省类处理
				this.metadata = AnnotationMetadata.introspect((Class<?>) source);
			}
			else {
				this.metadata = ((MetadataReader) source).getAnnotationMetadata();
			}
		}

		public ConfigurationClass asConfigClass(ConfigurationClass importedBy) {
			if (this.source instanceof Class) {
				return new ConfigurationClass((Class<?>) this.source, importedBy);
			}
			return new ConfigurationClass((MetadataReader) this.source, importedBy);
		}
		...
	}
importedBy:表示当前的sourceClass是被importedBy的@import引入的。

7.延迟候选类的BeanDefinitions

class ConfigurationClassBeanDefinitionReader {
	
	public void loadBeanDefinitions(Set<ConfigurationClass> configurationModel) {
		TrackedConditionEvaluator trackedConditionEvaluator = new TrackedConditionEvaluator();
		// 集合configurationModel元素为全量bean的类信息
		for (ConfigurationClass configClass : configurationModel) {
			loadBeanDefinitionsForConfigurationClass(configClass, trackedConditionEvaluator);
		}
	}
	
	private void loadBeanDefinitionsForConfigurationClass(
			ConfigurationClass configClass, TrackedConditionEvaluator trackedConditionEvaluator) {

		...
		//当前配置类是通过注解@Import导入则注册配置类其BeanDefinition至bean 工厂中
		if (configClass.isImported()) {
			registerBeanDefinitionForImportedConfigurationClass(configClass);
		}
		// 将配置类中通过注解@Bean注入的bean,其BeanDefinition注册至bean 工厂中
		for (BeanMethod beanMethod : configClass.getBeanMethods()) {
			loadBeanDefinitionsForBeanMethod(beanMethod);
		}
		loadBeanDefinitionsFromImportedResources(configClass.getImportedResources());
		// 如果配置类是 接口ImportBeanDefinitionRegistrars 的子类则将该候选类的BeanDefinition注册至bean 工厂中
		loadBeanDefinitionsFromRegistrars(configClass.getImportBeanDefinitionRegistrars());
	}
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
SpringBoot2的启动流程是通过@SpringBootApplication注解自动化配置来实现的。该注解包含了多个注解的组合,其中包括@ComponentScan、@EnableAutoConfiguration和@Configuration等。通过这些注解,Spring Boot会自动扫描并加载配置,并根据自动配置规则来配置应用程序。 具体而言,当应用程序启动时,Spring Boot会创建一个Spring应用程序上下文。在创建上下文的过程中,会先加载主配置(通常是包含main方法的),然后根据@ComponentScan注解扫描指定包下的所有组件。 接下来,Spring Boot会根据@EnableAutoConfiguration注解自动配置应用程序。这个注解会根据classpath和条件匹配的规则,加载配置,并将它们注册到应用程序上下文中。这些配置使用了@Configuration注解,会声明一些Bean,并根据条件来决定是否生效。 最后,Spring Boot会启动应用程序,并执行相应的事件处理器。这些事件处理器可以通过自定义ApplicationListener来实现。在应用程序运行期间,Spring Boot会触发不同的事件,并调用相应的事件处理器。 参考文献: 引用:SpringBoot2 | @SpringBootApplication注解 自动化配置流程源码分析(三) [2] 引用:SpringBoot2 | SpringBoot监听器源码分析 | 自定义ApplicationListener(六) 引用:该系列主要还是Spring的核心源码,不过目前Springboot大行其道,所以就从Springboot开始分析。最新版本是Springboot2.0.4,Spring5,所以新特性本系列后面也会着重分析

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值