文档章节

Spring IoC 源码分析 (基于注解) 之 包扫描

大王叫下
 大王叫下
发布于 08/19 17:13
字数 2058
阅读 1227
收藏 21

在上篇文章Spring IoC 源码分析 (基于注解) 一我们分析到,我们通过AnnotationConfigApplicationContext类传入一个包路径启动Spring之后,会首先初始化包扫描的过滤规则。那我们今天就来看下包扫描的具体过程。

还是先看下面的代码:
AnnotationConfigApplicationContext类

//该构造函数会自动扫描以给定的包及其子包下的所有类,并自动识别所有的Spring Bean,将其注册到容器中
	public AnnotationConfigApplicationContext(String... basePackages) {
		//初始化
		this();
		//扫描包、注册bean
		scan(basePackages);
		refresh();
	}

上文我们分析了this()方法,会去初始化AnnotatedBeanDefinitionReader读取器和ClassPathBeanDefinitionScanner扫描器,并初始化扫描过滤规则。
接下来我们看一下scan(basePackages)方法:
一直跟踪下去,发现调用了ClassPathBeanDefinitionScanner类中的scan()方法

//调用类路径Bean定义扫描器入口方法
	public int scan(String... basePackages) {
		//获取容器中已经注册的Bean个数
		int beanCountAtScanStart = this.registry.getBeanDefinitionCount();

		//启动扫描器扫描给定包
		doScan(basePackages);

		// Register annotation config processors, if necessary.
		//注册注解配置(Annotation config)处理器
		if (this.includeAnnotationConfig) {
			AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
		}

		//返回注册的Bean个数
		return (this.registry.getBeanDefinitionCount() - beanCountAtScanStart);
	}

可以看到主要是doScan(basePackages)方法实现了扫描的逻辑,我们继续跟踪进去看下

//类路径Bean定义扫描器扫描给定包及其子包
	protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
		Assert.notEmpty(basePackages, "At least one base package must be specified");
		//创建一个集合,存放扫描到Bean定义的封装类
		Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>();
		//遍历扫描所有给定的包
		for (String basePackage : basePackages) {
			//调用父类ClassPathScanningCandidateComponentProvider的方法
			//扫描给定类路径,获取符合条件的Bean定义
			Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
			//遍历扫描到的Bean
			for (BeanDefinition candidate : candidates) {
				//获取@Scope注解的值,即获取Bean的作用域
				ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);
				//为Bean设置作用域
				candidate.setScope(scopeMetadata.getScopeName());
				//为Bean生成名称
				String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);
				//如果扫描到的Bean不是Spring的注解Bean,则为Bean设置默认值,
				//设置Bean的自动依赖注入装配属性等
				if (candidate instanceof AbstractBeanDefinition) {
					postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);
				}
				//如果扫描到的Bean是Spring的注解Bean,则处理其通用的Spring注解
				if (candidate instanceof AnnotatedBeanDefinition) {
					//处理注解Bean中通用的注解,在分析注解Bean定义类读取器时已经分析过
					AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);
				}
				//根据Bean名称检查指定的Bean是否需要在容器中注册,或者在容器中冲突
				if (checkCandidate(beanName, candidate)) {
					BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
					//根据注解中配置的作用域,为Bean应用相应的代理模式
					definitionHolder =
							AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
					beanDefinitions.add(definitionHolder);
					//向容器注册扫描到的Bean
					registerBeanDefinition(definitionHolder, this.registry);
				}
			}
		}
		return beanDefinitions;
	}

这一大段代码基本上就是spring扫描识别注解,并注册Bean到IOC容器中的代码。
在第10行有一个findCandidateComponents(basePackage)方法,这个方法里就是具体的扫描逻辑。
继续跟踪:
ClassPathScanningCandidateComponentProvider类

