写点什么

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

用户头像
Java小咖秀
关注
发布于: 2021 年 03 月 19 日
@Component,@Service等注解是如何被解析的?

公众号:Java小咖秀,网站:[javaxks.com](https://www.javaxks.com)


作者 :Jitwxs ,链接: https://jitwxs.cn/4135e0a9.html


前言

@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<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 的核心方法。推荐:Java 面试练题宝典


概要分析

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


String packageSearchPath = ResourcePatternResolver.CLASSPATHALLURL_PREFIX resolveBasePackage(basePackage) + '/' + this.resourcePattern; 将 package 转化为 ClassLoader 类资源搜索路径 packageSearchPath,例如:com.wl.spring.boot 转化为 classpath:com/wl/spring/boot/*/*.class


  • Resource[] resources = getResourcePatternResolver().getResources(packageSearchPath); 加载搜素路径下的资源。

  • isCandidateComponent 判断是否是备选组件

  • 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


@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 处理。


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 方法找重点方法



AnnotationTypeFilter#matchself 方法如下:

@Overrideprotected 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(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{    // 省略部分代码@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。


这里面核心就是 metaAnnotationMap,搜索 AnnotationMetadataReadingVisitor 类,没有发现赋值的地方??!。


推荐:Java 面试练题宝典


查找 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 numelementvaluepairs field (or numvalues field for an array_value).

int numElementValuePairs = readUnsignedShort(currentOffset);

currentOffset += 2;

if (named) {

// Parse the elementvaluepairs 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


用户头像

Java小咖秀

关注

公众号:Java小咖秀,专注Java相关领域。 2020.06.18 加入

公众号【Java小咖秀】,回复“面试”白嫖一份博主Java面试题。 个人网站:https://www.javaxks.com。

评论

发布
暂无评论
@Component,@Service等注解是如何被解析的?