Component注解的派生性原理


1:模式注解

Stereotype Annotation俗稱為模式注解。Spring核心部分提供了幾種內建的模式注解,如@Component,@Repository,@Service,@Controller,@Configuration等。這些注解均派生於@Component

由於Java語言規定,Annotation不允許繼承,沒有類派生子類的特性,因此Spring采用元標注的方式實現注解之間的派生

2:@Component派生性

@Component注解作為Spring容器托管的通用模式組件,任何被@Component標注的組件均為組件掃描的候選對象。

任何論證過程離不開所處的環境,需要開發人員具備一定工程意識,包括軟件版本,特性范圍,兼容情況等。因此,論證過程從最低版本開始推導,逐步證明不同版本得提升和差異。

3:@Component注解派生性原理

ClassPathBeanDefinitionScanner#doScan(String... basePackages)調用時,它利用basePackages參數迭代執行的findCandidateComponents(String basePackage),每次執行結果都生成候選的BeanDefinition集合,即candidates變量。

 
        
public class ClassPathBeanDefinitionScanner extends ClassPathScanningCandidateComponentProvider{
    ...
 protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
        Assert.notEmpty(basePackages, "At least one base package must be specified");
        //獲取候選的BeanDefinition集合
        Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<BeanDefinitionHolder>();
        for (String basePackage : basePackages) {
            Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
            ...
        }
        return beanDefinitions;
    }   
    ...
}

而findCandidateComponents(String basePackage)從父類ClassPathScanningCandidateComponentProvider

中繼承。

 
        
public class ClassPathScanningCandidateComponentProvider implements EnvironmentCapable, ResourceLoaderAware {
    ...
 public Set<BeanDefinition> findCandidateComponents(String basePackage) {
        Set<BeanDefinition> candidates = new LinkedHashSet<BeanDefinition>();
        try {
            //獲取查詢的package,並處理占位符情況${...},轉換為ClassLoader資源(.class)搜索路徑
            String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
                    resolveBasePackage(basePackage) + '/' + this.resourcePattern;
            Resource[] resources = this.resourcePatternResolver.getResources(packageSearchPath);
            ...
            //resource迭代執行,當資源可讀取時,獲取該資源的MetadataReader對象
            for (Resource resource : resources) {
                ...
                if (resource.isReadable()) {
                    try {
                        //包含了類和注解元信息讀取方法
                        MetadataReader metadataReader = this.metadataReaderFactory.getMetadataReader(resource);
                        //判斷資源是否為候選的組件,通過excludeFilters和includeFilters進行判斷
                        if (isCandidateComponent(metadataReader)) {
                            //基於ASM,支持AnnotatedBeanDefinition接口
                            ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);
                            sbd.setResource(resource);
                            sbd.setSource(resource);
                            //判斷BeanDefinition是否候選組件
                            if (isCandidateComponent(sbd)) {
                                ...
                                candidates.add(sbd);
                            }
                            ...
            }
        }
        ...
        return candidates;
    }   
    ...
        
    
    /**
     * Determine whether the given class does not match any exclude filter
     * and does match at least one include filter.
     * @param metadataReader the ASM ClassReader for the class
     * @return whether the class qualifies as a candidate component
     */
    protected boolean isCandidateComponent(MetadataReader metadataReader) throws IOException{
        for (TypeFilter tf : this.excludeFilters) {
            if (tf.match(metadataReader, this.metadataReaderFactory)) {
                return false;
            }
        }
        for (TypeFilter tf : this.includeFilters) {
            if (tf.match(metadataReader, this.metadataReaderFactory)) {
                return isConditionMatch(metadataReader);
            }
        }
        return false;
    }
                
                
        /**
     * Determine whether the given bean definition qualifies as candidate.
     * <p>The default implementation checks whether the class is not an interface
     * and not dependent on an enclosing class.
     * <p>Can be overridden in subclasses.
     * @param beanDefinition the bean definition to check
     * @return whether the bean definition qualifies as a candidate component
     */
    protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
        AnnotationMetadata metadata = beanDefinition.getMetadata();
        return (metadata.isIndependent() && (metadata.isConcrete() ||
                (metadata.isAbstract() && metadata.hasAnnotatedMethods(Lookup.class.getName()))));
    }
                
                
                
    /**
     * Register the default filter for {@link Component @Component}.
     * <p>This will implicitly register all annotations that have the
     * {@link Component @Component} meta-annotation including the
     * {@link Repository @Repository}, {@link Service @Service}, and
     * {@link Controller @Controller} stereotype annotations.
     * <p>Also supports Java EE 6's {@link javax.annotation.ManagedBean} and
     * JSR-330's {@link javax.inject.Named} annotations, if available.
     *
     */
    @SuppressWarnings("unchecked")
    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));
            ...
        }
        try {
            this.includeFilters.add(new AnnotationTypeFilter(
    ((Class<? extends Annotation>) ClassUtils.forName("javax.inject.Named", cl)), false));
    ...
    }
    }
}

