京东一面:说说@Component,@Service等注解是如何被解析的?

沙海 2021年3月18日01:13:45杂谈 Java评论31字数 14919阅读49分43秒阅读模式
摘要

速读摘要

速读摘要文章源自JAVA秀-https://www.javaxiu.com/5173.html

点击上方"Java基基",选择"设为星标"做积极的人,而不是积极废人!在读取@Service,也读取了它的元注解,并将@Service作为@Component处理。逻辑很简单,就是判断该注解的元注解在,在不在metaAnnotationMap中,如果在就返回true。这里面核心就是metaAnnotationMap,搜索AnnotationMetadataReadingVisitor类,没有发现赋值的地方??!。文章源自JAVA秀-https://www.javaxiu.com/5173.html

原文约 5559 | 图片 9 | 建议阅读 12 分钟 | 评价反馈文章源自JAVA秀-https://www.javaxiu.com/5173.html

京东一面:说说@Component,@Service等注解是如何被解析的?

点击关注 ? Java基基 文章源自JAVA秀-https://www.javaxiu.com/5173.html

点击上方“Java基基”,选择“设为星标”文章源自JAVA秀-https://www.javaxiu.com/5173.html

做积极的人,而不是积极废人!文章源自JAVA秀-https://www.javaxiu.com/5173.html

文章源自JAVA秀-https://www.javaxiu.com/5173.html

源码精品专栏文章源自JAVA秀-https://www.javaxiu.com/5173.html

 文章源自JAVA秀-https://www.javaxiu.com/5173.html

文章源自JAVA秀-https://www.javaxiu.com/5173.html

来源:my.oschina.net/floor/blog/4325651文章源自JAVA秀-https://www.javaxiu.com/5173.html

京东一面:说说@Component,@Service等注解是如何被解析的?文章源自JAVA秀-https://www.javaxiu.com/5173.html

前言

@Component和@Service都是工作中常用的注解,Spring如何解析?文章源自JAVA秀-https://www.javaxiu.com/5173.html

1.@Component解析流程

找入口

Spring Framework2.0开始,引入可扩展的XML编程机制,该机制要求XML Schema命名空间需要与Handler建立映射关系。文章源自JAVA秀-https://www.javaxiu.com/5173.html

该关系配置在相对于classpath下的/META-INF/spring.handlers中。文章源自JAVA秀-https://www.javaxiu.com/5173.html

京东一面:说说@Component,@Service等注解是如何被解析的?文章源自JAVA秀-https://www.javaxiu.com/5173.html

如上图所示 ContextNamespaceHandler对应context:... 分析的入口。文章源自JAVA秀-https://www.javaxiu.com/5173.html

找核心方法

浏览ContextNamespaceHandler文章源自JAVA秀-https://www.javaxiu.com/5173.html

京东一面:说说@Component,@Service等注解是如何被解析的?文章源自JAVA秀-https://www.javaxiu.com/5173.html

在parse中有一个很重要的注释文章源自JAVA秀-https://www.javaxiu.com/5173.html

// Actually scan for bean definitions and register them.文章源自JAVA秀-https://www.javaxiu.com/5173.html

ClassPathBeanDefinitionScanner scanner = configureScanner(parserContext, element);文章源自JAVA秀-https://www.javaxiu.com/5173.html

大意是:ClassPathBeanDefinitionScanner#doScan是扫描BeanDefinition并注册的实现文章源自JAVA秀-https://www.javaxiu.com/5173.html

ClassPathBeanDefinitionScanner 的源码如下:文章源自JAVA秀-https://www.javaxiu.com/5173.html

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

上边的代码,从方法名,猜测:文章源自JAVA秀-https://www.javaxiu.com/5173.html

findCandidateComponents:从classPath扫描组件,并转换为备选BeanDefinition,也就是要做的解析@Component的核心方法。文章源自JAVA秀-https://www.javaxiu.com/5173.html

概要分析

findCandidateComponents在其父类ClassPathScanningCandidateComponentProvider 中。文章源自JAVA秀-https://www.javaxiu.com/5173.html

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大体思路如下:文章源自JAVA秀-https://www.javaxiu.com/5173.html

  1. String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX resolveBasePackage(basePackage) + '/' + this.resourcePattern;                            将package转化为ClassLoader类资源搜索路径packageSearchPath,例如:com.wl.spring.boot转化为classpath*:com/wl/spring/boot/**/*.class文章源自JAVA秀-https://www.javaxiu.com/5173.html

  2. Resource[] resources = getResourcePatternResolver().getResources(packageSearchPath); 加载搜素路径下的资源。文章源自JAVA秀-https://www.javaxiu.com/5173.html

  3. isCandidateComponent 判断是否是备选组件文章源自JAVA秀-https://www.javaxiu.com/5173.html

  4. candidates.add(sbd); 添加到返回结果的list文章源自JAVA秀-https://www.javaxiu.com/5173.html

