前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >spring源码篇(六)配置类解析过程

spring源码篇(六)配置类解析过程

作者头像
用针戳左手中指指头
修改2023-10-24 18:46:11
3790
修改2023-10-24 18:46:11
举报
文章被收录于专栏:学习计划学习计划

前言

该篇是基于前面知识点进行解析,但涉及到前面的内容不多,不了解前面知识的也不用慌。

知识回顾

前面介绍spring的启动过程,它的启动就是一个准备的过程。

spring启动它做了一些操作:

  1. 实例化beanFactory
  2. 实例化class读取器reader,bean扫描器scanner
  3. 加载我们的主配置类为BeanDefinition
  4. 初始化beanFactory;对beanFactory进行设置,包括后置处理器
  5. 执行自定义后置处理器
  6. 执行postProcessBeanDefinitionRegistry方法,按优先级,顺序执行,最后执行剩下没有执行的(这篇需要深入的)
  7. 执行postProcessBeanFactory方法
  8. beanFactory的后置处理器
  9. 初始化国际化资源对象messagesource
  10. 初始化事件发布器
  11. 注册监听器
  12. 实例化非懒加载bean,这里和之前的bean的生命周期接上了。
  13. 生命周期回调接口执行.onfresh
  14. 启动完成发布事件;

配置类的加载

先来看一个例子

代码语言:javascript
复制
@Component
public class CService {

	@Bean
	public FBean fBean() {
		return new FBean();
	}
}

一般我们都是用@Configuration来进行配置的,但是如上代码中的FBean,它也会被注册。这一个过程是怎么操作的我比较好奇,下面来慢慢探究。

spring启动时,在refresh方法里的一个方法:AbstractApplicationContext#invokeBeanFactoryPostProcessors

跟进之后在:PostProcessorRegistrationDelegate#invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory, List)

方法里,第一个后置处理器执行的是自定义后置处理器,所以这个忽略,直接看第二段;

之前的文章中,我们了解了bean的生命周期过程,那么启动spring容器,最重要的步骤是什么? 准备容器,生成单例bean。怎样才能生成bean,首先得有beanDefinition,为什么要有beanDefinition,可以在看看前面的《bean的生命周期》。而生成bean要在所有的class都扫描完,才会进行实例化,所以配置类扫描,是在扫描beanDefinition的最开始,所以,需要一个入口开启beanDefinition的扫描,在spring中有一个类BeanDefinitionRegistryPostProcessor,这个类看名字就是beanDefinition注册类后置处理器,那么要注册beanDefinition,就必须用它。

下面这一段是找实现了ProiorityOrdered接口的的后置处理器,在一开始实例化容器时默认添加ConfigurationClassPostProcessor,这个类就是实现了BeanDefinitionRegistryPostProcessor,配置类的查找和注册都是由它主导的。

代码语言:javascript
复制
	// 首先,先执行实现了PriorityOrdered接口的BeanDefinitionRegistryPostProcessor的postProcessBeanDefinitionRegistry方法
			String[] postProcessorNames =
					beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.class, true, false);
			for (String ppName : postProcessorNames) {
				// 判断这个类是否还实现了PriorityOrdered接口
				if (beanFactory.isTypeMatch(ppName, PriorityOrdered.class)) {
					// 这里调用了getBean,所以生成一个BeanDefinitionRegistryPostProcessor的bean对象
					// 调用getBean的时候,会添加到单例池中
					currentRegistryProcessors.add(beanFactory.getBean(ppName, BeanDefinitionRegistryPostProcessor.class));
					processedBeans.add(ppName);
				}
			}
			sortPostProcessors(currentRegistryProcessors, beanFactory);
			registryProcessors.addAll(currentRegistryProcessors);
			// 执行postProcessBeanDefinitionRegistry方法
			invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry);
			currentRegistryProcessors.clear();

跟进invokeBeanDefinitionRegistryPostProcessors最里面是这个:ConfigurationClassPostProcessor#processConfigBeanDefinitions

步骤1:找配置类

代码语言:javascript
复制
// 遍历BeanDefinitionRegistry中当前存在的beanDefinition,从中找出那些beanDefinition是配置类
for (String beanName : candidateNames) {
    BeanDefinition beanDef = registry.getBeanDefinition(beanName);
    if (beanDef.getAttribute(ConfigurationClassUtils.CONFIGURATION_CLASS_ATTRIBUTE) != null) {
        if (logger.isDebugEnabled()) {
            // 省略。。。
        }
    }
    // 检查BeanDefinition是不是配置类候选者,那么什么样的BeanDefinition符合呢?
    // 1. 存在@Configuration的就是配置类,或者
    // 2. 存在@Component,@ComponentScan,@Import,@ImportResource,或者
    // 3. 存在@Bean标注的方法
    else if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) {
        configCandidates.add(new BeanDefinitionHolder(beanDef, beanName));
    }
}
// 找不到候选就退出
if (configCandidates.isEmpty()) {
			return;
		}

