Spring 5.x 源码之ClassPathBeanDefinitionScanner

Spring 5.x 源码之ClassPathBeanDefinitionScanner

AnnotatedBeanDefinitionReader和ClassPathBeanDefinitionScanner的初始化是spring上线文初始化的起点,很多预加载的类会在spring接下来的初始化中发挥重要作用;

下面就是重点看看doScan()方法:

复制代码
protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
		Assert.notEmpty(basePackages, "At least one base package must be specified");
		Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>();
		for (String basePackage : basePackages) {
			// TODO 这个是重点,会把该包下面所有的Bean都扫描进去
			Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
			for (BeanDefinition candidate : candidates) {
				// TODO 拿到Scope元数据:此处为singleton
				ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);
				candidate.setScope(scopeMetadata.getScopeName());
				// TODO 生成Bean的名称,默认为首字母小写. 此处为"myTestService"
				String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);
				// TODO 此处为扫描的Bean ,为ScannedGenericBeanDefinition ,因为继承GenericBeanDefinition的父类AbstractBeanDefinition, 实现AnnotatedBeanDefinition所以为true
				if (candidate instanceof AbstractBeanDefinition) {
					postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);
				}
				// TODO 也是完善比如Bean上的一些注解信息:比如@Lazy、@Primary、@DependsOn、@Role、@Description   @Role注解用于Bean的分类分组,没有太大的作用
				if (candidate instanceof AnnotatedBeanDefinition) {
					AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);
				}
				if (checkCandidate(beanName, candidate)) {
					BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
					definitionHolder =
							AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
					beanDefinitions.add(definitionHolder);
					// TODO 注意 注意 注意:这里已经吧Bean注册进去工厂了,所有doScan()方法不接收返回值,也是没有任何问题的
					registerBeanDefinition(definitionHolder, this.registry);
				}
			}
		}
		return beanDefinitions;
	}
org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider#findCandidateComponents
复制代码
public Set<BeanDefinition> findCandidateComponents(String basePackage) {
    if (this.componentsIndex != null && indexSupportsIncludeFilters()) {
        return addCandidateComponentsFromIndex(this.componentsIndex, basePackage);
    }
    else {
        return scanCandidateComponents(basePackage);
    }
}
scanCandidateComponents:根据basePackage扫描候选的组件们(非常重要)
复制代码
	private Set<BeanDefinition> scanCandidateComponents(String basePackage) {
		Set<BeanDefinition> candidates = new LinkedHashSet<>();
		try {
			// 1.根据指定包名 生成包搜索路径
			//通过观察resolveBasePackage()方法的实现, 我们可以在设置basePackage时, 使用形如${}的占位符, Spring会在这里进行替换 只要在Enviroment里面就行
			// 本次值为:classpath*:com/fsx/config/**/*.class
			String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
					resolveBasePackage(basePackage) + '/' + this.resourcePattern;
			
			//2. 资源加载器 加载搜索路径下的 所有class 转换为 Resource[]
			// 拿着上面的路径,就可以getResources获取出所有的.class类,这个很强大~~~
			// 真正干事的为:PathMatchingResourcePatternResolver#getResources方法
			// 此处能扫描到两个类AppConfig(普通类,没任何注解标注)和RootConfig。所以接下里就是要解析类上的注解,以及过滤掉不是候选的类(比如AppConfig)
			
			// 注意:这里会拿到类路径下(不包含jar包内的)的所有的.class文件 可能有上百个,然后后面再交给后面进行筛选~~~~~~~~~~~~~~~~(这个方法,其实我们也可以使用)
			// 当然和getResourcePatternResolver和这个模版有关
			Resource[] resources = getResourcePatternResolver().getResources(packageSearchPath);

			// 记录日志(下面我把打印日志地方都删除)
			boolean traceEnabled = logger.isTraceEnabled();
			boolean debugEnabled = logger.isDebugEnabled();

			// 接下来的这个for循环:就是把一个个的resource组装成
			for (Resource resource : resources) {
				//文件必须可读 否则直接返回空了
				if (resource.isReadable()) {
					try {

						//读取类的 注解信息 和 类信息 ,两大信息储存到  MetadataReader
						MetadataReader metadataReader = getMetadataReaderFactory().getMetadataReader(resource);
				
						// 根据TypeFilter过滤排除组件。因为AppConfig没有标准@Component或者子注解,所以肯定不属于候选组件  返回false
						// 注意:这里一般(默认处理的情况下)标注了默认注解的才会true,什么叫默认注解呢?就是@Component或者派生注解。还有javax....的,这里省略啦
						if (isCandidateComponent(metadataReader)) {
						
							//把符合条件的 类转换成 BeanDefinition
							ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);
							sbd.setResource(resource);
							sbd.setSource(resource);
							
							// 再次判断 如果是实体类 返回true,如果是抽象类,但是抽象方法 被 @Lookup 注解注释返回true (注意 这个和上面那个是重载的方法) 
							// 这和上面是个重载方法  个人觉得旨在处理循环引用以及@Lookup上
							if (isCandidateComponent(sbd)) {
								candidates.add(sbd);
							}
						}
					} 
				}
			}
		}
		return candidates;
	}

