当前位置: 首页 > 科技观察

你知道@Component注解的派生是什么吗?

时间:2023-03-19 20:14:40 科技观察

相信很多小伙伴在日常工作中都会用到@Component注解。作为Spring容器托管的通用模式组件,任??何标有@Component注解的组件都会被Spring容器扫描。那么有的朋友要问了,很多时候我们不是直接写@Component注解,而是写类似@Service、@RestController、@Configuration等注解,不也一样可以扫描吗?那么这个@Component有什么特别之处吗?Meta-annotation在回答上面的问题之前,我们先了解一下什么是meta-annotation。所谓元注解,是指一个注解可以声明在其他注解上。也就是说,如果一个注解被标记在其他注解之上,那么它就是元注解。需要注意的是,这个元注解不是Spring领域的,而是Java领域的。Java中的很多注解,比如@Document、@Repeatable、@Target等,都是元注解。根据上面的解释,我们可以找到Spring容器中@ComponentConfigurationcontroller@Component的推导。通过上面的内容,我们是不是可以猜到@Component注解的特性是“继承”的呢?这就可以解释为什么我们可以直接写@Service了,@RestController注解也可以扫一扫。但是,由于Java注解不支持继承,例如,你通过下面的方式实现注解继承是非法的。@interface为了验证我们的猜想,我们可以通过跟踪源码来验证。我们的目的是研究为什么不直接使用@Component注解也能被Spring扫描到。也就是说,使用@Service和@RestController注解也可以变成SpringBeans。那么我们自然可以想到,在扫描的时候,一定要根据注解来判断是否将其初始化为一个SpringBean。只要找到判断条件,就可以解决我们的疑惑。由于SpringBoot项目是通过main方法启动的,调试起来非常方便。在这里,阿芬准备了一个简单的SpringBoot项目,其中除了启动类外,只有一个DemoController.java代码。packagecom.example.demojar.controller;importorg.springframework.web.bind.annotation.RestController;@RestControllerpublicclassDemoController{}启动类如下packagecom.example.demojar;importorg.springframework.boot.SpringApplication;导入org.springframework.boot。WebApplicationType;导入org.springframework.boot.autoconfigure.SpringBootApplication;导入org.springframework.boot.builder.SpringApplicationBuilder;导入org.springframework.context.ConfigurableApplicationContext;@SpringBootApplication(scanBasePackages={"com.example.demojar"})公共类DemoJarApplication{publicstaticvoidmain(String[]args){SpringApplication.run(DemoJarApplication.class,args);}}调试run方法,我们可以定位到org.springframework.boot.SpringApplication#run(java.lang.String...)方法,该方法会初始化SpringBootcontext上下文。context=createApplicationContext();默认情况下,它会进入如下方法,创建AnnotationConfigServletWebServerApplicationContext,并在其构造函数中构造一个ClassPathBeanDefinitionScanner类路径Bean扫描器。距离这里扫描相关的东西越来越近了。org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext.Factory#createcontext创建上下文后,我们会访问org.springframework.context.support.AbstractApplicationContext#refresh,然后到org.springframework。context.support.AbstractApplicationContext#invokeBeanFactoryPostProcessorsorg.springframework.context.annotation.ConfigurationClassPostProcessor#postProcessBeanDefinitionRegistryorg.springframework.context.annotation.ConfigurationClassPostProcessor#processConfigBeanDefinitions经过以上步骤,终于可以在org.rame中的如下方法annotation.ComponentScanAnnotationParser中定位扫描代码#parse里面,调用前面context初始化的scanner的org.springframework.context.annotation.ClassPathBeanDefinitionScanner#doScan方法。这里我们找到了扫描具体包路径的方法。这个方法主要看findCandidateComponents(basePackage);方法的内容,这个方法是返回一个合法的候选组件。说明该方法最终会返回需要注册为SpringBeans的候选组件,所以我们重点关注该方法的实现。跟踪这个方法org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider#findCandidateComponents进入我们可以看到添加到类路径的资源文件,然后根据资源文件生成一个MetadataReader对象,最后判断MetadataReader对象是否满足候选component如果满足条件,就会加入到Set集合中返回。继续跟踪源码,我们可以在org.springframework.core.type.filter.AnnotationTypeFilter#matchSelf方法中找到具体的判断方法,如下图,可以看到判断MetadataReader对象是否有meta-注解@Component。在调试的时候,我们会发现这里DemoController会返回true,而MetadataReader对象中存在多个映射。其实这些映射对应的就是Spring的注解。此映射中的注释确实包含@Component注释,因此它将返回true。那么接下来的问题就转化为如何创建我们的DemoController对应的MetadataReader对象。我们回过头来看看org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider#scanCandidateComponents方法,看看具体的MetadataReader对象是如何创建的,MetadataReadermetadataReader=getMetadataReaderFactory().getMetadataReader(resource);通过构造方法org.springframework.core.type.classreading.SimpleMetadataReader#SimpleMetadataReader创建MetadataReader对象,org.springframework.core.type.classreading.SimpleAnnotationMetadataReadingVisitor#visitEnd,最后定位到org.springframework.core.annotation。MergedAnnotationsCollection#MergedAnnotationsCollection在这里用于映射分配。继续定位org.springframework.core.annotation.AnnotationTypeMappings.Cache#createMappings、org.springframework.core.annotation.AnnotationTypeMappings#addAllMappings、addAllMappings方法,内部使用while循环和Deque循环查询meta-annotations进行赋值,代码如下,重点是这一行>>visitedAnnotationTypes){Dequequeue=newArrayDeque<>();addIfPossible(queue,null,annotationType,null,visitedAnnotationTypes);while(!queue.isEmpty()){AnnotationTypeMappingmapping=queue.removeFirst();this.mappings.add(映射);addMetaAnnotationsToQueue(队列,映射);}}privatevoidaddMetaAnnotationsToQueue(Dequequeue,AnnotationTypeMappingsource){Annotation[]metaAnnotations=AnnotationsScanner.getDeclaredAnnotations(source.getAnnotationType(),false);for(AnnotationmetaAnnotation:metaAnnotations){if(!isMappable(source,metaAnnotation)){继续;注释[]repeatedAnnotations=this.repeatableContainers.findRepeatedAnnotations(metaAnnotations);if(repeatedAnnotations!=null){for(AnnotationrepeatedAnnotation:repeatedAnnotations){if(!isMappable(source,repeatedAnnotation)){继续;}addIfPossible(队列,源,repeatedAnnotation);}}else{addIfPossible(队列,源,metaAnnotation);综上所述,我们可以发现,虽然我们没有直接写@Component注解,但是只要加上@Service、@RestController等注解,就可以成功被Spring扫描到,注册为SpringBeans。本质上是因为@Component被用作这些注解底部的元注解。经过源码分析,我们发现只要有@Component元注解标注的注解类,也会被扫描总结上面的源码跟踪过程可能比较枯燥和繁琐。最后,简单总结一下以上内容:在org.springframework.boot.SpringApplication#run(java.lang.String...)方法中创建一个Spring上下文;初始化上下文时会创建扫描器ClassPathBeanDefinitionScanner;beanFactory准备在org.springframework.context.support.AbstractApplicationContext#refresh中执行;资源扫描在org.springframework.core.org.springframework.context.annotation.ClassPathBeanDefinitionScanner#doScan中执行。annotation.MergedAnnotationsCollection#MergedAnnotationsCollection注释映射的赋值;org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider#scanCandidateComponents方法判断候选组件;上面的跟踪过程可能比较复杂,但是只要明白了原理,还是可以慢慢跟上的,因为只要把握好方向,就知道会先进行资源扫描。扫描完之后还要根据注解之间的关系进行判断,最终得到我们需要的候选组件集合。至于如何创建MetadataReader,如何获取meta-annotations,只要一步步看就能找到。最后说明一下,每个版本的SpringFramework的具体实现都会有所不同。阿粉使用的版本是5.3.24,看到你的代码跟踪效果和阿粉不一样也就不足为奇了。可能是版本不同,但本质是一样的。