如何确认是不是配置类

他这里有一个配置类候选的判断checkConfigurationClassCandidate方法,如下

代码语言:javascript
复制
public static boolean checkConfigurationClassCandidate(
			BeanDefinition beanDef, MetadataReaderFactory metadataReaderFactory) {

		String className = beanDef.getBeanClassName();
		if (className == null || beanDef.getFactoryMethodName() != null) {
			return false;
		}

		// 如果是AnnotatedBeanDefinition,那么就直接获取Metadata
		// 如果是其他的,那么则根据类解析出来Metadata
		AnnotationMetadata metadata;
		if (beanDef instanceof AnnotatedBeanDefinition &&
				className.equals(((AnnotatedBeanDefinition) beanDef).getMetadata().getClassName())) {
			// Can reuse the pre-parsed metadata from the given BeanDefinition...
			metadata = ((AnnotatedBeanDefinition) beanDef).getMetadata();
		}
		else if (beanDef instanceof AbstractBeanDefinition && ((AbstractBeanDefinition) beanDef).hasBeanClass()) {
			Class<?> beanClass = ((AbstractBeanDefinition) beanDef).getBeanClass();
			if (BeanFactoryPostProcessor.class.isAssignableFrom(beanClass) ||
					BeanPostProcessor.class.isAssignableFrom(beanClass) ||
					AopInfrastructureBean.class.isAssignableFrom(beanClass) ||
					EventListenerFactory.class.isAssignableFrom(beanClass)) {
				return false;
			}
			metadata = AnnotationMetadata.introspect(beanClass);
		}
		else {
			try {
				MetadataReader metadataReader = metadataReaderFactory.getMetadataReader(className);
				metadata = metadataReader.getAnnotationMetadata();
			}
			catch (IOException ex) {
				// 省略。。。
				return false;
			}
		}

		// 获取Configuration注解的属性信息,配置类分两种,被@Configuration标记的配置类为full,其他的配置类为lite,full的配置类会生成代理对象
		Map<String, Object> config = metadata.getAnnotationAttributes(Configuration.class.getName());
		if (config != null && !Boolean.FALSE.equals(config.get("proxyBeanMethods"))) {
			beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_FULL);
		}
		// 注意,并不是没有Configuration注解当前的BeanDefinition就不是一个配置类
		// 注意isConfigurationCandidate方法,会检查是否存在@Component, @ComponentScan,@Import,@ImportResource,@Bean注解
		else if (config != null || isConfigurationCandidate(metadata)) {
			beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_LITE);
		}
		else {
			// 如果没有Configuration注解信息,则返回false,表示不是一个配置类
			return false;
		}

		// It's a full or lite configuration candidate... Let's determine the order value, if any.
		Integer order = getOrder(metadata);
		if (order != null) {
			beanDef.setAttribute(ORDER_ATTRIBUTE, order);
		}

		return true;
	}

下面这个方法是判断一个类是不是配置类的。

candidateIndicators是静态代码块里定义的:

代码语言:javascript
复制
private static final Set<String> candidateIndicators = new HashSet<>(8);

	static {
		candidateIndicators.add(Component.class.getName());
		candidateIndicators.add(ComponentScan.class.getName());
		candidateIndicators.add(Import.class.getName());
		candidateIndicators.add(ImportResource.class.getName());
	}
代码语言:javascript
复制
public static boolean isConfigurationCandidate(AnnotationMetadata metadata) {
		// 配置类不能是接口
		if (metadata.isInterface()) {
			return false;
		}

		// 查看是否有复合的注解
		for (String indicator : candidateIndicators) {
			if (metadata.isAnnotated(indicator)) {
				return true;
			}
		}

		// 最后在方法上是否有@Bean注解
		try {
			return metadata.hasAnnotatedMethods(Bean.class.getName());
		}
		catch (Throwable ex) {
			if (logger.isDebugEnabled()) {
				logger.debug("Failed to introspect @Bean methods on class [" + metadata.getClassName() + "]: " + ex);
			}
		return false;
		}
	}

所以,判断一个类是不是配置类,并不是有@Configuration注解的才是,还有Component、componentScan、Import、ImportResource.

步骤2:配置类排序和名称生成器

