面試官:註解@Component,@Service是如何被解析的?


來源: https://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   doScan (String... basePackages)  {
   Assert.notEmpty(basePackages, "At least one base package must be specified");
   Set  beanDefinitions =  new LinkedHashSet<>();
    for (String basePackage : basePackages) {
       //findCandidateComponents 讀資源裝換爲BeanDefinition
      Set  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 EnvironmentCapableResourceLoaderAware {
//省略其他代碼
public Set   findCandidateComponents (String basePackage)  {
   if (this.componentsIndex != null && indexSupportsIncludeFilters()) {
      return addCandidateComponentsFromIndex(this.componentsIndex, basePackage);
   }
   else {
      return scanCandidateComponents(basePackage);
   }
}
private Set   scanCandidateComponents (String basePackage)  {
   Set  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) 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) 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處理。公衆號搜索,[Java學習之道],回覆'福利',3T資料等你來拿!

3. 探尋@Component派生性流程

回顧ClassPathScanningCandidateComponentProvider 中的關鍵的代碼片段如下:

private Set   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方法找重點方法」

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 > allMetaTypes =  this.metaAnnotationMap.values();
    for (Set  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 annotationClass = this.attributes.annotationType();
   if (annotationClass != null) {
      List  attributeList =  this.attributesMap.get( this.annotationType);
       if (attributeList ==  null) {
          this.attributesMap.add( this.annotationType,  this.attributes);
      }
       else {
         attributeList.add( 0this.attributes);
      }
       if (!AnnotationUtils.isInJavaLangAnnotationPackage(annotationClass.getName())) {
          try {
            Annotation[] metaAnnotations = annotationClass.getAnnotations();
             if (!ObjectUtils.isEmpty(metaAnnotations)) {
               Set  visited =  new LinkedHashSet<>();
                for (Annotation metaAnnotation : metaAnnotations) {
                  recursivelyCollectMetaAnnotations(visited, metaAnnotation);
               }
                if (!visited.isEmpty()) {
                  Set  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

  1. 添加到返回結果的list

本文分享自微信公衆號 - 架構真經(gentoo666)。
如有侵權,請聯繫 [email protected] 刪除。
本文參與“OSC源創計劃”,歡迎正在閱讀的你也加入,一起分享。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章