ClassPathScanningCandidateComponentProvider#isCandidateComponent其源码如下:文章源自JAVA秀-https://www.javaxiu.com/5173.html

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啊?文章源自JAVA秀-https://www.javaxiu.com/5173.html

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的注解的呢????文章源自JAVA秀-https://www.javaxiu.com/5173.html

2.查文档找思路

查阅官方文档,下面这话:文章源自JAVA秀-https://www.javaxiu.com/5173.html

https://docs.spring.io/spring/docs/5.0.17.RELEASE/spring-framework-reference/core.html#beans-meta-annotations文章源自JAVA秀-https://www.javaxiu.com/5173.html

@Component is a generic stereotype for any Spring-managed component. @Repository, @Service, and @Controller are specializations of @Component文章源自JAVA秀-https://www.javaxiu.com/5173.html

大意如下:文章源自JAVA秀-https://www.javaxiu.com/5173.html

@Component是任何Spring管理的组件的通用原型。@Repository、@Service和@Controller是派生自@Component。文章源自JAVA秀-https://www.javaxiu.com/5173.html

@Target({ElementType.TYPE})@Retention(RetentionPolicy.RUNTIME)@Documented// @Service 派生自@Component@Componentpublic @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处理。文章源自JAVA秀-https://www.javaxiu.com/5173.html

3. 探寻@Component派生性流程

回顾ClassPathScanningCandidateComponentProvider 中的关键的代码片段如下:文章源自JAVA秀-https://www.javaxiu.com/5173.html

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加了一层缓存。文章源自JAVA秀-https://www.javaxiu.com/5173.html

其内部的SimpleMetadataReaderFactory#getMetadataReader 为:文章源自JAVA秀-https://www.javaxiu.com/5173.html

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

这里可以看出文章源自JAVA秀-https://www.javaxiu.com/5173.html

MetadataReader metadataReader =new SimpleMetadataReader(...);文章源自JAVA秀-https://www.javaxiu.com/5173.html

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

京东一面:说说@Component,@Service等注解是如何被解析的?文章源自JAVA秀-https://www.javaxiu.com/5173.html

AnnotationTypeFilter#matchself方法如下:文章源自JAVA秀-https://www.javaxiu.com/5173.html

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

是metadata.hasMetaAnnotation法,从名称看是处理元注解,我们重点关注文章源自JAVA秀-https://www.javaxiu.com/5173.html

逐步分析

找metadata.hasMetaAnnotation

metadata=metadataReader.getAnnotationMetadata();文章源自JAVA秀-https://www.javaxiu.com/5173.html

metadataReader =new SimpleMetadataReader(...)文章源自JAVA秀-https://www.javaxiu.com/5173.html

metadata= new SimpleMetadataReader#getAnnotationMetadata()文章源自JAVA秀-https://www.javaxiu.com/5173.html

//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(。。)文章源自JAVA秀-https://www.javaxiu.com/5173.html

也就是说文章源自JAVA秀-https://www.javaxiu.com/5173.html

metadata.hasMetaAnnotation=AnnotationMetadataReadingVisitor#hasMetaAnnotation文章源自JAVA秀-https://www.javaxiu.com/5173.html

其方法如下:文章源自JAVA秀-https://www.javaxiu.com/5173.html

