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

这个系列分为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中。

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

找核心方法

浏览ContextNamespaceHandler 

在parse中有一个很重要的注释

// Actually scan for bean definitions and register them.

ClassPathBeanDefinitionScanner scanner = configureScanner(parserContext, element);

大意是: ClassPathBeanDefinitionScanner#doScan是扫描BeanDefinition并注册的实现

ClassPathBeanDefinitionScanner 的源码如下:

1protected Set<BeanDefinitionHolder> doScan(String... basePackages) { 2 Assert.notEmpty(basePackages, "At least one base package must be specified"); 3 Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>(); 4 for (String basePackage : basePackages) { 5 //findCandidateComponents 读资源装换为BeanDefinition 6 Set<BeanDefinition> candidates = findCandidateComponents(basePackage); 7 for (BeanDefinition candidate : candidates) { 8 ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate); 9 candidate.setScope(scopeMetadata.getScopeName()); 10 String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry); 11 if (candidate instanceof AbstractBeanDefinition) { 12 postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName); 13 } 14 if (candidate instanceof AnnotatedBeanDefinition) { 15 AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate); 16 } 17 if (checkCandidate(beanName, candidate)) { 18 BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName); 19 definitionHolder = 20 AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry); 21 beanDefinitions.add(definitionHolder); 22 registerBeanDefinition(definitionHolder, this.registry); 23 } 24 } 25 } 26 return beanDefinitions; 27}

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

findCandidateComponents:从classPath扫描组件,并转换为备选BeanDefinition,也就是要做的解析@Component的核心方法。

概要分析

findCandidateComponents在其父类ClassPathScanningCandidateComponentProvider 中。

1public class ClassPathScanningCandidateComponentProvider implements EnvironmentCapable, ResourceLoaderAware { 2//省略其他代码 3public Set<BeanDefinition> findCandidateComponents(String basePackage) { 4 if (this.componentsIndex != null && indexSupportsIncludeFilters()) { 5 return addCandidateComponentsFromIndex(this.componentsIndex, basePackage); 6 } 7 else { 8 return scanCandidateComponents(basePackage); 9 } 10} 11private Set<BeanDefinition> scanCandidateComponents(String basePackage) { 12 Set<BeanDefinition> candidates = new LinkedHashSet<>(); 13 try { 14 String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX + 15 resolveBasePackage(basePackage) + '/' + this.resourcePattern; 16 Resource[] resources = getResourcePatternResolver().getResources(packageSearchPath); 17 //省略部分代码 18 for (Resource resource : resources) { 19 //省略部分代码 20 if (resource.isReadable()) { 21 try { 22 MetadataReader metadataReader = getMetadataReaderFactory().getMetadataReader(resource); 23 if (isCandidateComponent(metadataReader)) { 24 ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader); 25 sbd.setSource(resource); 26 if (isCandidateComponent(sbd)) { 27 candidates.add(sbd); 28 //省略部分代码 29 } 30 } 31 catch (IOException ex) {//省略部分代码 } 32 return candidates; 33} 34}

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其源码如下:

1protected boolean isCandidateComponent(MetadataReader metadataReader) throws IOException { 2 //省略部分代码 3 for (TypeFilter tf : this.includeFilters) { 4 if (tf.match(metadataReader, getMetadataReaderFactory())) { 5 return isConditionMatch(metadataReader); 6 } 7 } 8 return false; 9}

includeFilters由registerDefaultFilters()设置初始值,有@Component,没有@Service啊?

1protected void registerDefaultFilters() { 2 this.includeFilters.add(new AnnotationTypeFilter(Component.class)); 3 ClassLoader cl = ClassPathScanningCandidateComponentProvider.class.getClassLoader(); 4 try { 5 this.includeFilters.add(new AnnotationTypeFilter( 6 ((Class<? extends Annotation>) ClassUtils.forName("javax.annotation.ManagedBean", cl)), false)); 7 logger.trace("JSR-250 'javax.annotation.ManagedBean' found and supported for component scanning"); 8 } 9 catch (ClassNotFoundException ex) { 10 // JSR-250 1.1 API (as included in Java EE 6) not available - simply skip. 11 } 12 try { 13 this.includeFilters.add(new AnnotationTypeFilter( 14 ((Class<? extends Annotation>) ClassUtils.forName("javax.inject.Named", cl)), false)); 15 logger.trace("JSR-330 'javax.inject.Named' annotation found and supported for component scanning"); 16 } 17 catch (ClassNotFoundException ex) { 18 // JSR-330 API not available - simply skip. 19 } 20}

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。

1@Target({ElementType.TYPE}) 2@Retention(RetentionPolicy.RUNTIME) 3@Documented 4// @Service 派生自@Component 5@Component 6public @interface Service { 7 8 /** 9 * The value may indicate a suggestion for a logical component name, 10 * to be turned into a Spring bean in case of an autodetected component. 11 * @return the suggested component name, if any (or empty String otherwise) 12 */ 13 @AliasFor(annotation = Component.class) 14 String value() default ""; 15 16}

@Component是@Service的元注解,Spring 大概率,在读取@Service,也读取了它的元注解,并将@Service作为@Component处理。

3. 探寻@Component派生性流程

回顾ClassPathScanningCandidateComponentProvider 中的关键的代码片段如下:

1private Set<BeanDefinition> scanCandidateComponents(String basePackage) { 2 //省略其他代码 3 MetadataReader metadataReader 4 =getMetadataReaderFactory().getMetadataReader(resource); 5 if(isCandidateComponent(metadataReader)){ 6 //.... 7 } 8} 9public final MetadataReaderFactory getMetadataReaderFactory() { 10 if (this.metadataReaderFactory == null) { 11 this.metadataReaderFactory = new CachingMetadataReaderFactory(); 12 } 13 return this.metadataReaderFactory; 14}

1. 确定metadataReader

CachingMetadataReaderFactory继承自 SimpleMetadataReaderFactory,就是对SimpleMetadataReaderFactory加了一层缓存。

其内部的SimpleMetadataReaderFactory#getMetadataReader 为:

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

这里可以看出

MetadataReader metadataReader =new SimpleMetadataReader(...);

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

AnnotationTypeFilter#matchself方法如下:

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

是metadata.hasMetaAnnotation法,从名称看是处理元注解,我们重点关注

逐步分析

找metadata.hasMetaAnnotation

metadata=metadataReader.getAnnotationMetadata();

metadataReader =new SimpleMetadataReader(...)

metadata= new SimpleMetadataReader#getAnnotationMetadata()

1//SimpleMetadataReader 的构造方法 2SimpleMetadataReader(Resource resource, @Nullable ClassLoader classLoader) throws IOException { 3 InputStream is = new BufferedInputStream(resource.getInputStream()); 4 ClassReader classReader; 5 try { 6 classReader = new ClassReader(is); 7 } 8 catch (IllegalArgumentException ex) { 9 throw new NestedIOException("ASM ClassReader failed to parse class file - " + 10 "probably due to a new Java class file version that isn't supported yet: " + resource, ex); 11 } 12 finally { 13 is.close(); 14 } 15 16 AnnotationMetadataReadingVisitor visitor = 17 new AnnotationMetadataReadingVisitor(classLoader); 18 classReader.accept(visitor, ClassReader.SKIP_DEBUG); 19 20 this.annotationMetadata = visitor; 21 // (since AnnotationMetadataReadingVisitor extends ClassMetadataReadingVisitor) 22 this.classMetadata = visitor; 23 this.resource = resource; 24}

metadata=new SimpleMetadataReader(...)**.**getAnnotationMetadata()= new AnnotationMetadataReadingVisitor(。。)

也就是说

metadata.hasMetaAnnotation=AnnotationMetadataReadingVisitor#hasMetaAnnotation

其方法如下:

1public class AnnotationMetadataReadingVisitor{ 2 // 省略部分代码 3@Override 4public boolean hasMetaAnnotation(String metaAnnotationType) { 5 Collection<Set<String>> allMetaTypes = this.metaAnnotationMap.values(); 6 for (Set<String> metaTypes : allMetaTypes) { 7 if (metaTypes.contains(metaAnnotationType)) { 8 return true; 9 } 10 } 11 return false; 12} 13}

逻辑很简单,就是判断该注解的元注解在,在不在metaAnnotationMap中,如果在就返回true。

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

查找metaAnnotationMap赋值

回到SimpleMetadataReader 的方法,

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

发现一个可疑的语句:classReader.accept。

查看accept方法

1public class ClassReader { 2 //省略其他代码 3public void accept(..省略代码){ 4 //省略其他代码 5 readElementValues( 6 classVisitor.visitAnnotation(annotationDescriptor, /* visible = */ true), 7 currentAnnotationOffset, 8 true, 9 charBuffer); 10} 11}

查看readElementValues方法

1public class ClassReader{ 2 //省略其他代码 3private int readElementValues( 4 final AnnotationVisitor annotationVisitor, 5 final int annotationOffset, 6 final boolean named, 7 final char[] charBuffer) { 8 int currentOffset = annotationOffset; 9 // Read the num_element_value_pairs field (or num_values field for an array_value). 10 int numElementValuePairs = readUnsignedShort(currentOffset); 11 currentOffset += 2; 12 if (named) { 13 // Parse the element_value_pairs array. 14 while (numElementValuePairs-- > 0) { 15 String elementName = readUTF8(currentOffset, charBuffer); 16 currentOffset = 17 readElementValue(annotationVisitor, currentOffset + 2, elementName, charBuffer); 18 } 19 } else { 20 // Parse the array_value array. 21 while (numElementValuePairs-- > 0) { 22 currentOffset = 23 readElementValue(annotationVisitor, currentOffset, /* named = */ null, charBuffer); 24 } 25 } 26 if (annotationVisitor != null) { 27 annotationVisitor.visitEnd(); 28 } 29 return currentOffset; 30} 31}

这里面的核心就是  annotationVisitor.visitEnd();

确定annotationVisitor

这里的annotationVisitor=AnnotationMetadataReadingVisitor#visitAnnotation

源码如下,注意这里传递了metaAnnotationMap!!

1public class AnnotationMetadataReadingVisitor{ 2@Override 3public AnnotationVisitor visitAnnotation(String desc, boolean visible) { 4 String className = Type.getType(desc).getClassName(); 5 this.annotationSet.add(className); 6 return new AnnotationAttributesReadingVisitor( 7 className, this.attributesMap, 8 this.metaAnnotationMap, this.classLoader); 9} 10}

annotationVisitor=AnnotationAttributesReadingVisitor

查阅annotationVisitor.visitEnd()

annotationVisitor=AnnotationAttributesReadingVisitor#visitEnd()

1public class AnnotationAttributesReadingVisitor{ 2@Override 3public void visitEnd() { 4 super.visitEnd(); 5 6 Class<? extends Annotation> annotationClass = this.attributes.annotationType(); 7 if (annotationClass != null) { 8 List<AnnotationAttributes> attributeList = this.attributesMap.get(this.annotationType); 9 if (attributeList == null) { 10 this.attributesMap.add(this.annotationType, this.attributes); 11 } 12 else { 13 attributeList.add(0, this.attributes); 14 } 15 if (!AnnotationUtils.isInJavaLangAnnotationPackage(annotationClass.getName())) { 16 try { 17 Annotation[] metaAnnotations = annotationClass.getAnnotations(); 18 if (!ObjectUtils.isEmpty(metaAnnotations)) { 19 Set<Annotation> visited = new LinkedHashSet<>(); 20 for (Annotation metaAnnotation : metaAnnotations) { 21 recursivelyCollectMetaAnnotations(visited, metaAnnotation); 22 } 23 if (!visited.isEmpty()) { 24 Set<String> metaAnnotationTypeNames = new LinkedHashSet<>(visited.size()); 25 for (Annotation ann : visited) { 26 metaAnnotationTypeNames.add(ann.annotationType().getName()); 27 } 28 this.metaAnnotationMap.put(annotationClass.getName(), metaAnnotationTypeNames); 29 } 30 } 31 } 32 catch (Throwable ex) { 33 if (logger.isDebugEnabled()) { 34 logger.debug("Failed to introspect meta-annotations on " + annotationClass + ": " + ex); 35 } 36 } 37 } 38 } 39} 40}

内部方法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

点赞
收藏

评论区

加载中...

相关推荐

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

Oracle 分组与拼接字符串同时使用

SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(

皕杰报表之UUID

​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为

手写Java HashMap源码

HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22

Android So动态加载 优雅实现与原理分析

背景:漫品Android客户端集成适配转换功能(基于目标识别(So库35M)和人脸识别库(5M)),导致apk体积50M左右,为优化客户端体验,决定实现So文件动态加载.!(https://oscimg.oschina.net/oscnet/00d1ff90e4b34869664fef59e3ec3fdd20b.png)点击上方“蓝字”关注我

@Enable驱动逻辑

这个系列分为5篇1\.@Component,@Service等注解是如何被解析的(https://my.oschina.net/floor/blog/4325651)2\.@Enable驱动原理(https://my.oschina.net/floor/blog/4333081)3\.@EnableAutoConfiguratio