默認情況下,ClassPathScanningCandidateComponentProvider構造參數useDefaultFilters為true,並且顯示傳遞給父類構造參數。該方法給屬性includeFilters增添了@Component類型AnnotationTypeFilter的TypeFilter。

ClassPathBeanDefinitionScanner默認過濾器引入標注@Component,@Repository,@Service或者@Controller等類。同理,它也能夠標注所有@Component的"派生"注解。

@Component注解只包含一個value屬性定義,所以其“派生”的注解也只能包含一個vlaue屬性定義。

Dubbo實現@Service注解掃描實例:

ClassPathBeanDefinitionScanner允許自定義類型過濾規則。因此,Dubbo的@Service沒有標注@Component情況下,通過scanner.addIncludeFilter(new AnnotationTypeFilter(Service.class))方式達到識別@Service標注類情況。但是沒有使用@Component注解的派生性。

Mybatis實現@Mapper注解掃描實例:

public class ClassPathMapperScanner extends ClassPathBeanDefinitionScanner{
    
    ...
        
  public ClassPathMapperScanner(BeanDefinitionRegistry registry) {
    super(registry, false);
  }
    
   /**
   * Configures parent scanner to search for the right interfaces. It can search
   * for all interfaces or just for those that extends a markerInterface or/and
   * those annotated with the annotationClass
   */
  public void registerFilters() {
    boolean acceptAllInterfaces = true;
​
    // if specified, use the given annotation and / or marker interface
    if (this.annotationClass != null) {
      addIncludeFilter(new AnnotationTypeFilter(this.annotationClass));
      acceptAllInterfaces = false;
    }
​
    // override AssignableTypeFilter to ignore matches on the actual marker interface
    if (this.markerInterface != null) {
      addIncludeFilter(new AssignableTypeFilter(this.markerInterface) {
        @Override
        protected boolean matchClassName(String className) {
          return false;
        }
      });
      acceptAllInterfaces = false;
    }
​
    if (acceptAllInterfaces) {
      // default include filter that accepts all classes
      addIncludeFilter(new TypeFilter() {
        @Override
        public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
          return true;
        }
      });
    }
​
    // exclude package-info.java
    addExcludeFilter(new TypeFilter() {
      @Override
      public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
        String className = metadataReader.getClassMetadata().getClassName();
        return className.endsWith("package-info");
      }
    });
  }
​
  /**
   * {@inheritDoc}
   */
  @Override
  protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
    return beanDefinition.getMetadata().isInterface() && beanDefinition.getMetadata().isIndependent();
  }
    
    
  private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
    GenericBeanDefinition definition;
    for (BeanDefinitionHolder holder : beanDefinitions) {
      definition = (GenericBeanDefinition) holder.getBeanDefinition();
     
       ...
​
      //復雜對象構建考慮使用FactoryBean接口           
      // the mapper interface is the original class of the bean
      // but, the actual class of the bean is MapperFactoryBean
     //添加泛型參數         
           definition.getConstructorArgumentValues().addGenericArgumentValue(definition.getBeanClassName());
     // issue #59
      definition.setBeanClass(this.mapperFactoryBean.getClass());
​
      definition.getPropertyValues().add("addToConfig", this.addToConfig);
      ...
    }
  }
    
  ...
}

4:思考擴展

思考1:利用ClassPathBeanDefinitionScanner類配合includeFiltersexcludeFilters定制化批量注冊Bean到Spring容器中。常常可以通過注解方式來包含或者排除候選類。

TypeFilter常用實現

  • AnnotationTypeFilter:注解類型過濾器

  • AssignableTypeFilter:確定此對象表示的類或者接口是否為給定類或者接口相同。

  • RegexPatternTypeFilter:判斷給定的類名是否符合指定正則表達式。

思考2:復雜對象構建考慮使用FactoryBean實現類。

思考3:如果是讀取類和注解信息可以考慮基於ASM或者反射,使用方式往下可以獲取。當獲取已加載的類信息可以考慮反射(反射大前提是被反射的Class被ClassLoader加載),ASM用於不需要將類路徑package下的Class全部加載,Spring應用指定Java package掃描Spring模式注解時,利用的就是基於ASM方式獲取類或者注解信息。基於ASM獲取會獲得更大性能。