代码语言:javascript
复制
// 将配置类进行排序,根据@Order注解进行排序
		configCandidates.sort((bd1, bd2) -> {
			int i1 = ConfigurationClassUtils.getOrder(bd1.getBeanDefinition());
			int i2 = ConfigurationClassUtils.getOrder(bd2.getBeanDefinition());
			return Integer.compare(i1, i2);
		});


		SingletonBeanRegistry sbr = null;
		// 当前BeanFactory是不是支持单例bean,如果支持则设置一个BeanNameGenerator,用来在扫描@Component和@Import某个Bean时取名字
		if (registry instanceof SingletonBeanRegistry) {
			sbr = (SingletonBeanRegistry) registry;
			if (!this.localBeanNameGeneratorSet) {
				// bean名称生成规则,可以通过 applicationContext.setBeanNameGenerator()设置
                // 这里默认指定了
				BeanNameGenerator generator = (BeanNameGenerator) sbr.getSingleton(
						AnnotationConfigUtils.CONFIGURATION_BEAN_NAME_GENERATOR);
				if (generator != null) {
                    // 扫描时名称生成器
					this.componentScanBeanNameGenerator = generator;
                    // import导入时的名称生成器
					this.importBeanNameGenerator = generator;
				}
			}
		}

		if (this.environment == null) {
			this.environment = new StandardEnvironment();
		}

配置类,作为项目的配置基础,必然存在一个顺序,因为有些配置是需要前提配置的,所有出现此种情况时,会去实现order接口,如果没有实现这个接口,那么会以最低优先级设置(Integer.MAX_VALUE),但一般情况下都是不存在这种情况的,所有我们使用的基本上都没做这样的实现,但不代表不需要。

步骤3:解析配置类

代码语言:javascript
复制
// 配置类解析器
ConfigurationClassParser parser = new ConfigurationClassParser(
				this.metadataReaderFactory, this.problemReporter, this.environment,
				this.resourceLoader, this.componentScanBeanNameGenerator, registry);
// configCandidates是已经找到的配置类
		Set<BeanDefinitionHolder> candidates = new LinkedHashSet<>(configCandidates);
		Set<ConfigurationClass> alreadyParsed = new HashSet<>(configCandidates.size());
		do {
			// 对配置BeanDefinition进行解析,解析完后会生成ConfigurationClass
			parser.parse(candidates);   //AppConfig.class
			parser.validate();

			// 添加解析到的class,还有我们的配置类
			Set<ConfigurationClass> configClasses = new LinkedHashSet<>(parser.getConfigurationClasses());
			configClasses.removeAll(alreadyParsed);

			// Read the model and create bean definitions based on its content
			if (this.reader == null) {
				this.reader = new ConfigurationClassBeanDefinitionReader(
						registry, this.sourceExtractor, this.resourceLoader, this.environment,
						this.importBeanNameGenerator, parser.getImportRegistry());
			}

			// 利用reader解析ConfigurationClass,同时注册BeanDefinition
			this.reader.loadBeanDefinitions(configClasses);
			alreadyParsed.addAll(configClasses);

			candidates.clear();
			if (registry.getBeanDefinitionCount() > candidateNames.length) {
				String[] newCandidateNames = registry.getBeanDefinitionNames();
				Set<String> oldCandidateNames = new HashSet<>(Arrays.asList(candidateNames));
				Set<String> alreadyParsedClasses = new HashSet<>();
				for (ConfigurationClass configurationClass : alreadyParsed) {
					alreadyParsedClasses.add(configurationClass.getMetadata().getClassName());
				}
				for (String candidateName : newCandidateNames) {
					if (!oldCandidateNames.contains(candidateName)) {
						BeanDefinition bd = registry.getBeanDefinition(candidateName);
						if (ConfigurationClassUtils.checkConfigurationClassCandidate(bd, this.metadataReaderFactory) &&
								!alreadyParsedClasses.contains(bd.getBeanClassName())) {
							candidates.add(new BeanDefinitionHolder(bd, candidateName));
						}
					}
				}
				candidateNames = newCandidateNames;
			}
		}
		while (!candidates.isEmpty());

怎样解析的配置类

在上面的do while中,有一个parser.parse(candidates);解析我们扫描到的配置类,我们进入它最里面看看