//扫描给定类路径的包
	public Set<BeanDefinition> findCandidateComponents(String basePackage) {
		//spring5.0开始 索引 开启的话生成文件META-INF/spring.components 后面加载直接从本地文件读取(一般不建议开启 spring.index.ignore=true)
		if (this.componentsIndex != null && indexSupportsIncludeFilters()) {
			return addCandidateComponentsFromIndex(this.componentsIndex, basePackage);
		}
		else {
			return scanCandidateComponents(basePackage);
		}
	}

这里有一个if判断,我们默认走的是else里的分支,即scanCandidateComponents(basePackage)方法。

private Set<BeanDefinition> scanCandidateComponents(String basePackage) {
		Set<BeanDefinition> candidates = new LinkedHashSet<>();
		try {
			//补全扫描路径,扫描所有.class文件 classpath*:com/mydemo/**/*.class
			String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
					resolveBasePackage(basePackage) + '/' + this.resourcePattern;
			//定位资源
			Resource[] resources = getResourcePatternResolver().getResources(packageSearchPath);
			boolean traceEnabled = logger.isTraceEnabled();
			boolean debugEnabled = logger.isDebugEnabled();
			for (Resource resource : resources) {
				if (traceEnabled) {
					logger.trace("Scanning " + resource);
				}
				if (resource.isReadable()) {
					try {
						//通过ASM获取class元数据,并封装在MetadataReader元数据读取器中
						MetadataReader metadataReader = getMetadataReaderFactory().getMetadataReader(resource);
						//判断该类是否符合@CompoentScan的过滤规则
						//过滤匹配排除excludeFilters排除过滤器(可以没有),包含includeFilter中的包含过滤器(至少包含一个)。
						if (isCandidateComponent(metadataReader)) {
							//把元数据转化为 BeanDefinition
							ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);
							sbd.setResource(resource);
							sbd.setSource(resource);
							//判断是否是合格的bean定义
							if (isCandidateComponent(sbd)) {
								if (debugEnabled) {
									logger.debug("Identified candidate component class: " + resource);
								}
								//加入到集合中
								candidates.add(sbd);
							}
							else {
								//不合格 不是顶级类、具体类
								if (debugEnabled) {
									logger.debug("Ignored because not a concrete top-level class: " + resource);
								}
							}
						}
						else {
							//不符@CompoentScan过滤规则
							if (traceEnabled) {
								logger.trace("Ignored because not matching any filter: " + resource);
							}
						}
					}
					catch (Throwable ex) {
						throw new BeanDefinitionStoreException(
								"Failed to read candidate component class: " + resource, ex);
					}
				}
				else {
					if (traceEnabled) {
						logger.trace("Ignored because not readable: " + resource);
					}
				}
			}
		}
		catch (IOException ex) {
			throw new BeanDefinitionStoreException("I/O failure during classpath scanning", ex);
		}
		return candidates;
	}

这里就是主要的扫描逻辑,代码中的注释已经说的很清楚了。
主要过程:

  • 根据包路径,扫描所有.class文件

  • 根据包路径,生成.class对应的Resource对象

  • 通过ASM获取class元数据,并封装在MetadataReader元数据读取器中

  • 判断该类是否符合过滤规则

  • 判断该类是否为独立的类、具体的类

  • 加入到集合中

我们来详细看下过滤的方法 isCandidateComponent(metadataReader)

//判断元信息读取器读取的类是否符合容器定义的注解过滤规则
	//@CompoentScan的过滤规则支持5种 (注解、类、正则、aop、自定义)
	protected boolean isCandidateComponent(MetadataReader metadataReader) throws IOException {
		//如果读取的类的注解在排除注解过滤规则中,返回false
		for (TypeFilter tf : this.excludeFilters) {
			if (tf.match(metadataReader, getMetadataReaderFactory())) {
				return false;
			}
		}
		//如果读取的类的注解在包含的注解的过滤规则中,则返回ture
		for (TypeFilter tf : this.includeFilters) {
			//判断当前类的注解是否match规则
			if (tf.match(metadataReader, getMetadataReaderFactory())) {
				//是否有@Conditional注解,进行相关处理
				return isConditionMatch(metadataReader);
			}
		}
		//如果读取的类的注解既不在排除规则,也不在包含规则中,则返回false
		return false;
	}

