作者:温安适

来源:my.oschina.net/floor/blog/4325651

前言

@Component和@Service都是工作中常用的注解,Spring如何解析?

1.@Component解析流程

找入口

Spring Framework2.0开始,引入可扩展的XML编程机制,该机制要求XML Schema命名空间需要与Handler建立映射关系。

该关系配置在相对于classpath下的/META-INF/spring.handlers中。

如上图所示 ContextNamespaceHandler对应context:... 分析的入口。

找核心方法

浏览ContextNamespaceHandler

在parse中有一个很重要的注释

// Actually scan for bean definitions and register them.

ClassPathBeanDefinitionScanner scanner = configureScanner(parserContext, element);

大意是:ClassPathBeanDefinitionScanner#doScan是扫描BeanDefinition并注册的实现 。

ClassPathBeanDefinitionScanner 的源码如下:

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) {//findCandidateComponents 读资源装换为BeanDefinitionSet<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) {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;
}

上边的代码,从方法名,猜测:

findCandidateComponents:从classPath扫描组件,并转换为备选BeanDefinition,也就是要做的解析@Component的核心方法。

概要分析

findCandidateComponents在其父类ClassPathScanningCandidateComponentProvider 中。

public class ClassPathScanningCandidateComponentProvider implements EnvironmentCapable, ResourceLoaderAware {
//省略其他代码
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 (IOException ex) {//省略部分代码 }return candidates;
}
}

findCandidateComponents大体思路如下:

  • String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX resolveBasePackage(basePackage) + '/' + this.resourcePattern; 将package转化为ClassLoader类资源搜索路径packageSearchPath,例如:com.wl.spring.boot转化为classpath*:com/wl/spring/boot/**/*.class

  • Resource[] resources = getResourcePatternResolver().getResources(packageSearchPath); 加载搜素路径下的资源。

  • isCandidateComponent 判断是否是备选组件

  • candidates.add(sbd); 添加到返回结果的list

ClassPathScanningCandidateComponentProvider#isCandidateComponent其源码如下:

protected boolean isCandidateComponent(MetadataReader metadataReader) throws IOException {//省略部分代码for (TypeFilter tf : this.includeFilters) {if (tf.match(metadataReader, getMetadataReaderFactory())) {return isConditionMatch(metadataReader);}}return false;
}

includeFilters由registerDefaultFilters()设置初始值,有@Component,没有@Service啊?

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.}
}

Spring如何处理@Service的注解的呢????

2.查文档找思路

查阅官方文档,下面这话:

https://docs.spring.io/spring/docs/5.0.17.RELEASE/spring-framework-reference/core.html#beans-meta-annotations

@Component is a generic stereotype for any Spring-managed component. @Repository, @Service, and @Controller are specializations of @Component

大意如下:

@Component是任何Spring管理的组件的通用原型。@Repository、@Service和@Controller是派生自@Component。

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
// @Service 派生自@Component
@Component
public @interface Service {/*** The value may indicate a suggestion for a logical component name,* to be turned into a Spring bean in case of an autodetected component.* @return the suggested component name, if any (or empty String otherwise)*/@AliasFor(annotation = Component.class)String value() default "";}

@Component是@Service的元注解,Spring 大概率,在读取@Service,也读取了它的元注解,并将@Service作为@Component处理。

3. 探寻@Component派生性流程

回顾ClassPathScanningCandidateComponentProvider 中的关键的代码片段如下:

private Set<BeanDefinition> scanCandidateComponents(String basePackage) {//省略其他代码MetadataReader metadataReader   =getMetadataReaderFactory().getMetadataReader(resource);  if(isCandidateComponent(metadataReader)){//....}
}
public final MetadataReaderFactory getMetadataReaderFactory() {if (this.metadataReaderFactory == null) {this.metadataReaderFactory = new CachingMetadataReaderFactory();}return this.metadataReaderFactory;
}

1. 确定metadataReader

CachingMetadataReaderFactory继承自 SimpleMetadataReaderFactory,就是对SimpleMetadataReaderFactory加了一层缓存。

其内部的SimpleMetadataReaderFactory#getMetadataReader 为:

