【Spring】Spring原理源码解析(二)-- Spring怎么识别@ComponentScan类并完成扫描生成BD注册到beanDefinitionMap中的

本文深入探讨Spring框架中@ComponentScan注解的工作原理和源码解析。从Spring初始化开始,分析ConfigurationClassPostProcessor如何处理配置类,进而触发扫描过程。通过ConfigurationClassParser解析配置类,使用ClassPathBeanDefinitionScanner进行包扫描,识别@Component等注解的类并将其注册为Bean。文章详细讲解了每个关键步骤的源码和逻辑,帮助读者理解Spring的核心机制。
摘要由CSDN通过智能技术生成

系列文章主旨

从如何把自己的Bean注册到Spring容器开始,从该点出发,每篇只关注一个核心流程的原理、源码,再由该篇带出引申出来的其他Spring知识点,继续剖析,最终达到理解Spring核心原理的目的;

上篇内容

分析了Spring如何把自己的类注册到容器中,并且引申到spring会实例化自己内部的postProcessor完成扫描bean;
详情:【Spring】Spring原理源码解析(一)-- 从如何把自己的类注册为Bean到容器开始

本篇内容

从上一篇的spring内部实现的ConfigurationClassPostProcessor,分析spring识别@ComponentScan并完成扫描的原理源码;
文章图片均出自:https://www.processon.com/view/link/62d776fb6376893785aaa0df

核心原理

代码示例

指定配置类初始化spring容器,方法内部会把配置类注册到spring容器中

public static void main(String[] args) {
   
	  // 该方法会把我们的配置类注册到spirng容器中
	  // 并执行refresh方法,初始化spring容器
      AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MyConfig.class);
  }

配置类MyConfig,指定了扫描包路径

@ComponentScan(basePackages = "com.zsh.demo.spring.learn.register.bean")
public class MyConfig {
   
}

图解

在这里插入图片描述

核心处理逻辑

Spring容器进行初始化时,会默认生成自己的bean工厂后置处理器ConfigurationClassPostProcessor,并注册到spring容器中,然后在执行所有的bean工厂后置处理时,就会执行到ConfigurationClassPostProcessor的postProcessBeanDefinitionRegistry()方法;

在执行该方法时,会实例化配置类的解析器ConfigurationClassParser,该解析器会解析我们注册到Spring的配置类,该jie类可以解析配置类的所有注解,得到配置类的注解元数据信息,本篇解析的是@ComponentScan注解的元数据信息;

其内部的还有一个组件解析器componentScanParser,把解析完的@ComponentScan注解的元数据信息交给该解析器,调用其解析方法parse();该组件会实例化扫描器ClassPathBeanDefinitionScanner,这个扫描器会真正的去执行扫描的动作;

ClassPathBeanDefinitionScanner该组件本身重要的组件为include、exclude拦截器,通过使用ASM技术1得到的类文件数据,会借由拦截器校验是否符合Spring认为的规则,如:@Component注解标识的类,就会把这些类构建为BeanDefinition对象,这样,就得到了所有的扫描bean;

进阶源码解析

spring注册配置类后初始化,调用内置工厂后置处理器
源码解析
public AnnotationConfigApplicationContext(Class<?>... componentClasses) {
   
	this();
	// 注册配置类
	register(componentClasses);
	// 初始化
	refresh();
}

this()方法会把内部的工厂后置处理器ConfigurationClassPostProcessor,注册到spring容器中,位置是在:org.springframework.context.annotation.AnnotationConfigUtils#registerAnnotationConfigProcessors(org.springframework.beans.factory.support.BeanDefinitionRegistry, java.lang.Object),详情可以参考【Spring】Spring原理源码解析(一)-- 从如何把自己的类注册为Bean到容器开始

register()方法把配置类解析为BeanDefinition并注册到spring容器中;
refresh()方法为spring容器初始化方法,跟进去,找到调用工厂后置处理器调用方法,invokeBeanFactoryPostProcessors;
继续跟进,找到调用非api加入的BeanFactoryPostProcessors调用postProcessBeanDefinitionRegistry位置

public static void invokeBeanFactoryPostProcessors(
			ConfigurableListableBeanFactory beanFactory, List<BeanFactoryPostProcessor> beanFactoryPostProcessors) {
   
	// .. 忽略非相关代码
	// 在这里调用
	invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry);
	// .. 忽略非相关代码
}

这里就是ConfigurationClassPostProcessor实现的postProcessBeanDefinitionRegistry()方法

