相信很多小伙伴在日常工作中都会用到@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){Deque