public class AnnotationMetadataReadingVisitor{    // 省略部分代码@Overridepublic 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。文章源自JAVA秀-https://www.javaxiu.com/5173.html

这里面核心就是metaAnnotationMap,搜索AnnotationMetadataReadingVisitor类,没有发现赋值的地方??!。文章源自JAVA秀-https://www.javaxiu.com/5173.html

查找metaAnnotationMap赋值

回到SimpleMetadataReader 的方法,文章源自JAVA秀-https://www.javaxiu.com/5173.html

//这个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。文章源自JAVA秀-https://www.javaxiu.com/5173.html

查看accept方法文章源自JAVA秀-https://www.javaxiu.com/5173.html

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

查看readElementValues方法文章源自JAVA秀-https://www.javaxiu.com/5173.html

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();文章源自JAVA秀-https://www.javaxiu.com/5173.html

确定annotationVisitor

这里的annotationVisitor=AnnotationMetadataReadingVisitor#visitAnnotation文章源自JAVA秀-https://www.javaxiu.com/5173.html

源码如下,注意这里传递了metaAnnotationMap!!文章源自JAVA秀-https://www.javaxiu.com/5173.html

public class AnnotationMetadataReadingVisitor{@Overridepublic 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文章源自JAVA秀-https://www.javaxiu.com/5173.html

查阅annotationVisitor.visitEnd()

annotationVisitor=AnnotationAttributesReadingVisitor#visitEnd()文章源自JAVA秀-https://www.javaxiu.com/5173.html

public class AnnotationAttributesReadingVisitor{@Overridepublic 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中。文章源自JAVA秀-https://www.javaxiu.com/5173.html

总结

大致如下:文章源自JAVA秀-https://www.javaxiu.com/5173.html

ClassPathScanningCandidateComponentProvider#findCandidateComponents文章源自JAVA秀-https://www.javaxiu.com/5173.html

1、将package转化为ClassLoader类资源搜索路径packageSearchPath文章源自JAVA秀-https://www.javaxiu.com/5173.html

2、加载搜素路径下的资源。文章源自JAVA秀-https://www.javaxiu.com/5173.html

3、isCandidateComponent 判断是否是备选组件。文章源自JAVA秀-https://www.javaxiu.com/5173.html

内部调用的TypeFilter的match方法:文章源自JAVA秀-https://www.javaxiu.com/5173.html

AnnotationTypeFilter#matchself中metadata.hasMetaAnnotation处理元注解文章源自JAVA秀-https://www.javaxiu.com/5173.html

metadata.hasMetaAnnotation=AnnotationMetadataReadingVisitor#hasMetaAnnotation文章源自JAVA秀-https://www.javaxiu.com/5173.html

就是判断当前注解的元注解在不在metaAnnotationMap中。文章源自JAVA秀-https://www.javaxiu.com/5173.html

AnnotationAttributesReadingVisitor#visitEnd()内部方法recursivelyCollectMetaAnnotations 递归的读取注解,与注解的元注解(读@Service,再读元注解@Component),并设置到metaAnnotationMap文章源自JAVA秀-https://www.javaxiu.com/5173.html

4、添加到返回结果的list文章源自JAVA秀-https://www.javaxiu.com/5173.html

文章源自JAVA秀-https://www.javaxiu.com/5173.html

欢迎加入我的知识星球,一起探讨架构,交流源码。加入方式,长按下方二维码噢文章源自JAVA秀-https://www.javaxiu.com/5173.html

京东一面:说说@Component,@Service等注解是如何被解析的?文章源自JAVA秀-https://www.javaxiu.com/5173.html

已在知识星球更新源码解析如下:文章源自JAVA秀-https://www.javaxiu.com/5173.html

京东一面:说说@Component,@Service等注解是如何被解析的?文章源自JAVA秀-https://www.javaxiu.com/5173.html

京东一面:说说@Component,@Service等注解是如何被解析的?文章源自JAVA秀-https://www.javaxiu.com/5173.html

京东一面:说说@Component,@Service等注解是如何被解析的?文章源自JAVA秀-https://www.javaxiu.com/5173.html

京东一面:说说@Component,@Service等注解是如何被解析的?文章源自JAVA秀-https://www.javaxiu.com/5173.html

最近更新《芋道 SpringBoot 2.X 入门》系列,已经 20 余篇,覆盖了 MyBatis、Redis、MongoDB、ES、分库分表、读写分离、SpringMVC、Webflux、权限、WebSocket、Dubbo、RabbitMQ、RocketMQ、Kafka、性能测试等等内容。文章源自JAVA秀-https://www.javaxiu.com/5173.html

提供近 3W 行代码的 SpringBoot 示例,以及超 4W 行代码的电商微服务项目。文章源自JAVA秀-https://www.javaxiu.com/5173.html

获取方式:点“在看”,关注公众号并回复 666 领取,更多内容陆续奉上。文章源自JAVA秀-https://www.javaxiu.com/5173.html

文章源自JAVA秀-https://www.javaxiu.com/5173.html

文章有帮助的话,在看,转发吧。谢谢支持哟 (*^__^*)
文章源自JAVA秀-https://www.javaxiu.com/5173.html

阅读原文文章源自JAVA秀-https://www.javaxiu.com/5173.html

继续阅读
速蛙云 - 极致体验,强烈推荐!!!购买套餐就免费送各大视频网站会员!快速稳定、独家福利社、流媒体稳定解锁!速度快,全球上网、视频、游戏加速、独立IP均支持!基础套餐性价比很高!这里不多说,我一直正在使用,推荐购买:https://www.javaxiu.com/59919.html
weinxin
资源分享QQ群
本站是JAVA秀团队的技术分享社区, 会经常分享资源和教程; 分享的时代, 请别再沉默!
沙海
匿名

发表评论

匿名网友 填写信息

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定