接着跟踪 tf.match()方法
AbstractTypeHierarchyTraversingFilter类

public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory)
			throws IOException {

		// This method optimizes avoiding unnecessary creation of ClassReaders
		// as well as visiting over those readers.
		//检查当前类的注解是否符合规律规则
		if (matchSelf(metadataReader)) {
			return true;
		}
		//check 类名是否符合规则
		ClassMetadata metadata = metadataReader.getClassMetadata();
		if (matchClassName(metadata.getClassName())) {
			return true;
		}

		//如果有继承父类
		if (this.considerInherited) {
			String superClassName = metadata.getSuperClassName();
			if (superClassName != null) {
				// Optimization to avoid creating ClassReader for super class.
				Boolean superClassMatch = matchSuperClass(superClassName);
				if (superClassMatch != null) {
					if (superClassMatch.booleanValue()) {
						return true;
					}
				}
				else {
					// Need to read super class to determine a match...
					try {
						if (match(metadata.getSuperClassName(), metadataReaderFactory)) {
							return true;
						}
					}
					catch (IOException ex) {
						logger.debug("Could not read super class [" + metadata.getSuperClassName() +
								"] of type-filtered class [" + metadata.getClassName() + "]");
					}
 				}
			}
		}

		//如果有实现接口
		if (this.considerInterfaces) {
			for (String ifc : metadata.getInterfaceNames()) {
				// Optimization to avoid creating ClassReader for super class
				Boolean interfaceMatch = matchInterface(ifc);
				if (interfaceMatch != null) {
					if (interfaceMatch.booleanValue()) {
						return true;
					}
				}
				else {
					// Need to read interface to determine a match...
					try {
						if (match(ifc, metadataReaderFactory)) {
							return true;
						}
					}
					catch (IOException ex) {
						logger.debug("Could not read interface [" + ifc + "] for type-filtered class [" +
								metadata.getClassName() + "]");
					}
				}
			}
		}

		return false;
	}

这里面最主要的是 matchSelf(metadataReader) 方法
AnnotationTypeFilter类

protected boolean matchSelf(MetadataReader metadataReader) {
		//获取注解元数据
		AnnotationMetadata metadata = metadataReader.getAnnotationMetadata();
		//check 注解及其派生注解中是否包含@Component
		//获取当前类的注解 metadata.hasAnnotation    @Controller
		//获取当前类的注解及其派生注解 metadata.hasAnnotation   @Controller包含的@Component\@Documented等等
		return metadata.hasAnnotation(this.annotationType.getName()) ||
				(this.considerMetaAnnotations && metadata.hasMetaAnnotation(this.annotationType.getName()));
	}

在这段代码代码中,可以解决我们之前的疑惑“Spring是怎么发现@Configuration、@Controller、@Service这些注解修饰的类的?
原来@Configuration、@Controller、@Service这些注解其实都是@Component的派生注解,我们看这些注解的代码会发现,都有@Component注解修饰。而spring通过metadata.hasMetaAnnotation()方法获取到这些注解包含@Component,所以都可以扫描到。如下:

然后我们再看回 scanCandidateComponents(basePackage)方法,接下来有一个 isCandidateComponent(sbd)方法,如下:

//是否是独立的类、具体的类
	protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
		AnnotationMetadata metadata = beanDefinition.getMetadata();
		return (metadata.isIndependent() && (metadata.isConcrete() ||
				(metadata.isAbstract() && metadata.hasAnnotatedMethods(Lookup.class.getName()))));
	}

这个方法的作用是,判断该类是否为

  • 顶层的类(没有父类或静态内部类)

  • 具体的类(不是抽象类或接口)