public class SimpleMetadataReaderFactory implements MetadataReaderFactory{@Overridepublic MetadataReader getMetadataReader(Resource resource) throws IOException {return new SimpleMetadataReader(resource, this.resourceLoader.getClassLoader());}
}

这里可以看出

MetadataReader metadataReader =new SimpleMetadataReader(...);

2.查看match方法找重点方法

AnnotationTypeFilter#matchself方法如下:

@Override
protected boolean matchSelf(MetadataReader metadataReader) {AnnotationMetadata metadata = metadataReader.getAnnotationMetadata();return metadata.hasAnnotation(this.annotationType.getName()) ||(this.considerMetaAnnotations && metadata.hasMetaAnnotation(this.annotationType.getName()));
}

是metadata.hasMetaAnnotation法,从名称看是处理元注解,我们重点关注

逐步分析

找metadata.hasMetaAnnotation

metadata=metadataReader.getAnnotationMetadata();

metadataReader =new SimpleMetadataReader(...)

metadata= new SimpleMetadataReader#getAnnotationMetadata()

//SimpleMetadataReader 的构造方法
SimpleMetadataReader(Resource resource, @Nullable ClassLoader classLoader) throws IOException {InputStream is = new BufferedInputStream(resource.getInputStream());ClassReader classReader;try {classReader = new ClassReader(is);}catch (IllegalArgumentException ex) {throw new NestedIOException("ASM ClassReader failed to parse class file - " +"probably due to a new Java class file version that isn't supported yet: " + resource, ex);}finally {is.close();}AnnotationMetadataReadingVisitor visitor =new AnnotationMetadataReadingVisitor(classLoader);classReader.accept(visitor, ClassReader.SKIP_DEBUG);this.annotationMetadata = visitor;// (since AnnotationMetadataReadingVisitor extends ClassMetadataReadingVisitor)this.classMetadata = visitor;this.resource = resource;
}

metadata=new SimpleMetadataReader(...).getAnnotationMetadata()= new AnnotationMetadataReadingVisitor(。。)

也就是说

metadata.hasMetaAnnotation=AnnotationMetadataReadingVisitor#hasMetaAnnotation

其方法如下:

public class AnnotationMetadataReadingVisitor{// 省略部分代码
@Override
public boolean hasMetaAnnotation(String metaAnnotationType) {Collection<Set<String>> allMetaTypes = this.metaAnnotationMap.values();for (Set<String> metaTypes : allMetaTypes) {if (metaTypes.contains(metaAnnotationType)) {return true;}}return false;
}
}

逻辑很简单,就是判断该注解的元注解在,在不在metaAnnotationMap中,如果在就返回true。

这里面核心就是metaAnnotationMap,搜索AnnotationMetadataReadingVisitor类,没有发现赋值的地方??!。

查找metaAnnotationMap赋值

回到SimpleMetadataReader 的方法,

//这个accept方法,很可疑,在赋值之前执行
SimpleMetadataReader(Resource resource, @Nullable ClassLoader classLoader) throws IOException {
//省略其他代码
AnnotationMetadataReadingVisitor visitor = new AnnotationMetadataReadingVisitor(classLoader);
classReader.accept(visitor, ClassReader.SKIP_DEBUG);this.annotationMetadata = visitor;}

发现一个可疑的语句:classReader.accept。

查看accept方法

public class ClassReader {//省略其他代码
public void accept(..省略代码){//省略其他代码readElementValues(classVisitor.visitAnnotation(annotationDescriptor, /* visible = */ true),currentAnnotationOffset,true,charBuffer);
}
}

查看readElementValues方法

public class ClassReader{//省略其他代码
private int readElementValues(final AnnotationVisitor annotationVisitor,final int annotationOffset,final boolean named,final char[] charBuffer) {int currentOffset = annotationOffset;// Read the num_element_value_pairs field (or num_values field for an array_value).int numElementValuePairs = readUnsignedShort(currentOffset);currentOffset += 2;if (named) {// Parse the element_value_pairs array.while (numElementValuePairs-- > 0) {String elementName = readUTF8(currentOffset, charBuffer);currentOffset =readElementValue(annotationVisitor, currentOffset + 2, elementName, charBuffer);}} else {// Parse the array_value array.while (numElementValuePairs-- > 0) {currentOffset =readElementValue(annotationVisitor, currentOffset, /* named = */ null, charBuffer);}}if (annotationVisitor != null) {annotationVisitor.visitEnd();}return currentOffset;
}
}

