2
头图

Foreword

@Component and @Service are common annotations in work, how does Spring parse them?

analysis process 1609bae398c844

find entrance

Since Spring Framework 2.0, an extensible XML programming mechanism has been introduced, which requires the XML Schema namespace to establish a mapping relationship with the Handler.

The relationship is configured in /META-INF/spring.handlers relative to the classpath.

As shown in the figure above, ContextNamespaceHandler corresponds to the entry of context:... analysis.

Find the core method

Browse ContextNamespaceHandler

There is a very important note in parse

// Actually scan for bean definitions and register them.

ClassPathBeanDefinitionScanner scanner = configureScanner(parserContext, element);

The general idea is: ClassPathBeanDefinitionScanner#doScan is the realization of scanning BeanDefinition and registration.

The source code of ClassPathBeanDefinitionScanner is as follows:

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

The code above, guessing from the method name:

findCandidateComponents: Scan components from classPath and convert them to candidate BeanDefinition, which is the core method of @Component parsing.

summary analysis

findCandidateComponents is in its parent class, 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;
}
}

The general idea of findCandidateComponents is as follows:

  • String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX resolveBasePackage(basePackage) + '/' + this.resourcePattern; converts package to ClassLoader resource search path packageSearchPath, for example: com.wl.spring.boot converted to classpath*:com/wl/spring/boot/**/*.class
  • Resource[] resources = getResourcePatternResolver().getResources(packageSearchPath); loads the resources under the search path.
  • isCandidateComponent Determine whether it is a candidate component
  • candidates.add(sbd); added to the list of returned results

The source code of ClassPathScanningCandidateComponentProvider#isCandidateComponent

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

The initial value of includeFilters is set by registerDefaultFilters(). There is @Component but no @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.
   }
}

How does Spring handle @Service annotations? ? ? ?

Second, check documents to find ideas

Check the official documentation, the following words:

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

The general idea is as follows:

@Component is a common prototype for any Spring managed component. @Repository, @Service, and @Controller are derived from @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 is the meta-annotation of @Service. Spring has a high probability of reading @Service and its meta-annotation, and processing @Service as @Component.

III. Exploring @Component Derivative Process

Recall the key code snippets in ClassPathScanningCandidateComponentProvider are as follows:

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. Determine the metadataReader

CachingMetadataReaderFactory inherits from SimpleMetadataReaderFactory, which adds a layer of caching to SimpleMetadataReaderFactory.

The internal SimpleMetadataReaderFactory#getMetadataReader is:

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

Can be seen here

MetadataReader metadataReader =new SimpleMetadataReader(...);

2. Look at the match method to find the key method

The AnnotationTypeFilter#matchself method is as follows:

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

It is the metadata.hasMetaAnnotation method. From the perspective of the name, it deals with meta-annotations. We focus on

Step by step analysis of

find 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(。。)

In other words

metadata.hasMetaAnnotation=AnnotationMetadataReadingVisitor#hasMetaAnnotation

The method is as follows:

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

The logic is very simple, that is, to determine whether the meta-annotation of the annotation is in, or not in the metaAnnotationMap, and if it is, it returns true.

The core of this is metaAnnotationMap. Searching for the AnnotationMetadataReadingVisitor class, there is no place to find the assignment? ? ! .

Find metaAnnotationMap assignment

Back to the SimpleMetadataReader method,

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

A suspicious statement was found: classReader.accept.

View accept method

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

View the readElementValues method

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

The core of this is annotationVisitor.visitEnd();

confirm annotationVisitor

Here annotationVisitor=AnnotationMetadataReadingVisitor#visitAnnotation

The source code is as follows, note that metaAnnotationMap is passed here! !

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

See 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);
            }
         }
      }
   }
}
}

The internal method recursivelyCollectMetaAnnotations reads the annotations recursively, and the meta annotations of the annotations (read @Service, then read the meta annotation @Component), and set them to metaAnnotationMap, which is the metaAnnotationMap in AnnotationMetadataReadingVisitor.

Summary

It is roughly as follows:

ClassPathScanningCandidateComponentProvider#findCandidateComponents

1. Convert package to ClassLoader resource search path packageSearchPath

2. Load the resources under the search path.

3.isCandidateComponent Determine whether it is a candidate component.

The match method of TypeFilter called internally:

  • AnnotationTypeFilter#matchself metadata.hasMetaAnnotation processing meta annotation
  • metadata.hasMetaAnnotation=AnnotationMetadataReadingVisitor#hasMetaAnnotation

It is to determine whether the meta-annotation of the current annotation is in the metaAnnotationMap.

AnnotationAttributesReadingVisitor#visitEnd() internal method recursivelyCollectMetaAnnotations reads annotations recursively, and the meta annotations of the annotations (read @Service, then read meta annotation @Component), and set to metaAnnotationMap

4. Add to the list of returned results

written at the end

Welcome everyone to pay attention to my public calm as the code ], a large number of Java-related articles, learning materials will be updated in it, and the sorted information will also be placed in it.

If you think the writing is good, just like it and add a follower! Pay attention, don’t get lost, keep updating! ! !


风平浪静如码
121 声望38 粉丝