代码语言:javascript
复制
protected void processConfigurationClass(ConfigurationClass configClass) throws IOException {
		if (this.conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.PARSE_CONFIGURATION)) {
			return;
		}
    // 先看看有没有解析过
		ConfigurationClass existingClass = this.configurationClasses.get(configClass);
		if (existingClass != null) {
			if (configClass.isImported()) {
				if (existingClass.isImported()) {
					existingClass.mergeImportedBy(configClass);
				}
				return;
			}
			else {
// 这里的意思是,可能存在旧的,但是以新的为准
				this.configurationClasses.remove(configClass);
				this.knownSuperclasses.values().removeIf(configClass::equals);
			}
		}

		SourceClass sourceClass = asSourceClass(configClass);
		do {
			sourceClass = doProcessConfigurationClass(configClass, sourceClass);
		}
		while (sourceClass != null);

		// 把当前配置类存在configurationClasses里面
		this.configurationClasses.put(configClass, configClass);
	}

进入sourceClass = doProcessConfigurationClass(configClass, sourceClass);

步骤1:找所有component

递归查找component,这个比较简单,找到后还会查找内部类。

代码语言:javascript
复制
// 1. 如果配置bean上有@Component注解,递归去解析内部类上的注解
		if (configClass.getMetadata().isAnnotated(Component.class.getName())) {
			// Recursively process any member (nested) classes first
			processMemberClasses(configClass, sourceClass);
		}

步骤2:解析@PropertySource

代码语言:javascript
复制
// 2. 解析@PropertySource注解
		for (AnnotationAttributes propertySource : AnnotationConfigUtils.attributesForRepeatable(
				sourceClass.getMetadata(), PropertySources.class,
				org.springframework.context.annotation.PropertySource.class)) {
			if (this.environment instanceof ConfigurableEnvironment) {
				processPropertySource(propertySource);
			}
			else {
                // 省略。。。
			}
		}

@PropertySource注解很少用到,我也是没用过,它的用法和ConditionalOnProperty这个注解有相似之处。

如下例子:

代码语言:javascript
复制
@Component
@ConfigurationProperties(prefix = "test")
@PropertySource(value = {"classpath:pro.properties"})
//@PropertySource(value = {"file:E:/pro.properties"})
public class TestClass {

    @Value("${test.name}")
    private String name;
}

看下里面的实现:

代码语言:javascript
复制
private void processPropertySource(AnnotationAttributes propertySource) throws IOException {
		String name = propertySource.getString("name");
		if (!StringUtils.hasLength(name)) {
			name = null;
		}
		String encoding = propertySource.getString("encoding");
		if (!StringUtils.hasLength(encoding)) {
			encoding = null;
		}
		String[] locations = propertySource.getStringArray("value");
		Assert.isTrue(locations.length > 0, "At least one @PropertySource(value) location is required");
		boolean ignoreResourceNotFound = propertySource.getBoolean("ignoreResourceNotFound");

		Class<? extends PropertySourceFactory> factoryClass = propertySource.getClass("factory");
		PropertySourceFactory factory = (factoryClass == PropertySourceFactory.class ?
				DEFAULT_PROPERTY_SOURCE_FACTORY : BeanUtils.instantiateClass(factoryClass));

		// @PropertySource注解中所指定的properties文件路径
		for (String location : locations) {
			try {
				// 解析占位符
				String resolvedLocation = this.environment.resolveRequiredPlaceholders(location);
				// 得到资源文件
				Resource resource = this.resourceLoader.getResource(resolvedLocation);
				// 把资源文件解析成PropertySource对象,并且添加到environment中去
				addPropertySource(factory.createPropertySource(name, new EncodedResource(resource, encoding)));
			}
			catch (IllegalArgumentException | FileNotFoundException | UnknownHostException ex) {
				// Placeholders not resolvable or resource not found when trying to open it
				if (ignoreResourceNotFound) {
					if (logger.isInfoEnabled()) {
						logger.info("Properties location [" + location + "] not resolvable: " + ex.getMessage());
					}
				}
				else {
					throw ex;
				}
			}
		}
	}

这里的实现就是获取到注解propertySource的属性值(路径),然后通过ResourceLoader加载,然后解析到environment中去。

步骤3:解析@ComponentScan注解,并进行扫描

代码语言:javascript
复制
	// 3. 解析@ComponentScan注解,并进行扫描
		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) {

				// 扫描得到BeanDefinition
                // 这里面有做doScan的操作
				Set<BeanDefinitionHolder> scannedBeanDefinitions =
						this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());

				for (BeanDefinitionHolder holder : scannedBeanDefinitions) {
					BeanDefinition bdCand = holder.getBeanDefinition().getOriginatingBeanDefinition();
					if (bdCand == null) {
						bdCand = holder.getBeanDefinition();
					}

					// 检查扫描所得到BeanDefinition是不是配置Bean,基本上都有@Component注解,所以都是配置类
					if (ConfigurationClassUtils.checkConfigurationClassCandidate(bdCand, this.metadataReaderFactory)) {
						parse(bdCand.getBeanClassName(), holder.getBeanName());
					}
				}
			}
		}