这里面的核心就是  annotationVisitor.visitEnd();

确定annotationVisitor

这里的annotationVisitor=AnnotationMetadataReadingVisitor#visitAnnotation

源码如下,注意这里传递了metaAnnotationMap!!

public class AnnotationMetadataReadingVisitor{
@Override
public AnnotationVisitor visitAnnotation(String desc, boolean visible) {String className = Type.getType(desc).getClassName();this.annotationSet.add(className);return new AnnotationAttributesReadingVisitor(className, this.attributesMap,this.metaAnnotationMap, this.classLoader);
}
}

annotationVisitor=AnnotationAttributesReadingVisitor

查阅annotationVisitor.visitEnd()

annotationVisitor=AnnotationAttributesReadingVisitor#visitEnd()

public class AnnotationAttributesReadingVisitor{
@Override
public void visitEnd() {super.visitEnd();Class<? extends Annotation> annotationClass = this.attributes.annotationType();if (annotationClass != null) {List<AnnotationAttributes> attributeList = this.attributesMap.get(this.annotationType);if (attributeList == null) {this.attributesMap.add(this.annotationType, this.attributes);}else {attributeList.add(0, this.attributes);}if (!AnnotationUtils.isInJavaLangAnnotationPackage(annotationClass.getName())) {try {Annotation[] metaAnnotations = annotationClass.getAnnotations();if (!ObjectUtils.isEmpty(metaAnnotations)) {Set<Annotation> visited = new LinkedHashSet<>();for (Annotation metaAnnotation : metaAnnotations) {recursivelyCollectMetaAnnotations(visited, metaAnnotation);}if (!visited.isEmpty()) {Set<String> metaAnnotationTypeNames = new LinkedHashSet<>(visited.size());for (Annotation ann : visited) {metaAnnotationTypeNames.add(ann.annotationType().getName());}this.metaAnnotationMap.put(annotationClass.getName(), metaAnnotationTypeNames);}}}catch (Throwable ex) {if (logger.isDebugEnabled()) {logger.debug("Failed to introspect meta-annotations on " + annotationClass + ": " + ex);}}}}
}
}

内部方法recursivelyCollectMetaAnnotations 递归的读取注解,与注解的元注解(读@Service,再读元注解@Component),并设置到metaAnnotationMap,也就是AnnotationMetadataReadingVisitor 中的metaAnnotationMap中。

总结

大致如下:

ClassPathScanningCandidateComponentProvider#findCandidateComponents

1.将package转化为ClassLoader类资源搜索路径packageSearchPath

2.加载搜素路径下的资源。

3.isCandidateComponent 判断是否是备选组件。

内部调用的TypeFilter的match方法:

  • AnnotationTypeFilter#matchself中metadata.hasMetaAnnotation处理元注解

  • metadata.hasMetaAnnotation=AnnotationMetadataReadingVisitor#hasMetaAnnotation

就是判断当前注解的元注解在不在metaAnnotationMap中。

AnnotationAttributesReadingVisitor#visitEnd()内部方法recursivelyCollectMetaAnnotations 递归的读取注解,与注解的元注解(读@Service,再读元注解@Component),并设置到metaAnnotationMap

4.添加到返回结果的list

