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

Wesley13
• 阅读 444

这个系列分为5篇

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

2.@Enable驱动原理

3. @EnableAutoConfiguration处理逻辑

4. spring,springBoot事件

5. 仅需四步,写一个springboot starter

前言

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

1.@Component解析流程

找入口

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

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

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

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

找核心方法

浏览ContextNamespaceHandler 

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

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

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

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大体思路如下:

  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
  2. Resource[] resources = getResourcePatternResolver().getResources(packageSearchPath);  加载搜素路径下的资源。
  3. isCandidateComponent 判断是否是备选组件
  4. 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 {
    @Override
public MetadataReader getMetadataReader(Resource resource) throws IOException {
   return new SimpleMetadataReader(resource, this.resourceLoader.getClassLoader());
}
    }

这里可以看出

MetadataReader metadataReader =new SimpleMetadataReader(...);

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

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

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

点赞
收藏
评论区
推荐文章
blmius blmius
2年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
Stella981 Stella981
2年前
Android So动态加载 优雅实现与原理分析
背景:漫品Android客户端集成适配转换功能(基于目标识别(So库35M)和人脸识别库(5M)),导致apk体积50M左右,为优化客户端体验,决定实现So文件动态加载.!(https://oscimg.oschina.net/oscnet/00d1ff90e4b34869664fef59e3ec3fdd20b.png)点击上方“蓝字”关注我
Wesley13 Wesley13
2年前
@Enable驱动逻辑
这个系列分为5篇1\.@Component,@Service等注解是如何被解析的(https://my.oschina.net/floor/blog/4325651)2\.@Enable驱动原理(https://my.oschina.net/floor/blog/4333081)3\.@EnableAutoConfiguratio
Wesley13 Wesley13
2年前
00:Java简单了解
浅谈Java之概述Java是SUN(StanfordUniversityNetwork),斯坦福大学网络公司)1995年推出的一门高级编程语言。Java是一种面向Internet的编程语言。随着Java技术在web方面的不断成熟,已经成为Web应用程序的首选开发语言。Java是简单易学,完全面向对象,安全可靠,与平台无关的编程语言。
Wesley13 Wesley13
2年前
@EnableAutoConfiguration处理逻辑
这个系列分为5篇1\.@Component,@Service等注解是如何被解析的(http://my.oschina.net/u/3907912)2\.@Enable驱动原理(https://my.oschina.net/floor/blog/4333081)3\.@EnableAutoConfiguration处理逻辑(htt
Wesley13 Wesley13
2年前
35岁是技术人的天花板吗?
35岁是技术人的天花板吗?我非常不认同“35岁现象”,人类没有那么脆弱,人类的智力不会说是35岁之后就停止发展,更不是说35岁之后就没有机会了。马云35岁还在教书,任正非35岁还在工厂上班。为什么技术人员到35岁就应该退役了呢?所以35岁根本就不是一个问题,我今年已经37岁了,我发现我才刚刚找到自己的节奏,刚刚上路。
Easter79 Easter79
2年前
Spring常用注解
组件注解相关@Component@Controller(https://my.oschina.net/u/1774615),@RestController@Service(https://my.oschina.net/service)@Repository(https://my.oschina.n
Wesley13 Wesley13
2年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
Python进阶者 Python进阶者
3个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这