这段里有两个parse方法,上面的一个parse,是将componentScan注解解析后,再根据basePackages去扫描路径下的bean,然后下面一个parse,是解析配置类,两个作用不一样,而且本身的实现都不一样,上面那个是ComponentScanAnnotationParser的实现,下面是ConfigurationClassParser的实现。

相当于是,上面的对ComponentScan里的标注的路径下找到配置类,然后下面的对找到的这些配置类进行解析,虽然这个路径已经很清晰了,我们还是深入看看它的一个逻辑吧。

componentScanParser.parse

首先呢,是componentScanParser.parse,解析componentScan注解信息:

代码语言:javascript
复制
public Set<BeanDefinitionHolder> parse(AnnotationAttributes componentScan, final String declaringClass) {
// 创建扫描器
		ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(this.registry,
				componentScan.getBoolean("useDefaultFilters"), this.environment, this.resourceLoader);

		// 设置BeanName生成器
		Class<? extends BeanNameGenerator> generatorClass = componentScan.getClass("nameGenerator");
		boolean useInheritedGenerator = (BeanNameGenerator.class == generatorClass);
		scanner.setBeanNameGenerator(useInheritedGenerator ? this.beanNameGenerator :
				BeanUtils.instantiateClass(generatorClass));

		ScopedProxyMode scopedProxyMode = componentScan.getEnum("scopedProxy");
		if (scopedProxyMode != ScopedProxyMode.DEFAULT) {
			scanner.setScopedProxyMode(scopedProxyMode);
		}
		else {
			Class<? extends ScopeMetadataResolver> resolverClass = componentScan.getClass("scopeResolver");
			scanner.setScopeMetadataResolver(BeanUtils.instantiateClass(resolverClass));
		}

		scanner.setResourcePattern(componentScan.getString("resourcePattern"));

		// 设置IncludeFilter(包含的规则)
		for (AnnotationAttributes filter : componentScan.getAnnotationArray("includeFilters")) {
			for (TypeFilter typeFilter : typeFiltersFor(filter)) {
				scanner.addIncludeFilter(typeFilter);
			}
		}

		// 设置ExcludeFilter(排除的规则)
		for (AnnotationAttributes filter : componentScan.getAnnotationArray("excludeFilters")) {
			for (TypeFilter typeFilter : typeFiltersFor(filter)) {
				scanner.addExcludeFilter(typeFilter);
			}
		}

		// 设置懒加载
		boolean lazyInit = componentScan.getBoolean("lazyInit");
		if (lazyInit) {
			scanner.getBeanDefinitionDefaults().setLazyInit(true);
		}
// 获取componentScan里我们设置的基础扫描路径
		Set<String> basePackages = new LinkedHashSet<>();
		String[] basePackagesArray = componentScan.getStringArray("basePackages");
    // 注意这里
		for (String pkg : basePackagesArray) {
			String[] tokenized = StringUtils.tokenizeToStringArray(this.environment.resolvePlaceholders(pkg),
					ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
			Collections.addAll(basePackages, tokenized);
		}
		for (Class<?> clazz : componentScan.getClassArray("basePackageClasses")) {
			basePackages.add(ClassUtils.getPackageName(clazz));
		}
		if (basePackages.isEmpty()) {
			basePackages.add(ClassUtils.getPackageName(declaringClass));
		}
// 这里增加了一个抽象类的过滤器,因为尽管抽象类上写了component注解,可以认为是一个bean,但它并不能实例化
    // 所以这个过滤器是会过滤掉抽象类和接口。
		scanner.addExcludeFilter(new AbstractTypeHierarchyTraversingFilter(false, false) {
			@Override
			protected boolean matchClassName(String className) {
				return declaringClass.equals(className);
			}
		});

		// 开始扫描包路径,得到BeanDefinitionHolder
		return scanner.doScan(StringUtils.toStringArray(basePackages));
	}

在看这段的时候,我突然发现,它对我们设置的路径还有解析,所以我尝试了一下,结果:

竟然是正确的,所以,尽管我们在数组元素里写多个路径也是可以的。

代码语言:javascript
复制
// 这里对每一个路径又解析了一次
String[] tokenized = StringUtils.tokenizeToStringArray(this.environment.resolvePlaceholders(pkg),
					ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
			Collections.addAll(basePackages, tokenized);

那么到这里基本上是可以理解了,但是为了和前面章节能够串联,这里我再贴一段,就是它扫描的方法soScan,这个方法,可以对照《bean的生命周期》来看,里面findCandidateComponents这个方法就是bean生命周期一开始的那一段。

代码语言:javascript
复制
protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
		// 这是Spring中的Assert,大家开发时也可以用
		Assert.notEmpty(basePackages, "At least one base package must be specified");
		Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>();
		for (String basePackage : basePackages) {
			// 扫描包路径得到BeanDefinition,得到的BeanDefinition是空的,还没有解析类上所定义的注解信息
			Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
			for (BeanDefinition candidate : candidates) {
				// 得到Scope的信息,并设置
				ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);
				candidate.setScope(scopeMetadata.getScopeName());
				// 得到beanName
				String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);

				if (candidate instanceof AbstractBeanDefinition) {
					postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);
				}
				if (candidate instanceof AnnotatedBeanDefinition) {
					AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);
				}
				if (checkCandidate(beanName, candidate)) {
					// 生成BeanDefinitionHolder并注册到registry中
					BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
					definitionHolder =
							AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
					beanDefinitions.add(definitionHolder);
					registerBeanDefinition(definitionHolder, this.registry);
				}
			}
		}
		return beanDefinitions;
	}