快手二面:@Component,@Service等注解是如何被解析的?相关推荐

  1. @bean注解和@component注解的区别_通过源码查看 @Component,@Service 等注解是如何被解析的...

    点击上方"匠心零度",选择"设为星标" 做积极的人,而不是积极废人 来源:my.oschina.net/floor/blog/4325651 前言 1.@Com ...

  2. @Component,@Service等注解是如何被解析的?

    来源:my.oschina.net/floor/blog/4325651 前言 @Component和@Service都是工作中常用的注解,Spring如何解析? 1.@Component解析流程 找 ...

  3. @service注解_Spring 中 @Component、@Service 等注解如何被解析的

    作者:温安适原文:https://juejin.im/post/6844904199688306702#heading-5 前言 @Component和@Service都是工作中常用的注解,Sprin ...

  4. Spring 中 @Component、@Service 等注解如何被解析?

    点击上方蓝色"程序猿DD",选择"设为星标" 回复"资源"获取独家整理的学习资料! 来源 | http://8rr.co/EjqL 前言 @ ...

  5. @service注解_Spring 中 @Component、@Service 等注解如何被解析?

    前言 @Component和@Service都是工作中常用的注解,Spring如何解析? 1.@Component解析流程 找入口 Spring Framework2.0开始,引入可扩展的XML编程机 ...

  6. 自动化测试(二) 单元测试junit的Test注解突然不能使用原因以及解决方案

    自动化测试(二) 单元测试junit的Test注解突然不能使用原因以及解决方案 参考文章: (1)自动化测试(二) 单元测试junit的Test注解突然不能使用原因以及解决方案 (2)https:// ...

  7. 二、RISC-V SoC内核注解——译码 代码讲解

    tinyriscv这个SoC工程的内核cpu部分,采用经典的三级流水线结构进行设计,即大家所熟知的:取值->译码->执行三级流水线. 另外,在最后一个章节中会上传额外添加详细注释的工程代码 ...

  8. java---编写一个方法,返回一个int型的二维数组,数组中的元素通过解析字符串参数获得。

    题目: 编写一个方法,返回一个int型的二维数组,数组中的元素通过解析字符串参数获得,字符串如下"1,2:3,4,5:6,7"对应的数组为: d[0][0]=1 d[0][1]=2 ...

  9. YOLO系列 --- YOLOV7算法(二):YOLO V7算法detect.py代码解析

    YOLO系列 - YOLOV7算法(二):YOLO V7算法detect.py代码解析 parser = argparse.ArgumentParser()parser.add_argument('- ...

  10. Oracle Net Service:监听器与服务名解析方法

    Oracle Net Service:监听器与服务名解析方法 什么是Oracle Net服务 Oracle Net服务配置 Oracle Net监听器 静态注册 动态注册 Oracle Net服务名解 ...

最新文章

  1. PTA基础编程题目集-7-15 计算圆周率
  2. python代码写完怎么运行-教你如何编写、保存与运行 Python 程序
  3. 发布开源库的踩坑经历:jitpack.io
  4. 方案接口服务器问题记录
  5. linux安装完怎么分区,Linux系统安装模式下硬盘分区怎么做?
  6. JAVA通信编程(三)——TCP通讯
  7. sshpass远程执行脚本_终端-Linux命令之非交互SSH密码验证-Sshpass
  8. 点餐小程序源码_微信小程序餐饮点餐商城前端模版
  9. Lasso回归算法: 坐标轴下降法与最小角回归法小结
  10. web端访问远程桌面
  11. php 深度验证18位身份证是否正确,并获取地址,出生日期(时间戳),性别。
  12. mac 中 caps lock和control键交换,以及alt键替换option方法
  13. 和99.9%的人的认知完全相反的富人思维
  14. Orz 终于有了自己的博客地址
  15. 嵌入式主板广泛的解决方案
  16. 网络(十三)之ACL的高级应用
  17. mac hdmi 不能调整音量_有史以来最贵的苹果Mac笔记本,PowerBook 5300ce(1995)体验...
  18. 浅谈一个完整网站配色方案设计大法2实例篇
  19. 纳税服务系统【系统、子系统首页】
  20. python manage.py migrate和 migrate --fake和migrate --fake-initial的区别

热门文章

  1. 带有分页的列表的跳转后,返回时怎么实现保留分页的页数等信息
  2. 移动磁盘哪种格式更好
  3. Unity 2018.3.8 f1 个人版的Standard Assets在哪里下载?
  4. 发力多人在线游戏!PS5有望2020年E3展会亮相!
  5. ReactNative集成百度语音合成
  6. App架构设计经验谈:服务端接口的设计
  7. 调用百度地图 API 移动地图时 maker 始终在地图中间 并根据maker 经纬度 返回地址...
  8. JDBC操作数据库,第一:jsp插入mysql数据库,坎坷摸索分享
  9. ARCGIS 二次开发可以参考的资源(长期更新)
  10. 点滴积累【JS】---JQuery实现条形统计图,适用于选择题等统计