// 备注:此时ComponentScan这个注解还并没有解析

MetadataReader metadataReader = getMetadataReaderFactory().getMetadataReader(resource);

new CachingMetadataReaderFactory().getMetadataReader(resource);

super.getMetadataReader(resource);

org.springframework.core.type.classreading.SimpleMetadataReaderFactory#getMetadataReader(org.springframework.core.io.Resource)

public MetadataReader getMetadataReader(Resource resource) throws IOException {

return new SimpleMetadataReader(resource, this.resourceLoader.getClassLoader());

}

AnnotationMetadataReadingVisitor visitor = new AnnotationMetadataReadingVisitor(classLoader);

classReader.accept(visitor, ClassReader.SKIP_DEBUG);

CachingMetadataReaderFactory(); 读取工厂类

SimpleMetadataReader读取器

AnnotationMetadataReadingVisitor visitor = new AnnotationMetadataReadingVisitor(classLoader);

classReader.accept(visitor, ClassReader.SKIP_DEBUG);

ClassPathBeanDefinitionScanner#postProcessBeanDefinition
复制代码
protected void postProcessBeanDefinition(AbstractBeanDefinition beanDefinition, String beanName) {
		beanDefinition.applyDefaults(this.beanDefinitionDefaults);
		if (this.autowireCandidatePatterns != null) {
			beanDefinition.setAutowireCandidate(PatternMatchUtils.simpleMatch(this.autowireCandidatePatterns, beanName));
		}
	}

public void applyDefaults(BeanDefinitionDefaults defaults) {
		setLazyInit(defaults.isLazyInit());
		setAutowireMode(defaults.getAutowireMode());
		setDependencyCheck(defaults.getDependencyCheck());
		setInitMethodName(defaults.getInitMethodName());
		setEnforceInitMethod(false);
		setDestroyMethodName(defaults.getDestroyMethodName());
		setEnforceDestroyMethod(false);
	}

理解ClassPathBeanDefinitionScanner的工作原理,可以帮助理解Spring IOC 容器的初始化过程。

同时对理解MyBatis 的 Mapper 扫描 也是有很大的帮助。

因为 MyBatis 的MapperScannerConfigurer的底层实现也是一个ClassPathBeanDefinitionScanner的子类。就像我们自定义扫描器那样,自定定义了 过滤器的过滤规则。

相关推荐
赫瑞11 分钟前
数据结构中的排列组合 —— Java实现
java·开发语言·数据结构
Victor35635 分钟前
MongoDB(87)如何使用GridFS?
后端
Victor35638 分钟前
MongoDB(88)如何进行数据迁移?
后端
小红的布丁1 小时前
单线程 Redis 的高性能之道
redis·后端
GetcharZp1 小时前
Go 语言只能写后端?这款 2D 游戏引擎刷新你的认知!
后端
周末也要写八哥1 小时前
多进程和多线程的特点和区别
java·开发语言·jvm
惜茶2 小时前
vue+SpringBoot(前后端交互)
java·vue.js·spring boot
宁瑶琴2 小时前
COBOL语言的云计算
开发语言·后端·golang
杰克尼3 小时前
springCloud_day07(MQ高级)
java·spring·spring cloud
普通网友3 小时前
阿里云国际版服务器,真的是学生党的性价比之选吗?
后端·python·阿里云·flask·云计算