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

2023-10-24 18:46:11 浏览数 (1)

前言

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

知识回顾

前面介绍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,然后一层层查找的,在本篇中没有深入去了解这个过程。

0 人点赞