至此,ClassPathBeanDefinitionScanner类中的doScan(basePackages)方法中的findCandidateComponents(basePackage)方法已经结束了,即我们的包扫描也结束了,已经把扫描到的类存入到了集合中,结下来就是解析注册Bean的过程了。

总结
通过这篇文章,我们可以回答之前的一些问题了:

  • Spring是怎么发现@Bean、@Controller、@Service这些注解修饰的类的?
    通过 matchSelf(metadataReader)方法,判断这些注解中是否包含@Component

  • @CompoentScan注解是怎么起作用的?
    通过 isCandidateComponent(metadataReader)方法过滤

© 著作权归作者所有

大王叫下
粉丝 15
博文 61
码字总数 52321
作品 0
杭州
私信 提问
Spring 之 IoC 源码分析 (基于注解方式)

一、 IoC 理论 IoC 全称为 Inversion of Control,翻译为 “控制反转”,它还有一个别名为 DI(Dependency Injection),即依赖注入。 二、IoC方式 Spring为IoC提供了2种方式,一种是基于xml...

星爵22
08/19
260
0
手写Spring基本体系IOC+AOP+MVC+事物管理等简单实现。

注:由于本工程包含的内容较多。大部分设计图、代码示例不能提供。 请移步至码云clone查看:https://gitee.com/Coodyer/Coody-Framework 本文仅介绍实现思路。 By:Coody 644556636@qq.com ...

Coody
2018/03/01
1K
1
Spring对注解(Annotation)处理源码分析1——扫描和读取Bean定义

1.从Spring2.0以后的版本中,Spring也引入了基于注解(Annotation)方式的配置,注解(Annotation)是JDK1.5中引入的一个新特性,用于简化Bean的配置,某些场合可以取代XML配置文件。开发人员对注...

李长春
2011/10/08
1K
0
SpringBoot系列二:SpringBoot自动配置原理

原文出处:晴枫 1 SpringBoot运作原理 上一章中我们提到主程序类的注解 @SpringBootApplication 注解,它其实是个组合注解,源码如下: @Target({ElementType.TYPE})@Retention(RetentionPol...

晴枫
2018/11/21
0
0
Spring Boot 启动过程分析

1. Spring Boot 入口——main方法 从上面代码可以看出,Annotation定义(@SpringBootApplication)和类定义(SpringApplication.run)最为耀眼,所以分析 Spring Boot 启动过程,我们就从这两...

徐志毅
2018/05/27
0
0

没有更多内容

加载失败,请刷新页面

加载更多

cookie

cookie: n. 饼干;小甜点 为什么会引入Cookie(在客户端保持http状态) 因为http协议是一种无状态协议,web服务器本身不能识别出哪些请求是同一个服务器发送的,浏览器的每一次请求都是独立...

五公里
6分钟前
1
0
PHP常用函数

<?php/** * 获取客户端IP * @return [string] [description] */function getClientIp() { $ip = NULL; if (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) { $arr = explode('......

半缘修道半缘君丶
6分钟前
1
0
go语言环境搭建

一、vscode安装go环境: 如果go get不成功,可以使用git clone url dir_name go get github.com/nsf/gocodego get github.com/uudashr/gopkgs/cmd/gopkgsgo get github.com/fatih/gom......

mbzhong
17分钟前
2
0
线程数究竟设多少合理

转载: 2016-03-29 58沈剑 架构师之路 一、需求缘起 Web-Server通常有个配置,最大工作线程数,后端服务一般也有个配置,工作线程池的线程数量,这个线程数的配置不同的业务架构师有不同的经...

xiaolyuh
17分钟前
3
0
jQuery使用serialize()方法输出序列化表单值

实例 输出序列化表单值的结果: $("button").click(function(){ $("div").text($("form").serialize()); }); 定义和用法 serialize() 方法通过序列化表单值,创建 URL 编码文本字符串。 您可......

前端老手
24分钟前
1
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部