parse
代码语言:javascript
复制
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));
	}

然后这个方法,继续往里走,就又会回到下面这段,就是递归的解析配置类。

代码语言:javascript
复制
sourceClass = doProcessConfigurationClass(configClass, sourceClass);

那么这个componentScan的步骤它所做的,就是:

  1. 扫描ComponentScan得到配置类列表
    1. 创建扫描器
    2. 创建beanName生成器
    3. includeFilter、excludeFilter设置
    4. 懒加载
    5. 路径解析(这有一个特殊点)
    6. 设置默认的过滤器
    7. 扫描路径(doScan),这里可转到bean的生命周期篇章
  2. 解析扫描的配置类
    1. 递归整个方法

步骤4: 解析@Import

代码语言:javascript
复制
// 4. 解析@Import,getImports方法返回AppConfig.class上定义的Import注解中所导入的类的信息
		processImports(configClass, sourceClass, getImports(sourceClass), true);

这一步就是解析得到import里导入的类的信息,然后根据导入的类实现什么接口走哪一种解析方法,代码不贴了,都是走一样的逻辑,还有递归。

还有一点是,SpringBoot中的自动配置类就是用到了这个Import,这个后面再详细说说。

最后还要说一下,这个import的使用是有3种方式,提前说一下,再看代码会比较好理解些。

  1. 使用注解Import
  2. 实现ImportSelector接口
  3. 实现ImportBeanDefinitionRegistrar接口

那么再来看代码;

  1. 首先先看一下getImports这个方法,这个方法是执行processImports前调用的,如果说processImports方式法是执行import(导入),那么getImports就是从配置类里找出Import信息。

getImport方法深入之后是下面的方法:

它找的就是找有Import注解里的值,这里会看到递归调用了,这个其实就是存在这样的一种情况就是:

一个配置类有多个注解,但那些注解可能也是一个存在Import注解的,所以需要遍历注解,然后在递归调用。

就比如,SpringBoot中的自动配置的核心注解@EnableSpringbootApplication

代码语言:javascript
复制
private void collectImports(SourceClass sourceClass, Set<SourceClass> imports, Set<SourceClass> visited)
			throws IOException {

		if (visited.add(sourceClass)) {
			for (SourceClass annotation : sourceClass.getAnnotations()) {
				String annName = annotation.getMetadata().getClassName();
				if (!annName.equals(Import.class.getName())) {
					collectImports(annotation, imports, visited);
				}
			}
			imports.addAll(sourceClass.getAnnotationAttributes(Import.class.getName(), "value"));
		}
	}
  1. 那么再来看processImports,上一步是找出了所以的import,然后这里就是进行导入,上面也说了有3中方式实现效果,那么这里就是对这3种分类处理。
