spring源码解析-扫描
相信这双手,无论何时,都有力量把自己从任何处境中拉出来,凡是自强不息者,我辈皆能自救。
前言
spring扫描这部分相对来说还是比较简单的,但是也有一些比较有意思的点,例如spring扫描的时候竟然也有索引,这个是不是没有想到,见证奇迹的时候到了
一、扫描?
spring中的扫描就像是雷达,我们想干掉敌机(彻底理解spring)那么就应该先找到他的位置。
二、扫描逻辑
1.入口
代码如下(示例):
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
我们点进去AnnotationConfigApplicationContext这个方法,进入到以下方法:
public AnnotationConfigApplicationContext() {
StartupStep createAnnotatedBeanDefReader = this.getApplicationStartup().start("spring.context.annotated-bean-reader.create");
this.reader = new AnnotatedBeanDefinitionReader(this);
createAnnotatedBeanDefReader.end();
this.scanner = new ClassPathBeanDefinitionScanner(this);
}
解析:
this.reader = new AnnotatedBeanDefinitionReader(this);
这行代码其实就是创建了个BeanDfinition的读取器,其实他是读取的但是并不是扫描的真正的扫描是
this.scanner = new ClassPathBeanDefinitionScanner(this);
ClassPathBeanDefinitionScanner 这个类里面有一个scan的方法其中调用了doscan方法就是spring的扫描逻辑
public int scan(String... basePackages) {
int beanCountAtScanStart = this.registry.getBeanDefinitionCount();
doScan(basePackages);
// Register annotation config processors, if necessary.
if (this.includeAnnotationConfig) {
AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
}
//看看扫描出来多少bean
return (this.registry.getBeanDefinitionCount() - beanCountAtScanStart);
}
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) {
Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
for (BeanDefinition candidate : candidates) {
ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);
candidate.setScope(scopeMetadata.getScopeName());
String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);
if (candidate instanceof AbstractBeanDefinition) {
postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);
}
if (candidate instanceof AnnotatedBeanDefinition) {
// 解析@Lazy、@Primary、@DependsOn、@Role、@Description
AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);
}
// 检查Spring容器中是否已经存在该beanName
if (checkCandidate(beanName, candidate)) {
BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
definitionHolder =
AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
beanDefinitions.add(definitionHolder);
// 注册
registerBeanDefinition(definitionHolder, this.registry);
}
}
}
return beanDefinitions;
}
首先得到一个basePackages是一个包路径类似于com.mo.yu这种形式的,然后去遍历所有的你定义的包路径一个一个进行扫描,然后会进入findCandidateComponents 这个方法
public Set<BeanDefinition> findCandidateComponents(String basePackage) {
//这个方法是索引方法 相当于你要在配置一个springcomponents文件用kv的形式写上,k包路径v那个注解,但是其实没有什么卵用
//就是扫描的时候速度稍微快一点不用循环,而是直接读取的这个配置文件里面的东西。spring 考虑的真周到TMD。
if (this.componentsIndex != null && indexSupportsIncludeFilters()) {
return addCandidateComponentsFromIndex(this.componentsIndex, basePackage);
}
else {
//一般都是直接进入这个方法
return scanCandidateComponents(basePackage);
}
}
我们一般都是会直接进入scanCandidateComponents这个方法中的但是如果你要是配置了springcomponents 文件就会进入if条件中,这个方法其实没啥卵用,有兴趣的同学可以进去看看。
private Set<BeanDefinition> scanCandidateComponents(String basePackage) {
Set<BeanDefinition> candidates = new LinkedHashSet<>();
try {
// 获取basePackage下所有的文件资源
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技术
MetadataReader metadataReader = getMetadataReaderFactory().getMetadataReader(resource);
// excludeFilters、includeFilters判断 看看是否需要排除
if (isCandidateComponent(metadataReader)) {
ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);
sbd.setSource(resource);
//看看是不是加了Component 注解
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 {
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;
}
这边他会把com.mo.yu这样的路径转换成
classpath*:com/mo/yu/**/*.class这样的路径,通过这个路径其实是得到了一个file的一个文件,也就是这个包下所有的文件,然后进行遍历
MetadataReader metadataReader = getMetadataReaderFactory().getMetadataReader(resource);
这行代码其实是他new了一个元数据读取器,可以理解成一个工具类,它可以读取类的信息,类里面的注解,已经里面的抽象类。
接下来回去判断你的注解上加没加一些排除的类,在下面的时候就会给你排除掉
看看是否加了Component注解
这个方法会判断你是不是接口是不是抽象类加了没有加Lookup注解
经一系列判断就会返回出去
解析出来bean的类型多例的还是单例的
在这里插入图片描述
在这里插入图片描述
解析是否是懒加载等这些注解
然后最终注册到beanDefinitionMap中去
总结
等再次摸鱼的时候来一个总结