@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
   
	// 忽略非相关

	// 继续跟进
	processConfigBeanDefinitions(registry);
}

跟进方法之后,这就是我们要分析的扫描Bean的入口

识别并扫描Bean,源码入口
public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
   
	List<BeanDefinitionHolder> configCandidates = new ArrayList<>();
	String[] candidateNames = registry.getBeanDefinitionNames();

	for (String beanName : candidateNames) {
   
		BeanDefinition beanDef = registry.getBeanDefinition(beanName);
		if (beanDef.getAttribute(ConfigurationClassUtils.CONFIGURATION_CLASS_ATTRIBUTE) != null) {
   
			if (logger.isDebugEnabled()) {
   
				logger.debug("Bean definition has already been processed as a configuration class: " + beanDef);
			}
		}
		else if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) {
   
			configCandidates.add(new BeanDefinitionHolder(beanDef, beanName));
		}
	}

	// Return immediately if no @Configuration classes were found
	if (configCandidates.isEmpty()) {
   
		return;
	}

	// Sort by previously determined @Order value, if applicable
	configCandidates.sort((bd1, bd2) -> {
   
		int i1 = ConfigurationClassUtils.getOrder(bd1.getBeanDefinition());
		int i2 = ConfigurationClassUtils.getOrder(bd2.getBeanDefinition());
		return Integer.compare(i1, i2);
	});

	// Detect any custom bean name generation strategy supplied through the enclosing application context
	SingletonBeanRegistry sbr = null;
	if (registry instanceof SingletonBeanRegistry) {
   
		sbr = (SingletonBeanRegistry) registry;
		if (!this.localBeanNameGeneratorSet) {
   
			BeanNameGenerator generator = (BeanNameGenerator) sbr.getSingleton(
					AnnotationConfigUtils.CONFIGURATION_BEAN_NAME_GENERATOR);
			if (generator != null) {
   
				this.componentScanBeanNameGenerator = generator;
				this.importBeanNameGenerator = generator;
			}
		}
	}

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

	// Parse each @Configuration class
	ConfigurationClassParser parser = new ConfigurationClassParser(
			this.metadataReaderFactory, this.problemReporter, this.environment,
			this.resourceLoader, this.componentScanBeanNameGenerator, registry);

	Set<BeanDefinitionHolder> candidates = new LinkedHashSet<>(configCandidates);
	Set<ConfigurationClass> alreadyParsed = new HashSet<>(configCandidates.size());
	do {
   
		// 核心入口
		parser.parse(candidates);
		parser.validate();

		// ..忽略非相关
	}
	while (!candidates.isEmpty());

	// ..忽略非相关
}

下面就是围绕着该方法进行解析;

初始化配置类解析器ConfigurationClassParser
找到容器中的所有配置类
图解

在这里插入图片描述

源码解析

先从spring拿到所有已注册bean的名称,然后进行遍历;
遍历时,会通过bean名称拿到BeanDefinition,然后先看是不是没有处理过,没有的话继续,通过checkConfigurationClassCandidate()方法,知道是不是配置类,是的话,会存入到configCandidates集合里面;
如果集合里面最后没有配置类,就直接返回了,因为连扫包的路径都不知道;
如果有的话,给这些配置类进行一个排序,等待后面有序解析;

// 拿到所有bean的名称
List<BeanDefinitionHolder> configCandidates = new ArrayList<>();
String[] candidateNames = registry.getBeanDefinitionNames();

// 遍历
for (String beanName : candidateNames) {
   
	// 拿到bean定义
	BeanDefinition beanDef = registry.getBeanDefinition(beanName);
	// 这里是看下是不是重复校验了bean,因为有可能名称不一样,但实际上是同一个类
	if (beanDef.getAttribute(ConfigurationClassUtils.CONFIGURATION_CLASS_ATTRIBUTE) != null) {
   
		if (logger.isDebugEnabled()) {
   
			logger.debug("Bean definition has already been processed as a configuration class: " + beanDef);
		}
	}
	// 这里checkConfigurationClassCandidate()会校验这些bean哪些是配置类
	else if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) {
   
		configCandidates.add(new BeanDefinitionHolder(beanDef, beanName));
	}
}

// 如果没有找到配置类,则没有需要扫描的包,这里就会直接退出
if (configCandidates.isEmpty()) {
   
	return;
}

// 对要被解析的这些配置Bean,进行一个排序
configCandidates.sort((bd1, bd2) -> {
   
	int i1 = ConfigurationClassUtils.getOrder(bd1.getBeanDefinition());
	int i2 = ConfigurationClassUtils.getOrder(bd2.getBeanDefinition());
	return Integer.compare(i1, i2);
});