思考4:資源讀取考慮使用ResourcePatternResolver,這個對象的獲取可以通過Spring提供的工具類

ResourcePatternUtils.getResourcePatternResolver(resourceLoader)。在使用的時候,考慮處理

占位符${...}的情況,注意資源是否可讀。

5:多層次@Component派生性

(1):具體發展過程不再細說,詳解請看SpringBoot編程思想這本書。其多層次@Component注解派生性構建在Spring4.x。其核心處理類為AnnotationMetadataReadingVisitor,其采用遞歸的方式查找元注解

(2):Spring中,MetadataReader接口唯一實現非公開類SimpleMetadataReader。可以通過SimpleMetadataReaderFactory(ASM字節碼操作)CachingMetadataReaderFactory獲取。

其中在SimpleMetadataReader實現上看,ClassMetadataReadingVisitorAnnotationMetadataReadingVisitor分別是ClassMetadattaAnnotationMetadata實現類。

由於ClassPathBeanDefinitionScanner在尋找候選的BeanDefinition過程中,將指定basePackage參數下

的*.class資源進行元信息解析,也就是ClassMetadataAnnotationMetadata對象。

AnnotationMetadataReadingVisitor實現上使用了AnnotationAttributesReadingVisitor,該類主要實現方法是visitEnd()Spring2.5實現未采用層次遞歸獲取Annotation[],所以僅支持單層次的@Component派生。Spring3.x實現僅兩層@Component派生。Spring4.x開始采用遞歸方式查找元注解。相關實現在Spring為公開類AnnotationAttributesReadingVisitor。

final class AnnotationAttributesReadingVisitor extends RecursiveAnnotationAttributesVisitor {

    private final MultiValueMap<String, AnnotationAttributes> attributesMap;

    private final Map<String, Set<String>> metaAnnotationMap;


    ...


    @Override
    public void visitEnd() {
        super.visitEnd();

        Class<?> 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);
            }
            Set<Annotation> visited = new LinkedHashSet<Annotation>();
            Annotation[] metaAnnotations = AnnotationUtils.getAnnotations(annotationClass);
            if (!ObjectUtils.isEmpty(metaAnnotations)) {
                for (Annotation metaAnnotation : metaAnnotations) {
                    if (!AnnotationUtils.isInJavaLangAnnotationPackage(metaAnnotation)) {
                        recursivelyCollectMetaAnnotations(visited, metaAnnotation);
                    }
                }
            }
            if (this.metaAnnotationMap != null) {
                Set<String> metaAnnotationTypeNames = new LinkedHashSet<String>(visited.size());
                for (Annotation ann : visited) {
                    metaAnnotationTypeNames.add(ann.annotationType().getName());
                }
                this.metaAnnotationMap.put(annotationClass.getName(), metaAnnotationTypeNames);
            }
        }
    }

    private void recursivelyCollectMetaAnnotations(Set<Annotation> visited, Annotation annotation) {
        Class<? extends Annotation> annotationType = annotation.annotationType();
        String annotationName = annotationType.getName();
        if (!AnnotationUtils.isInJavaLangAnnotationPackage(annotationName) && visited.add(annotation)) {
            try {
                // Only do attribute scanning for public annotations; we'd run into
                // IllegalAccessExceptions otherwise, and we don't want to mess with
                // accessibility in a SecurityManager environment.
                if (Modifier.isPublic(annotationType.getModifiers())) {
                    this.attributesMap.add(annotationName,
                            AnnotationUtils.getAnnotationAttributes(annotation, false, true));
                }
          //實現遞歸查找元注解
for (Annotation metaMetaAnnotation : annotationType.getAnnotations()) { recursivelyCollectMetaAnnotations(visited, metaMetaAnnotation); } } catch (Throwable ex) { if (logger.isDebugEnabled()) { logger.debug("Failed to introspect meta-annotations on [" + annotation + "]: " + ex); } } } } }