代码语言:javascript
复制
private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass,
			Collection<SourceClass> importCandidates, boolean checkForCircularImports) {

		if (importCandidates.isEmpty()) {
			return;
		}

		if (checkForCircularImports && isChainedImportOnStack(configClass)) {
			this.problemReporter.error(new CircularImportProblem(configClass, this.importStack));
		}
		else {
			this.importStack.push(configClass);
			try {
				for (SourceClass candidate : importCandidates) {
                    // 它属于是ImportSelector,需要添加到集合中等待处理
					if (candidate.isAssignable(ImportSelector.class)) {
						// 加载类对象
						Class<?> candidateClass = candidate.loadClass();
						// 实例化Selector对象,它需要生成handle对象
						ImportSelector selector = ParserStrategyUtils.instantiateClass(candidateClass, ImportSelector.class,
								this.environment, this.resourceLoader, this.registry);
						if (selector instanceof DeferredImportSelector) {
							this.deferredImportSelectorHandler.handle(configClass, (DeferredImportSelector) selector);
						}
						else {
							// 递归
							String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());
							Collection<SourceClass> importSourceClasses = asSourceClasses(importClassNames);
							processImports(configClass, currentSourceClass, importSourceClasses, false);
						}
					}
					// 它属于一个bean定义注册器
					else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) {
						// 如果@Import注解中的类实现了ImportBeanDefinitionRegistrar接口,就把该类的实例放入importBeanDefinitionRegistrars中,
						// 后面再执行该实例的registerBeanDefinitions方法
						Class<?> candidateClass = candidate.loadClass();
						ImportBeanDefinitionRegistrar registrar =
								ParserStrategyUtils.instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.class,
										this.environment, this.resourceLoader, this.registry);
						configClass.addImportBeanDefinitionRegistrar(registrar, currentSourceClass.getMetadata());
					}
					else {
						this.importStack.registerImport(
								currentSourceClass.getMetadata(), candidate.getMetadata().getClassName());
						// 解析配置类
						processConfigurationClass(candidate.asConfigClass(configClass));
					}
				}
			}
			catch (BeanDefinitionStoreException ex) {
				throw ex;
			}
			catch (Throwable ex) {
				throw new BeanDefinitionStoreException(
						"Failed to process import candidates for configuration class [" +
						configClass.getMetadata().getClassName() + "]", ex);
			}
			finally {
				this.importStack.pop();
			}
		}

步骤5: 解析@ImportResource

这里是对spring的配置进行加载

代码语言:javascript
复制
// 5. 解析@ImportResource,得到导入进来的spring的xml配置文件,然后解析
		AnnotationAttributes importResource =
				AnnotationConfigUtils.attributesFor(sourceClass.getMetadata(), ImportResource.class);
		if (importResource != null) {
            // 资源位置
			String[] resources = importResource.getStringArray("locations");
            // 这里默认是BeanDefinitionReader的读取器
			Class<? extends BeanDefinitionReader> readerClass = importResource.getClass("reader");
			for (String resource : resources) {
				String resolvedResource = this.environment.resolveRequiredPlaceholders(resource);

				configClass.addImportedResource(resolvedResource, readerClass);
			}
		}

这一步比较简单。

步骤6: 解析配置类中的加了@Bean注解的方法

代码语言:javascript
复制
// 6. 解析配置类中的加了@Bean注解的方法
		Set<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(sourceClass);
		for (MethodMetadata methodMetadata : beanMethods) {
			configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));
		}
// 7. 如果配置类实现了某个接口,那么则解析该接口中的加了@Bean注解的默认方法
		processInterfaces(configClass, sourceClass);

SourceClass保存class对象和AnnotationMetadata对象,我们不妨看一下retrieveBeanMethodMetadata这个方法:

代码语言:javascript
复制
private Set<MethodMetadata> retrieveBeanMethodMetadata(SourceClass sourceClass) {
    // 获取到注解的元数据对象
		AnnotationMetadata original = sourceClass.getMetadata();
    // 找到含有Bean注解的方法
		Set<MethodMetadata> beanMethods = original.getAnnotatedMethods(Bean.class.getName());
		if (beanMethods.size() > 1 && original instanceof StandardAnnotationMetadata) {
			// Try reading the class file via ASM for deterministic declaration order...
			// Unfortunately, the JVM's standard reflection returns methods in arbitrary
			// order, even between different runs of the same application on the same JVM.
			try {
				AnnotationMetadata asm =
						this.metadataReaderFactory.getMetadataReader(original.getClassName()).getAnnotationMetadata();
				Set<MethodMetadata> asmMethods = asm.getAnnotatedMethods(Bean.class.getName());
				if (asmMethods.size() >= beanMethods.size()) {
					Set<MethodMetadata> selectedMethods = new LinkedHashSet<>(asmMethods.size());
					for (MethodMetadata asmMethod : asmMethods) {
						for (MethodMetadata beanMethod : beanMethods) {
							if (beanMethod.getMethodName().equals(asmMethod.getMethodName())) {
								selectedMethods.add(beanMethod);
								break;
							}
						}
					}
					if (selectedMethods.size() == beanMethods.size()) {
						// All reflection-detected methods found in ASM method set -> proceed
						beanMethods = selectedMethods;
					}
				}
			}
			catch (IOException ex) {
			//
			}
		}
		return beanMethods;
	}