这个方法主要是先通过bd拿到元数据信息
然后先通过最简单:
如果不是代理类,又有@Configuration注解,则是配置类;
或者看isConfigurationCandidate()这个方法的校验是不是配置类;
如果上述两个校验都不符合,则不是配置类;
如果是配置类,则把@Order的值放入到元数据,在元数据打上标识(完全是配置类,可能是配置类,无论哪种都会被解析),后续该bd就不会被重复校验了;

public static boolean checkConfigurationClassCandidate(
			BeanDefinition beanDef, MetadataReaderFactory metadataReaderFactory) {
   

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

	AnnotationMetadata metadata;
	// 先看是不是被注解的类,是的话拿到元数据
	if (beanDef instanceof AnnotatedBeanDefinition &&
			className.equals((
使用spring-integration-webflux的DSL实现客户端和服务端流式交互需要以下步骤: 1. 引入依赖 在pom.xml文件引入以下依赖: ```xml <dependency> <groupId>org.springframework.integration</groupId> <artifactId>spring-integration-webflux</artifactId> </dependency> ``` 2. 创建服务端 使用WebFlux.fn来创建服务端,示例代码如下: ```java import org.springframework.http.MediaType; import org.springframework.integration.dsl.IntegrationFlow; import org.springframework.integration.dsl.MessageChannels; import org.springframework.integration.webflux.dsl.WebFlux; import org.springframework.stereotype.Component; @Component public class Server { public IntegrationFlow serverFlow() { return IntegrationFlows.from(WebFlux.inboundGateway("/stream") .requestMapping(m -> m.produces(MediaType.APPLICATION_STREAM_JSON_VALUE)) .requestPayloadType(String.class)) .channel(MessageChannels.flux()) .log() .map(String::toUpperCase) .map(s -> s + "-stream") .get(); } } ``` 该方法创建了一个接收客户端请求的WebFlux.inboundGateway,并将请求传递给MessageChannels.flux()通道,接着执行一些转换操作,并返回结果。在示例,将请求转换为大写并添加后缀“-stream”。 3. 创建客户端 使用WebFlux.fn来创建客户端,示例代码如下: ```java import org.springframework.http.MediaType; import org.springframework.integration.dsl.IntegrationFlow; import org.springframework.integration.dsl.MessageChannels; import org.springframework.integration.webflux.dsl.WebFlux; import org.springframework.stereotype.Component; @Component public class Client { public IntegrationFlow clientFlow() { return IntegrationFlows.from(MessageChannels.flux()) .handle(WebFlux.outboundGateway("http://localhost:8080/stream") .httpMethod(HttpMethod.POST) .expectedResponseType(String.class) .expectedResponseType(MediaType.APPLICATION_STREAM_JSON_VALUE)) .log() .get(); } } ``` 该方法从MessageChannels.flux()通道接收请求,并将请求发送到WebFlux.outboundGateway,该网关将请求发送到服务端的“/stream”端点。示例,期望响应为String型,MediaType为APPLICATION_STREAM_JSON_VALUE。 4. 创建Spring应用程序 在Spring Boot应用程序的主,创建Spring Integration流: ```java import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.annotation.Bean; import org.springframework.integration.dsl.IntegrationFlow; @SpringBootApplication public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } @Bean public IntegrationFlow serverFlow(Server server) { return server.serverFlow(); } @Bean public IntegrationFlow clientFlow(Client client) { return client.clientFlow(); } } ``` 5. 测试 启动应用程序并访问http://localhost:8080/stream,应该能看到似以下的输出: ``` 2019-12-03 16:37:45.947 INFO 6282 --- [ctor-http-nio-2] o.s.integration.handler.LoggingHandler : GenericMessage [payload=Hello, world!, headers={id=0c9be9f7-191f-8b1a-3f92-12d2c2bd8eaa, contentType=application/json;charset=UTF-8}] 2019-12-03 16:37:45.947 INFO 6282 --- [ctor-http-nio-2] o.s.integration.handler.LoggingHandler : GenericMessage [payload=HELLO, WORLD!-STREAM, headers={id=dcf0d3c6-9bca-2c39-0f7c-5d5b5db5c5a8, contentType=application/json;charset=UTF-8}] ``` 这表示客户端已成功将请求发送到服务端,并且服务端已成功执行转换操作并将响应发送回客户端。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值