(3):思考擴展

  • 考慮使用ASM的方式讀取類或者注解相關信息。(不需要全部將指定路徑下的類加載)

    • MetadataReaderFactory:獲取MetadataReader工廠

      • SimpleMetadataReaderFactory:簡單獲取MetadataReader工廠實現

        • ClassReader:基於ASM讀取類相關信息,公開類,不建議單獨使用。

        • AnnotationMetadataReadingVisitor:基於ASM讀取注解元數據相關信息,不建議單獨使用。

        • MethodMetadataReadingVisitor:基於ASM讀取方法相關信息,不建議單獨使用。

      • CachingMetadataReaderFactory:繼承SimpleMetadataReaderFactory,增加緩存MetadataReader資源功能。

    • MetadataReader:獲取訪問類和注解相關信息。通過MetadataReaderFactory獲取。

      • Resource getResource():獲取類文件資源引用

      • ClassMetadata getClassMetadata():讀取基礎類的基本元數據

      • AnnotationMetadata getAnnotationMetadata():讀取底層類完整注解元數據,包含注解方法的注解元數據。

  • 考慮使用反射的方式讀取類或者注解相關信息(比較費時而且該類必須被ClassLoader加載)

    • StandardClassMetadata:基於反射讀取類元數據,可建議單獨使用。

    • StandardAnnotationMetadata:基於反射讀取注解元數據,可建議單獨使用

    • StandardMethodMetadata:基於反射讀取方法元數據,可建議單獨使用

  • 考慮使用Spring內部支持的有用工具類,都是來自於spring-core包中。多使用spring內建API,學習他們的長處。

    • ClassUtils:類工具類

    • CollectionUtils:集合工具類

    • NumberUtils:Number工具類

    • MimeTypeUtils:媒體類型工具類

    • IdGenerator:Id生成器

    • StringUtils:字符串工具類

    • ResourceUtils:資源工具類

    • ReflectionUtils:反射工具類

    • MethodIntrospector:方法自省工具類(EventListenerMethodProcessor#processBean中有使用)

    • PatternMatchUtils:正則資源匹配工具類

    • ObjectUtils:對象工具類

3:組合注解

組合注解指某個注解"元標注"一個或多個其他注解,其目的在於將這些關聯的注解行為組合成單個自定義注解。

Spring Framework的類加載通過ASM實現,如ClassReader。相對於ClassLoader體系,Spring ASM更為底層,讀取的是類資源,直接操作其中的字節碼,獲取相關元信息。如MetadataReader接口

/**
 * Simple facade for accessing class metadata,
 * as read by an ASM {@link org.springframework.asm.ClassReader}.
 *
 * @author Juergen Hoeller
 * @since 2.5
 */
public interface MetadataReader {
​
    /**
     * Return the resource reference for the class file.
     */
    Resource getResource();
​
    /**
     * Read basic class metadata for the underlying class.
     */
    ClassMetadata getClassMetadata();
​
    /**
     * Read full annotation metadata for the underlying class,
     * including metadata for annotated methods.
     */
    AnnotationMetadata getAnnotationMetadata();
​
}

AnnotationMetadataReadingVisitor同時實現了ClassMetadata及AnnotationMetadata。因此,元注解的實現集中到AnnotationMetadataReadingVisitorAnnotationAttributesReadingVisitor之中。

MetadataReader對象通過MetadataReaderFactory對象獲取。

 
        
/**
 * Factory interface for {@link MetadataReader} instances.
 * Allows for caching a MetadataReader per original resource.
 *
 * @author Juergen Hoeller
 * @since 2.5
 * @see SimpleMetadataReaderFactory
 * @see CachingMetadataReaderFactory
 */
public interface MetadataReaderFactory {
​
    /**
     * Obtain a MetadataReader for the given class name.
     * @param className the class name (to be resolved to a ".class" file)
     * @return a holder for the ClassReader instance (never {@code null})
     * @throws IOException in case of I/O failure
     */
    MetadataReader getMetadataReader(String className) throws IOException;
​
    /**
     * Obtain a MetadataReader for the given resource.
     * @param resource the resource (pointing to a ".class" file)
     * @return a holder for the ClassReader instance (never {@code null})
     * @throws IOException in case of I/O failure
     */
    MetadataReader getMetadataReader(Resource resource) throws IOException;
​
}
​
 

具體某個注解的元注解信息則通過getMetaAnnotationTypes(String)方法查詢。

AnnotationMetadata實現AnnotationMetadataReadingVisitor(ASM實現)StandardAnnotationMetadata(反射)

  • 注解元信息抽象:AnnotationMetadata

    • AnnotationMetadataReadingVisitor

      • AnnotationAttributesReadingVisitor(遞歸查找元注解)

  • 類元信息抽象:ClassMetadata

  • 方法元信息抽象:MethodMetadata

  • 注解屬性抽象:AnnotationAttributes

  • 屬性環境抽象:Environment

  • 屬性文件抽象:PropertySource

  • 元信息讀取抽象:MetadataReader

    • 通過MetadataReaderFactory獲取

方法內省:MethodIntrospector

        Map<Method, EventListener> annotatedMethods = null;
            annotatedMethods = MethodIntrospector.selectMethods(targetType,
                        (MethodIntrospector.MetadataLookup<EventListener>) method ->
                                AnnotatedElementUtils.findMergedAnnotation(method, EventListener.class));

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM