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

news2024/11/20 11:29:36

对于 @Component 注解在日常的工作中相信很多小伙伴都会使用到,作为一种 Spring 容器托管的通用模式组件,任何被 @Component 注解标注的组件都会被 Spring 容器扫描。

那么有的小伙伴就要问了,很多时候我们并没有直接写 @Component 注解呀,写的是类似于 @Service@RestController@Configuration 等注解,不也是一样可以被扫描到吗?那这个 @Component 有什么特别的吗?

元注解

在回答上面的问题之前,我们先来了解一下什么叫元注解,所谓元注解就是指一个能声明在其他注解上的注解,换句话说就是如果一个注解被标注在其他注解上,那么它就是元注解。

要说明的是这个元注解并不是 Spring 领域的东西, 而是 Java 领域的,像 Java 中的很多注解比如 @Document@Repeatable@Target 等都属于元注解。

根据上面的解释我们可以发现在 Spring 容器里 @Component 就是以元注解的形式存在,因为我们可以在很多其他注解里面找到它的身影,如下所示

Configuration

controller

@Component 的派生性

通过上面的内容我们是不是可以猜测一下那就是 @Component 注解的特性被"继承"下来了?这就可以解释为什么我们可以直接写@Service@RestController 注解也是可以被扫描到的。但是由于 Java 的注解是不支持继承的,比如你想通过下面的方式来实现注解的继承是不合法的。

@interface

为了验证我们的猜想,可以通过跟踪源代码来验证一下,我们的目的是研究为什么不直接使用 @Component 注解也能被 Spring 扫描到,换句话说就是使用 @Service@RestController 的注解也能成为 Spring Bean

那我们很自然的就可以想到,在扫描的时候一定是根据注解来进行了判断是否要初始化成 Spring Bean 的。我们只要找到了判断条件就可以解决我们的疑惑了。

由于 SpringBoot 项目是通过 main 方法进行启动的,调试起来还是很方便的,阿粉这边准备了一个简单的 SpringBoot 工程,里面除了启动类之外只有一个DemoController.java 代码如下

package com.example.demojar.controller;

import org.springframework.web.bind.annotation.RestController;

@RestController
public class DemoController {
}

启动类如下

package com.example.demojar;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.WebApplicationType;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.context.ConfigurableApplicationContext;

@SpringBootApplication(scanBasePackages = {"com.example.demojar"})
public class DemoJarApplication {

	public static void main(String[] args) {
		SpringApplication.run(DemoJarApplication.class, args);
	}
}

Debug run 方法,我们可以定位到 org.springframework.boot.SpringApplication#run(java.lang.String...) 方法,该方法里面会初始化 SpringBoot 上下文 context

context = createApplicationContext();

默认情况下会进到下面的方法,并创建 AnnotationConfigServletWebServerApplicationContext 并且其构造函数中构造了 ClassPathBeanDefinitionScanner 类路径 Bean 扫描器。此处已经越来越接近扫描相关的内容了。

org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext.Factory#create

context 上下文创建完成过后,接下来我们我们会接入到 org.springframework.context.support.AbstractApplicationContext#refresh,再到 org.springframework.context.support.AbstractApplicationContext#invokeBeanFactoryPostProcessors

org.springframework.context.annotation.ConfigurationClassPostProcessor#postProcessBeanDefinitionRegistry

org.springframework.context.annotation.ConfigurationClassPostProcessor#processConfigBeanDefinitions

经过上面的步骤,最终可以可以定位到扫描的代码在下面的方法 org.springframework.context.annotation.ComponentScanAnnotationParser#parse 里面,调用前面上下文初始化的扫描器的 org.springframework.context.annotation.ClassPathBeanDefinitionScanner#doScan 方法,

到这里我们已经定位到了扫描具体包路径的方法,这个方法里面主要看 findCandidateComponents(basePackage); 方法的内容,这个方法就是返回合法的候选组件。说明这个方法会最终返回需要被注册成 Spring Bean 的候选组件,那我们重点就要看这个方法的实现。

跟踪这个方法 org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider#findCandidateComponents 进去我们可以看到通过加进类路径里面的资源文件,然后再根据资源文件生成 MetadataReader 对象,最后判断这个 MetadataReader 对象是否满足候选组件的条件,如果满足就添加到 Set 集合中进行返回。

继续追踪源码我们可以找到具体的判断方法在 org.springframework.core.type.filter.AnnotationTypeFilter#matchSelf 方法中,如下所示,可以看到这里对 MetadataReader 对象进行了判断是否有元注解 @Component。在调试的时候我们会发现 DemoController 在此处会返回 true,并且该 MetadataReader 对象里面还有多个 mappings ,其实这些 mappings 对应的就是 Spring 的注解。

这个 mappings 里面的注解确实包含了 @Component 注解,因此会返回 true。那么接下来问题就转换成,我们的 DemoController 对应的 MetadataReader 对象是如何创建的。

我们看回到 org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider#scanCandidateComponents 方法,看看具体 MetadataReader 对象是如何创建的,MetadataReader metadataReader = getMetadataReaderFactory().getMetadataReader(resource);

通过构造方法 org.springframework.core.type.classreading.SimpleMetadataReader#SimpleMetadataReader 进行 MetadataReader 对象的创建,org.springframework.core.type.classreading.SimpleAnnotationMetadataReadingVisitor#visitEnd,最终定位到 org.springframework.core.annotation.MergedAnnotationsCollection#MergedAnnotationsCollection 这里进行 mappings 赋值。

继续定位到 org.springframework.core.annotation.AnnotationTypeMappings.Cache#createMappingsorg.springframework.core.annotation.AnnotationTypeMappings#addAllMappingsaddAllmappings 方法,内部使用了一个 while 循环和 Deque 来循环查询元注解进行赋值,代码如下所示,重点是这一行 Annotation[] metaAnnotations = AnnotationsScanner.getDeclaredAnnotations(source.getAnnotationType(), false);

private void addAllMappings(Class<? extends Annotation> annotationType,
			Set<Class<? extends Annotation>> visitedAnnotationTypes) {

		Deque<AnnotationTypeMapping> queue = new ArrayDeque<>();
		addIfPossible(queue, null, annotationType, null, visitedAnnotationTypes);
		while (!queue.isEmpty()) {
			AnnotationTypeMapping mapping = queue.removeFirst();
			this.mappings.add(mapping);
			addMetaAnnotationsToQueue(queue, mapping);
		}
	}

	private void addMetaAnnotationsToQueue(Deque<AnnotationTypeMapping> queue, AnnotationTypeMapping source) {
		Annotation[] metaAnnotations = AnnotationsScanner.getDeclaredAnnotations(source.getAnnotationType(), false);
		for (Annotation metaAnnotation : metaAnnotations) {
			if (!isMappable(source, metaAnnotation)) {
				continue;
			}
			Annotation[] repeatedAnnotations = this.repeatableContainers.findRepeatedAnnotations(metaAnnotation);
			if (repeatedAnnotations != null) {
				for (Annotation repeatedAnnotation : repeatedAnnotations) {
					if (!isMappable(source, repeatedAnnotation)) {
						continue;
					}
					addIfPossible(queue, source, repeatedAnnotation);
				}
			}
			else {
				addIfPossible(queue, source, metaAnnotation);
			}
		}
	}

综上所述我们可以发现尽管我们没有直接写 @Component 注解,只要我们加了类似于 @Service@RestController 等注解也是可以成功被 Spring 扫描到注册成 Spring Bean 的,本质的原因是因为这些注解底层都使用了 @Component 作为元注解,经过源码分析我们发现了只要有 @Component 元注解标注的注解类也是同样会被进行扫描的。

总结

上面的源码追踪过程可能会比较枯燥和繁琐,最后我们来简单总结一下上面的内容:

  1. 方法 org.springframework.boot.SpringApplication#run(java.lang.String...) 中进行 Spring 上下文的创建;
  2. 在初始化上下文的时候会创建扫描器 ClassPathBeanDefinitionScanner
  3. org.springframework.context.support.AbstractApplicationContext#refresh 进行 beanFactory 准备;
  4. org.springframework.context.annotation.ClassPathBeanDefinitionScanner#doScan 进行资源扫描
  5. org.springframework.core.annotation.MergedAnnotationsCollection#MergedAnnotationsCollection 进行注解 mappings 的赋值;
  6. org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider#scanCandidateComponents 方法中进行候选组件的判断;

上面追踪的过程可能会比较复杂,但是只要我们理解了原理还是可以慢慢跟上的,因为我们只要把握好了方向,知道首先肯定会进行资源扫描,扫描完了肯定是根据注解之间的关系进行判断,最终得到我们需要的候选组件集合。至于如何创建 MetadataReader 和如何获取元注解,只要我们一步步看下去就是可以找到的。

最后说明一下,Spring Framework 每个版本的具体实现会有差异,阿粉使用的版本是 5.3.24 ,所以如果小伙伴看到自己的代码追踪的效果跟阿粉的不一样也不会奇怪,可能是因为版本不一样而已,不过本质上都是一样的。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/106463.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

计算机毕设Python+Vue寻人系统设计(程序+LW+部署)

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

Spring Boot日志文件

哈喽呀&#xff0c;你好呀&#xff0c;欢迎呀&#xff0c;快来看一下这篇宝藏博客吧~~~ 目录 1.日志快速扫盲 2.Spring Boot项目日志简单分析 3.自定义打印日志 4.通过设置日志的级别来筛选和控制日志输出的内容 5.日志持久化 1.日志快速扫盲 什么是日志?说白了就是控制…

Java学习笔记——Idea集成git

Idea集成git-创建本地仓库-提交代码

深入浅出pom.xml文件

前言 在每一个pom文件的开头都会有这样几行代码 <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0" xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocatio…

Spring Boot3.0正式发布及新特性解读

Spring Boot 3.0 正式发布 同时发布更新的还有 2.7.x 和 2.6.x 两条版本线&#xff0c;Spring Boot 是我见过的发版最守时的技术框架之一。 Spring Boot 3.0 现已正式发布&#xff0c;它包含了 12 个月以来 151 个开发者的 5700 多次代码提交。这是自 4.5 年前发布 2.0 以来&a…

Live800:在线客服系统排名是怎么样的?

在线客服系统排名是怎么样的?在线客服系统提供商提供哪些服务?在线客服系统评测要点?这些都是企业很关心的问题,这里进行简要的解答。 在线客服系统排名是怎么样的? 客观来说在线客服系统没有统一的行业标准,因此也没有统一的排名。各在线客服系统厂商各有特色,行业竞争激…

【大数据】python连接并使用redis

文章目录redis安装redis连接python安装redis库conda下载及配置vs连接redisredis使用stringset设置getrange截取append追加内容strlen(key) 字节长度listlpush,rpush赋值lpushx,rpushx只给存在的键值赋值llen 列表个数linsert 在某一个值前或者后插入新值lset 对某一个索引位置赋…

Hive 源码解读 准备篇 Debug 讲解

使用 Hive 执行 HQL 查询时遇到 bug,解决办法无非几种,explain HQL、查看日志、远程 Debug,本文就将详细讲解如何使用 Idea 远程 Debug。 1. Debug 环境准备 下载 Hive 源码包,自行编译一下,建议在 Linux 环境下编译,然后将整个编译好的包全部拷贝到 IDEA 工作目录中并…

软考高级-系统架构设计师-知识点总结(一)架构设计基础

第一部分&#xff0c;架构设计基础。由系统架构设计师概述、计算机与网络基础知识、信息系统基础知识、系统开发基础知识四部分构成。 目录 系统架构设计师概述 系统架构的概念和历史 系统架构设计师的定义 系统架构师具备的能力 计算机与网络基础知识 操作系统基础 操作…

加法扩散模型全部过程推导和实现代码

🍿*★,*:.☆欢迎您/$:*.★* 🍿 add_diff 使用之前的扩散方法(get_image_by_t_cv)总结出来的 get_noise 和 get_x 通过 add_diff 可以得到 通过get_x 带入 两组参数 可以推导出 get_xt_1 使用add_diff(这里要反着用 参考show_add_diff_r) 输入 x noise t t max 可以得到x…

DockeFile的介绍与使用

目录 1. Dockfile是什么 2. Dockerfile的基本组成 2.1 FROM 2.2 MAINTAINER 2.3 RUN 2.4 COPY 2.5 ADD 2.6 EXPOSE 2.7 WORKDIR 2.8 ONBUILD 2.9 USER 2.10 VOLUME 2.11 CMD 2.12 ENTRYPOINT 3. dockerfile示例 3.1 准备 3.2 将该目录上传至linux 3.3 构建镜…

Docker+Jenkins+Gitee+Maven项目配置jdk、maven、gitee等拉取代码并自动构建以及遇到的那些坑

场景 CentOS中使用Docker安装Jenkins&#xff1a; CentOS中使用Docker安装Jenkins_霸道流氓气质的博客-CSDN博客_centos docker jenkins 在上面使用Docker部署起来Jenkins的基础上&#xff0c;怎样拉取SpringBoot项目代码并编译构建。 后台项目的搭建参考如下。 若依前后端…

OpenFeign AutoConfiguration源码解析

本文约2千字&#xff0c;主要知识 OpenFeign的父子容器FeignClient的注册 背景 在使用Spring Cloud时&#xff0c;经常使用OpenFeign 作为远程服务调用的类库&#xff1b;Feign 是一种声明式服务调用组件&#xff0c;它在 RestTemplate 的基础上做了进一步的封装。通过 Feig…

跨平台应用开发进阶(五十)uni-app ios web-view嵌套H5项目白屏问题分析及解决

文章目录一、前言二、问题分析三、解决方案3.1 nvue 页面替代 vue 页面3.2 白屏检测刷新3.2.1 自动刷新3.2.2 手动刷新3.3 总结四、拓展阅读一、前言 应用uni-app框架开发好APP上架使用过程中&#xff0c;发现应用经过长时间由后台切换至前台时&#xff0c;通过webview方式嵌套…

SQL语句(基本)

SELECT 语句的 执行过程&#xff1a; from clause ---> where clause ---> select --->group by ---> having--->order by ---> limit 写法顺序: select col1,... from clause ---> where clause ---> group by ---> having---> order by --->…

“ 请你要发光 而不是被照亮 “

做一个厉害的大人 勇敢地长大 成为会发光的星星 勇音频&#xff1a;00:0003:41 | 01 | 世界不会辜负努力拼搏的人 光明的前途在乌云散去之后 请你一定一定坚持自己 勿忘初心 要做会发光的星星 成为想成为的大人啊 | 02 | 我牵起你的手 你望向我的眼 少了你的懵懂青涩…

总线一:IIC

一、I2C集成电路总线, 多用于主控制器和从器件间的主从通信。 二、适用场景&#xff1a;在小数据量场合使用&#xff0c;传输距离短。 三、IIC是半双工。IIC的物理层&#xff1a;两条总线线路&#xff0c;一条是串行数据线SDA&#xff0c;一条是串行时钟线SCL&#xff0c;当总…

《Python知识手册》更新到V4.1版,快拿走学习

前言 最近&#xff0c;我花了点时间&#xff0c;把《Python知识手册》的部分内容进行了更新&#xff0c;更新后的版本号为 v4.1 版。 python知识手册内容&#xff1a;《Python知识手册》 没有比较完整的覆盖 Python 的基础知识。因此&#xff0c;针对手册的阅读&#xff0c;各…

程序员年底好找工作吗?

到年底了除非必要不要辞职&#xff01;除非必要不要辞职&#xff01;除非必要不要辞职&#xff01; 重要的事情说三遍。 很多老哥问我&#xff1a;工作干不下去了&#xff0c;这会儿辞职找工作合适吗&#xff1f;今天就来为大家解答一下&#xff0c;为什么不要在年底辞职&…

Redis6入门到实战------ 六、Redis_Jedis_测试

1 Jedis所需要的jar包 在pom文件中引入依赖 <dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> <version>3.2.0</version> </dependency>2 连接Redis注意事项 禁用Linux的防火墙&#xff1a;L…