Spring注解扫描-@ComponentScan底层原理分析
Spring注解扫描-@ComponentScan底层原理分析
1、@ComponentScan 简介
@ComponentScan 注解的作用可以简述为:将项目中所有被 @Component 注解直接或者间接标记的类组装成BeanDefinition, 然后以Map<beanName, BeanDefinition>的形式存储,为后续生成bean对象做准备。 继承了@Component的注解包括:@Controller、@RestController、@Repository、@Service、@Configuration等。
@ComponentScan重要参数:
- value:用来指定basePackage的路径;
- excludeFilters:可以把被@Component标识的类排除扫描;
- includeFilters:可以把不被@Component标识的类加入到扫描;
示例:
@ComponentScan(value = "com.dw",
excludeFilters = {@ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE,classes = UserService.class)},
includeFilters = {@ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE,classes = User.class)})
2、在讲源码之前呢,我们可以先增强几个知识点的概念。这些知识点在阅读Spring扫描源码中起着关键作用。
2.1、被@ComponentScan扫描注入到IOC容器中的BeanDefinition,其具体实现类是:ScannedGenericBeanDefinition。
2.2、@ComponentScan扫描涉及到的相关注解:@Conditional和@Scope和@Lookup和@Lazy、@Primary、@DependsOn、@Role、@Description。
@Conditional的作用是:即使该类被@Component修饰,但是Conditional返回false,该类也不会被成功扫描; @Scope的作用是:仅仅作为一个标识,赋值给BeanDefinition的scope属性; @Lookup:抽象类本身不能被扫描进容器,但是被@Lookup注解修饰则可以; @Lazy、@Primary、@DependsOn、@Role:这几个类是一起被处理的,也只是用来给BeanDefinition赋值,分别对应着:setLazyInit(boolean)、setPrimary(boolean)、setDependsOn(value)、setRole(value)、setDescription(value);
2.3、须知Resource类和MetadataReader类。 Resource[]数组对象会存储basePackage包路径下所有的class的文件对象(注意:是所有的class的文件对象都会存储,无论有没有被@Component修饰); MetadataReader类是Spring用来解析类的信息(比如类名、类中的方法、类上的注解、是否是抽象类…,这些都可以称之为类的元数据)的一个工具类。而 ClassMetadata、AnnotationMetadata都用来封装解析后的类信息。
3、源代码调用链路分析
Spring配置类的解析都是通过ConfigurationClassPostProcessor 这个类处理的, 而这个类实现了BeanDefinitionRegistryPostProcessor接口, 这个接口主要是Spring容器初始化时, 对IOC容器中的Bean的增删改操作。 具体的调用链路如下:
org.springframework.context.annotation.ConfigurationClassPostProcessor#postProcessBeanDefinitionRegistry()--> #processConfigBeanDefinitions()
-->
org.springframework.context.annotation.ConfigurationClassParser#parse(Set<BeanDefinitionHolder>)
-->
org.springframework.context.annotation.ConfigurationClassParser#processConfigurationClass()--> #doProcessConfigurationClass()
-->
org.springframework.context.annotation.ComponentScanAnnotationParser#parse()
-->
org.springframework.context.annotation.ClassPathBeanDefinitionScanner#scan()--> #doScan()
4、核心源码分析
源码具体位置:org.springframework.context.annotation.ClassPathBeanDefinitionScanner#doScan
protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
Assert.notEmpty(basePackages, "At least one base package must be specified");
Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>();
// 遍历basePackages
for (String basePackage : basePackages) {
// 扫描basePackages下所有的文件,进行include、exclude、@Conditional判断后返回BeanDefinition
Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
// 循环处理candidates设置相关的属性值, 然后注册BeanDefinition
for (BeanDefinition candidate : candidates) {
// scope解析:单例与多例
ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);
candidate.setScope(scopeMetadata.getScopeName());
// 生成beanName:默认是类名首字母小写,如果@Component中指定了name则使用指定的name
String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);
/* ScannedGenericBeanDefinition extends GenericBeanDefinition(AbstractBeanDefinition)
implements AnnotatedBeanDefinition */
// 设置BeanDefinition的默认值
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);
// 注册: beanDefinitionMap.put(beanName, beanDefinition)
registerBeanDefinition(definitionHolder, this.registry);
}
}
}
return beanDefinitions;
}
上面doScan()方法的核心其实是 findCandidateComponents(basePackage)--> scanCandidateComponents(basePackage) 这个方法, 主要负责basePackage包下资源文件的读取(PathMatchingResourcePatternResolver), 判断是否时候选的Bean, 如果是则会创建一个ScannedGenericBeanDefinition。 并添加到集合中,最终返回这个集合, 会注入到IOC容器中。