这里根据注释说是要通过ASM得到@Bean注解方法的顺序,因为JVM里是不保证顺序的,但是sourceClass之前看到的是new SourceClass,里面的Metadata对象就是和this.metadataReaderFactory.getMetadataReader(original.getClassName()).getAnnotationMetadata()一样的,那么这里为什么又要再来一次呢?

代码语言:javascript
复制
private SourceClass asSourceClass(ConfigurationClass configurationClass) throws IOException {
		AnnotationMetadata metadata = configurationClass.getMetadata();
		if (metadata instanceof StandardAnnotationMetadata) {
                // 它走的是这个分支
			return asSourceClass(((StandardAnnotationMetadata) metadata).getIntrospectedClass());
		}
		return asSourceClass(metadata.getClassName());
	}

在调试后发现,它创建SourceClass是用的class对象,并非是用全类名,不是说spring里的class扫描是用asm不加载class吗,怎么现在又有了?

这里我忽略了一点,就是我们启动入口传入的配置类会被实例化,因为我们传入的是class对象,它从一开始就被加载了。

AnnotatedGenericBeanDefinition abd = new AnnotatedGenericBeanDefinition(beanClass); public AnnotatedGenericBeanDefinition(Class<?> beanClass) { setBeanClass(beanClass); // 当前类上有哪些注解 this.metadata = AnnotationMetadata.introspect(beanClass); }

所以,有两种情况,一直就是我们启动spring时传入的配置类,它被JVM加载,不保证@Bean注解的方法的顺序,另一种是ASM解析的配置类,它是有顺序的,所以这里,它再次通过ASM解析,保证@Bean注解的方法的顺序。

步骤7:父类递归

代码语言:javascript
复制
// 8. 如果有父类,则返回父类给上层遍历进行处理
		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();
			}
		}

总结

配置类的解析发生在spring启动时,首先是解析总的一个配置类,然再从这个配置类深入解析,然后重复这个步骤,直到没有配置类了,才会进行bean的实例化步骤。

配置类的一个流程:

  1. 后置处理器ConfigurationClassPostProcessor
  2. 找配置类的beanDefinition
    1. @Configuration、@Component、@ComponentScan、@Import、@ImportResource还有@Bean
  3. 对配置类排序(@Order)
  4. 配置beanName生成器
  5. 解析配置类
    1. 先创建一个ConfigurationClassParser
    2. 解析生成ConfigurationClass
      1. 找component注解的类,并且检查内部类,然后解析
      2. 解析PropertySource注解
      3. 解析ComponentScan注解,先扫描到配置类,然后在对配置类解析
      4. Import的解析
      5. 解析ImportResource
      6. 解析Bean注解
    3. ConfigurationClass,同时注册为BeanDefinition

配置类就到这一步,它主要是将所有的bean注册为beanDefinition,之后还会有一步实例化步骤。

文中一直提到配置类,其实并不准确

上面流程中也有写,在spring中,配置类可以是5种:@Configuration、@Component、@ComponentScan、@Import、@ImportResource,所有配置类并不是单指某一类。

严格来说的话,在spring中,它以bean作为一个可管理的对象,那么进一步对bean对象的加载进行配置的是配置类,所有配置类也是一个bean,只是这些所谓的配置类有了配置的权限,就像我们创建一个配置类,我们可以用@Configuratoin,也可以用@Component,而这些配置类在spring中会被单独处理,因为他们比一般普通的bean有了其他的意义。

@ComponentScan注解是怎么实现扫描的

它内部由ClassPathBeanDefinitionScanner实现路径下的扫描,它会先根据我们在componentScan注解中配置属性来配置这个扫描器,然后执行扫描器的doScan方法,底层是通过路径得到资源路径resource,然后一层层查找的,在本篇中没有深入去了解这个过程。

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2021-05-04 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • 知识回顾
  • 配置类的加载
    • 步骤1:找配置类
      • 如何确认是不是配置类
        • 步骤2:配置类排序和名称生成器
          • 步骤3:解析配置类
            • 怎样解析的配置类
            • 步骤1:找所有component
            • 步骤2:解析@PropertySource
            • 步骤3:解析@ComponentScan注解,并进行扫描
            • 步骤4: 解析@Import
            • 步骤5: 解析@ImportResource
            • 步骤6: 解析配置类中的加了@Bean注解的方法
            • 步骤7:父类递归
        • 总结
        相关产品与服务
        容器服务
        腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档