持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第14天,点击查看活动详情
基于Spring 5.4.2版本分析
1. @Component概要
Spring中有一个重要的注解那就是 @Component 。对于Spring中不同场景下使用的注解例如:@Repository 、@Service 、 @Controller 都是通过 @Component 注解衍生出来的。
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Service {
@AliasFor(annotation = Component.class)
String value() default "";
}
在这里代码中还看到一个重要的注解来实现派生: @AliasFor 。
2. @Component入口
不论是现在Spring流行的注解方式还是以前的老式的XML配置方式都有一个入口。这里就只分析注解模式。
对于XML配置的方式可以从下面的代码中找到入口:
public class ContextNamespaceHandler extends NamespaceHandlerSupport {
@Override
public void init() {
registerBeanDefinitionParser("property-placeholder", new PropertyPlaceholderBeanDefinitionParser());
registerBeanDefinitionParser("property-override", new PropertyOverrideBeanDefinitionParser());
registerBeanDefinitionParser("annotation-config", new AnnotationConfigBeanDefinitionParser());
registerBeanDefinitionParser("component-scan", new ComponentScanBeanDefinitionParser());
registerBeanDefinitionParser("load-time-weaver", new LoadTimeWeaverBeanDefinitionParser());
registerBeanDefinitionParser("spring-configured", new SpringConfiguredBeanDefinitionParser());
registerBeanDefinitionParser("mbean-export", new MBeanExportBeanDefinitionParser());
registerBeanDefinitionParser("mbean-server", new MBeanServerBeanDefinitionParser());
}
}
对于注解方式
public AnnotationConfigApplicationContext(String... basePackages) {
this();
scan(basePackages);
refresh();
}
可以看一下 AnnotationConfigApplicationContext 类。
不管是注解方式还是XML方式最终都是通过 ClassPathBeanDefinitionScanner 类来将包含有 @Component 注解的类定义加载到Spring容器里面。
3. ClassPathBeanDefinitionScanner解析
ClassPathBeanDefinitionScanner 作用主要是扫描带有 @Component 注解的类并实现注册。下面来看一下 ClassPathBeanDefinitionScanner#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) {
//找基础包下面的候选的Component
Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
//下面是对加载的Component做进一步处理
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) {
AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);
}
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;
}
通过上面的代码可以看出来主要是通过 ClassPathScanningCandidateComponentProvider#findCandidateComponents 来实现加载
4. findCandidateComponents解析
findCandidateComponents方法属于ClassPathScanningCandidateComponentProvider#findCandidateComponents。
public Set<BeanDefinition> findCandidateComponents(String basePackage) {
if (this.componentsIndex != null && indexSupportsIncludeFilters()) {
return addCandidateComponentsFromIndex(this.componentsIndex, basePackage);
}
else {
return scanCandidateComponents(basePackage);
}
}
private Set<BeanDefinition> scanCandidateComponents(String basePackage) {
Set<BeanDefinition> candidates = new LinkedHashSet<>();
try {
String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
resolveBasePackage(basePackage) + '/' + this.resourcePattern;
Resource[] resources = getResourcePatternResolver().getResources(packageSearchPath);
//省略了部分打印的代码
for (Resource resource : resources) {
if (resource.isReadable()) {
try {
MetadataReader metadataReader = getMetadataReaderFactory().getMetadataReader(resource);
if (isCandidateComponent(metadataReader)) {
ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);
sbd.setSource(resource);
if (isCandidateComponent(sbd)) {
candidates.add(sbd);
}
}
}
catch (Throwable ex) {
throw new BeanDefinitionStoreException(
"Failed to read candidate component class: " + resource, ex);
}
}
else {
}
}
}
catch (IOException ex) {
throw new BeanDefinitionStoreException("I/O failure during classpath scanning", ex);
}
return candidates;
}
该方法主要还是从基础包中获取组件。从代码分析一下大概的思路:
- 根据基础包拼装扫描的路径正则表达式。
- 获取路径下面的
.class文件包装成Resource数组。 - 将
.class文件包装成Resource处理成MetadataReader - 判断
MetadataReader是否为候选组件 - 判断
ScannedGenericBeanDefinition是否为候选组件,ScannedGenericBeanDefinition由MetadataReader构建。
判断是否为候选组件通过 ClassPathScanningCandidateComponentProvider#isCandidateComponent 方法。
5. isCandidateComponent方法解析
isCandidateComponent 方法在 ClassPathScanningCandidateComponentProvider#isCandidateComponent 类中。
protected boolean isCandidateComponent(MetadataReader metadataReader) throws IOException {
//省略部分代码
for (TypeFilter tf : this.includeFilters) {
//判断是否为@Component注解修饰
if (tf.match(metadataReader, getMetadataReaderFactory())) {
return isConditionMatch(metadataReader);
}
}
return false;
}
在 ClassPathScanningCandidateComponentProvider 初始化的时候回只设置了 @Component 注解。并没有看到 @Repository 、@Service 、 @Controller 这些。
//ClassPathScanningCandidateComponentProvider#registerDefaultFilters
protected void registerDefaultFilters() {
this.includeFilters.add(new AnnotationTypeFilter(Component.class));
ClassLoader cl = ClassPathScanningCandidateComponentProvider.class.getClassLoader();
try {
this.includeFilters.add(new AnnotationTypeFilter(
((Class<? extends Annotation>) ClassUtils.forName("javax.annotation.ManagedBean", cl)), false));
logger.trace("JSR-250 'javax.annotation.ManagedBean' found and supported for component scanning");
}
catch (ClassNotFoundException ex) {
// JSR-250 1.1 API (as included in Java EE 6) not available - simply skip.
}
try {
this.includeFilters.add(new AnnotationTypeFilter(
((Class<? extends Annotation>) ClassUtils.forName("javax.inject.Named", cl)), false));
logger.trace("JSR-330 'javax.inject.Named' annotation found and supported for component scanning");
}
catch (ClassNotFoundException ex) {
// JSR-330 API not available - simply skip.
}
}
前面说过 @Repository 、@Service 、 @Controller 都是通过 @Component 派生来的。
6. @Component派生过程解析
在 ClassPathScanningCandidateComponentProvider#scanCandidateComponents 有这样一段代码
MetadataReader metadataReader = getMetadataReaderFactory().getMetadataReader(resource);
public final MetadataReaderFactory getMetadataReaderFactory() {
if (this.metadataReaderFactory == null) {
this.metadataReaderFactory = new CachingMetadataReaderFactory();
}
return this.metadataReaderFactory;
}
这一段代码主要是为了获取 MetadataReader 。该接口定义了获取当前类注解的元数据和当前类的元数据。在Spring框架中唯一的实现就是 SimpleMetadataReader 。下面来看一下 SimpleMetadataReader 类的构造函数:
SimpleMetadataReader(Resource resource, @Nullable ClassLoader classLoader) throws IOException {
SimpleAnnotationMetadataReadingVisitor visitor = new SimpleAnnotationMetadataReadingVisitor(classLoader);
getClassReader(resource).accept(visitor, PARSING_OPTIONS);
this.resource = resource;
this.annotationMetadata = visitor.getMetadata();
}
在构造函数中有两个类:
-
SimpleAnnotationMetadataReadingVisitor
主要用于访问注解
-
ClassReader
类读取器,基于ASM框架实现的。
这里我们还可以看一下类的元数据类的继承关系:
在 SimpleMetadataReader 类的构造函数中有这样一段代码 getClassReader(resource).accept(visitor, PARSING_OPTIONS); 主要是用来类上面的注解。包括注解上面的注解。
getClassReader(resource).accept(visitor, PARSING_OPTIONS)这段代码就是实现了注解派生的关键
7. 猜想验证
首先写一个Controller
然后在 IDEA设置条件断点:
看一下metadata变量:
然后看一下变量值annotations:
这里可以看出来已经读取到了 AsyncController 类上面的注解,然后在变量中有一个mappings的变量下面来看一下:
这个里面就包含了除了Java的注解以外的所有注解。(这里由于页面的关系不能完全展示)
8. 总结
- @Component是spring注解的基础
- ClassPathBeanDefinitionScanner扫描制定基础包下面的包含@Component注解
- 通过ClassReader(基于ASM实现)来读取类上面所有的注解。
我是蚂蚁背大象,文章对你有帮助点赞关注我,文章有不正确的地方请您斧正留言评论~谢谢!