啥情况?为什么我的 Service 无法注入进来?

ca492344389da9200c7bff65ee508e98.gif

作者 | 敖丙

来源 | 敖丙

今天同事火急火燎的走了过来,说:快帮我看看这个错误,啥情况啊?

我一看报错:

Field xxxService in com.xx.xx.service.impl.XxXServiceImpl required a bean of type 'com.xx.xx.service.XxxService' that could not be found.

我其实已经知道是啥情况了,但是怕他不知道,所以还是耐心的跟她解释了一下,她听完后说:能不能写下来啊,免得我下次还会忘。

这个错误其实就是这个Bean在Spring容器中找不到,发生这种错误时,常见的有两种情况:

1、@ComponentScan注解里的扫描路径没包含这个类

2、这个类的头上没加@Component注解

那么问题就来了:为什么@ComponentScan没扫描到或者没加@Component注解就注入不到Spring容器中?这个问题有点无厘头(没加@Component注解你还想注入到Spring容器中?)

我换种问法:为什么@ComponentScan扫描到了并且加了@Component注解就能注入到Spring容器中?

当然你可以直接回答:因为Spring规定这样做的

当然我也会接着反问你:Mybatis的Mapper就没用@Component注解,凭啥它就能注入到Spring容器中?

76b6bc7ac8705d358dc1bc07fe4e0a54.png

问题分析

要回答:为什么@ComponentScan扫描到了并且加了@Component注解就能注入到Spring容器中?

我们首先需要对问题进行拆解:

1、@ComponentScan扫描是做了什么?

2、加了@Component注解又代表了什么?

回答了这两个问题我们再进行猜想:以上过程是否可以进行自定义?如何自定义?否则就没有办法说明Mapper是如何注入到Spring容器中的。

295161be82cebe88219e07389228ee70.png

@ComponentScan扫描是做了什么?

这个过程大概是这样的:Spring通过扫描指定包下的类,解析这些类的信息,转化成为BeanDefinition,注册到beanDefinitionMap中。

那么这个过程的详情情况又是如何呢?

我们先来了解一下这个过程中涉及到的角色:

1、BeanDefinition:Bean定义,内含Class的相关信息

2、ConfigurationClassPostProcessor:配置类处理器,查找配置类,创建配置类解析器

3、ConfigurationClassParser:配置类解析器,解析配置类,创建@ComponentScan注解解析器

4、ComponentScanAnnotationParser:@ComponentScan注解解析器,解析@ComponentScan注解,创建Bean定义扫描器

5、ClassPathBeanDefinitionScanner:Bean定义扫描器,扫描指定包下的所有类,将符合的类转化为BeanDefinition

6、BeanDefinitionRegistry:BeanDefinition注册器,注册BeanDefinition

从上往下看,我们可以轻易的发现,这整个过程有一种层层递进的关系:

8d6ded122ef6d303c121e46cc45dadc8.png

下面我们再来看看这些角色的具体职责。

1.配置类处理器

配置类处理器主要做了3件事

1、查找配置类

2、创建配置类解析器并调用

3、加载配置类解析器所返回的@Import与@Bean注解的类

1.1查找配置类

你可能会有疑惑,配置类不是我们传入的吗?为什么还需要去查找配置类呢?

这是因为Spring整个调用链路十分复杂,不可能说把配置类往下层层传递,而是一开始时就将配置类注册到BeanDefinitonMap中了。

查找配置类大致有两个过程:

1、从BeanFactory中获取到所有的BeanDefiniton信息

2、判断BeanDefiniton是否为配置类

第一步很好解决,所有的BeanDefiniton是放在BeanFactory的BeanDefinitonMap中,直接从中获取就可以了。

而对于第二点,首先我们要知道什么是配置类?

在Spring中,有两种配置类:

1、full类型:标识了@Configuration注解的类

2、lite类型:

标识了@Component @ComponentScan @Import @ImportResource @Bean 注解的类(其中之一就行)

他们唯一的区别就在于:full类型的类会在后置处理步骤中进行动态代理

@Configuraiton
public class MyConfiguration{@Beanpublic Car car(){return new Car(wheel());}@Beanpublic Wheel wheel(){return new Wheel();}
}

当查找出所有的配置类信息之后,紧接着就是创建配置类解析器,并将所有的配置类交由配置类解析器进行解析

1.2流程图

0adcf23d9977feb9614c79fef1614991.png

2.配置类解析器

配置类解析器的职责如下:

  • 判断该类是否应该跳过解析

  • 解析内部类信息

  • 解析@PropertySources注解信息

  • 解析@ComponentScan注解信息

  • 解析@Import注解信息

  • 解析@Bean注解信息

2.1判断该类是否应该跳过解析

所谓判断类是否应该跳过解析,其实就是判断类是否标识了@Conditional注解并且是否满足该条件。如果标识了该注解并且不满足条件,那么则跳过解析步骤。

如我们常见的@Profile,@ConditionalOnMissBean等都是由此控制。

2.2解析内部类信息

有时候我们的配置类里面有内部类,并且内部类也是个配置类,那么就需要用此方式进行解析。

2.3解析@ComponentScan注解信息

该步骤主要是利用**@ComponentScan注解解析器进行解析@ComponentScan注解,从而获取到BeanDefinition列表,再判断这些BeanDefinition是否是个配置类,是则再次调用配置类解析器**进行递归解析。

3.@ComponentScan注解解析器

在该步骤中,Spring会将我们配置在@ComponentScan注解上的所有信息提取出来,存入到Bean定义扫描器中,再利用Bean定义扫描器得到符合条件的BeanDefiniton。

c25a8a207cae0eef081c7fa4f4f19cce.png

excludeFilter和includeFilter用于扫描时判断class是否符合要求。

默认的excludeFilter:扫描时排除掉自己这个class

默认的includeFilter: 扫描时判断该class是否标识@Component注解

4.Bean定义扫描器

BeanDefinitionScanner主要做了三件事:

1、扫描包路径下的类

2、给BeanDefiniton设值

3、使用BeanDefinition注册器将BeanDefiniton注册到容器中

4.1扫描包路径下的类

扫描包路径的步骤可以简单理解为遍历class文件的过程,遍历包下的每个class,判断该class是否满足条件——标识了@Component注解,将满足条件的class转化为BeanDefiniton,此时BeanDefiniton只有metedata信息,还没有具体设值。

4.2给BeanDefiniton设值

如果我们在类上加了类似这些注解:@Lazy @Primary @DependsOn,那么就需要将这些注解转化为实际的属性设到BeanDefiniton中。

4.3流程图

468863b4aa76d0f9be6b72d704a8830f.png

5.BeanDefinition注册器

BeanDefinitionRegistry的作用就是将BeanDefiniton放到BeanDefinitonMap中

5a2fff04cd9730bb273d357496af1f16.png

思考

现在我们已经知道了扫描包的整体过程,再来回顾一下这个问题:Mybatis的Mapper是怎么注入到Spring容器中的?

像这种问题咋一看很难理解,常常在面试的情况发生,因为面试官是拿着答案问问题。

但是我们思考的话,就应该换个角度:怎么才能让Mapper注册到Spring中 -> 怎么才能让自定义的注解标识的Class注册到Spring中?

不知道这样问是否简单些呢?

2955e8b69d9ccc1f173b7804b2fde307.png

方法

1.使用TypeFilter

我们知道@Component注解是和默认注册的IncludeFilter配套使用的,那么同样我们也可以使用一个自定义的IncludeFilter与我们的自定义注解配套使用

自定义Mapper注解

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Mapper {
}

使用Mapper

@Mapper
public class MyMapper {public void hello(){System.out.println("myMapper hello");}
}

测试

添加一个自定义的IncludeFilter进行测试

859d0a1b789ac90580ee9d2fd23ae393.png

**注意:**此方式只能支持自定义注解标识在实体类的情况,如果将Mapper注解加在接口上,则你会收获一个异常:No bean named 'myMapper' available

答案很简单,因为接口不能实例化,所以Spring默认判断如果该类非实体类,则不注册到容器中。

那么我们怎么才能让加了Mapper注解的接口能注册到Spring中呢?

2.自定义扫描器

既然Spring的扫描器无法支持接口,那么我们就重写它——的判断逻辑。

所以我们方式很简单:继承ClassPathBeanDefinitionScanner,重写判断Class是否符合的逻辑

public class ClassPathMapperScanner extends ClassPathBeanDefinitionScanner {@Overrideprotected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {// 重写判断beanDefinition是否为接口逻辑,改为只有类为接口时才允许注册return beanDefinition.getMetadata().isInterface() && beanDefinition.getMetadata().isIndependent();}//省略构造方法
}

逻辑已经改好了,现在迎来一个新问题:怎么让Spring使用它?

通过整体流程我们知道,Bean定义扫描器是在**@ComponentScan注解解析器**的解析流程中创建(new)出来的,我们又不能更改这个流程,所以, Game Over?

但,为什么一定要在Spring的扫描流程中使用我们的扫描器呢?我们可以在Spring的扫描流程结束后,再扫描一遍不就好了吗?

还记得有什么方式可以做到这件事吗?后置处理器!

3.使用后置处理器

我们通过使用BeanDefinitionRegistryPostProcessor,让Spring的扫描流程结束之后,进行一次后置处理。在后置处理中,创建出自定义的扫描器,进行第二次扫描。

@Component
public class MapperScannerProcessor implements BeanDefinitionRegistryPostProcessor {@Overridepublic void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {// 创建出自定义的扫描器ClassPathMapperScanner classPathMapperScanner = new ClassPathMapperScanner(registry, false);// 添加filter,class添加了Mapper注解才注册到Spring中classPathMapperScanner.addIncludeFilter(new AnnotationTypeFilter(Mapper.class));// 这里可以改为从外部设值,不必写死classPathMapperScanner.scan("com.my.spring.test.custom");}
}

使用这种方式,你会发现,我们的接口确实注册到BeanDefinitionMap中了。

f277358d4449e074ea6127a84a81e588.png

但是,你仍然会收到一个错误:

Failed to instantiate [com.my.spring.test.custom.InterfaceMapper]: Specified class is an interface

接口确实是无法实例化的,虽然我们把它注册到了Spring中。但Mybatis又是怎么做的呢?

答案是替换,Mybatis将图中的beanClass替换成了FactoryBean: MapperFactoryBean,然后将原有的beanClass放入了它的mapperInterface属性中

它的getObject方法长这样

public T getObject() throws Exception {return getSqlSession().getMapper(this.mapperInterface);
}

好了,关于思考的内容就到这里,我们只是借用Mybatis的现象进行思考,再深入就是Mybatis的内容了。

6f7e3ef2371eec51f674c4e785cf9504.png

小结

本文借助一个开发时常见的问题进行分析,介绍了Spring的配置类解析与扫描过程,同时,还借助了Mybatis中的现象,思考怎么才能让自定义的注解标识Class注册到Spring中这一问题,并使用案例给出了一份较好的答案,希望大家能够通过案例更加深入的了解该流程。

同样,通过本次学习,来评论区回答以下问题吧~

1、什么是配置类?Spring中有哪几种配置类?有什么区别?

2、BeanDefinitionRegistryPostProcessor有什么用?你知道哪些案例吗?

94848b6d74d1db3a6209e03ee24dc6b5.gif

e7ae2e9e0e347199b27f166a9fe43cd5.png

往期推荐

Android 13 第一个开发者版本来了,网友直呼:Android 12 还没玩透!

好饭不怕晚,扒一下 Redis 的配置文件

使用这个库,让你的服务操作 Redis 速度飞起

使用 Cilium 增强 Kubernetes 网络安全

c573d3933b6b4c56aeeba9649673c12b.gif

点分享

7ea89c0926daf9dbbb22675dcbeb3a33.gif

点收藏

b5aca3fff11c7d00af3d47e399590454.gif

点点赞

6e32f2c2b84f65db0b3fd5c22119c951.gif

点在看

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

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

相关文章

oracle sga pga mysql_oracle实例内存(SGA和PGA)调整-xin

一、名词解释(1)SGA:System Global Area是Oracle Instance的基本组成部分,在实例启动时分配;系统全局域SGA主要由三部分构成:共享池、数据缓冲区、日志缓冲区。(2)共享池:Shared Pool用于缓存最近被执行的SQL语句和最近被使用的数…

IoT Studio可视化搭建平台编辑历史功能的思考与探索

简介: 在前端可视化搭建领域中“重做”和“撤销”这两个功能已经是标配中的标配,毕竟只要有用户行为的地方就可能会有出错,这两个功能无疑就是为用户提供了“后悔药”。目前有各种各样的可视化搭建平台,本文介绍IoT Studio可视化搭…

云计算架构设计6大原则,你遵循了吗?| 赠书

作者 | 吕昭波 2006年,第一个云计算(Cloud Computing)产品诞生,云计算的概念也被提出,现在云计算几乎已经渗入所有的行业和应用场景中。我们不一定能直接感受到云计算对日常生活、工作、学习的影响,但作为I…

python写自动化工具_微软最强 Python 自动化工具开源了!不用写一行代码!

1. 前言最近,微软开源了一款非常强大的 Python 自动化依赖库:playwright-python它支持主流的浏览器,包含:Chrome、Firefox、Safari、Microsoft Edge 等,同时支持以无头模式、有头模式运行playwright-python 提供了同步…

复杂推理模型从服务器移植到Web浏览器的理论和实战

简介: 随着机器学习的应用面越来越广,能在浏览器中跑模型推理的Javascript框架引擎也越来越多了。在项目中,前端同学可能会找到一些跑在服务端的python算法模型,很想将其直接集成到自己的代码中,以Javascript语言在浏览…

国家网络安全宣传周:勒索病毒利如刀,上网备好技能包

简介: 近年来,勒索病毒携带着日趋成熟的手段革新和愈发隐蔽、复杂的“进化”能力,开启了“重装上阵”的疯狂模式,“出镜率”大有提高。由于大型政企机构的网络资产价值高,就成了勒索病毒的头号“猎物”。 政企机构信息…

漫画:什么是“低代码”开发平台?

作者 | 小灰来源 | 程序员小灰什么是低代码?其实,这并不是最近才出现的新概念。自从计算机诞生以来,人们就一直在想方设法提升计算机程序编写的效率。从最初的机器语言,发展到现在的C、Java、Python等高级语言;从完全独…

OpenKruise 如何实现应用的可用性防护?

简介: OpenKruise 在 2021.9.6 发布了最新的 v0.10.0 版本新增了弹性拓扑管理和应用安全防护等能力,本文将为大家揭晓 OpenKruise 是如何实现应用的可用性防护能力。 前言 OpenKruise 是阿里云开源的云原生应用自动化管理套件,也是当前托管…

Serverless 工程实践 | Serverless 应用优化与调试秘诀

简介: 本文将以阿里云函数计算为例,提供了在线调试、本地调试等多种应用优化与调试方案。 作者|刘宇 前言:本文将以阿里云函数计算为例,提供了在线调试、本地调试等多种应用优化与调试方案。 Serverless 应用调试秘…

新一代容器平台ACK Anywhere,来了

简介: 近日,阿里云容器服务全面升级为ACK Anywhere,让企业在任何需要云的地方,都能获得一致的容器基础设施能力。 5G、AR、AIoT 等场景在推动新一代云架构的演进,而容器重塑了云的使用方式。 近日,阿里云…

高密自智,体小量大,希捷Exos Corvault存储系统为数据洞察赋能

2022年2月24日——全球领先的海量数据存储基础设施解决方案提供商希捷科技(NASDAQ:STX)在线上举办了主题为“高密自智,体小量大”的新一代PB级自修复存储系统——Exos Corvault新品鉴赏会。 此次鉴赏会邀请到了E企研究院首席研究…

Apache Flink 在汽车之家的应用与实践

简介: 汽车之家如何基于 Flink 上线了 AutoStream 平台并持续打磨。 本文整理自汽车之家实时计算平台负责人邸星星在 Flink Forward Asia 2020 分享的议题《Apache Flink 在汽车之家的应用及实践》。主要内容包括: 背景及现状AutoStream 平台基于 Flink …

以色列安全厂商Check Point发布全新logo与重要安全新品,持续深耕中国市场

作者 | 宋慧 出品 | CSDN 云计算 2022年伊始,以色列老牌安全厂商Check Point在自家主办的安全行业大会CPX360上,正式宣布将启用全新企业logo以及企业口号。 Check Point公司中国区总经理陈石磊在对国内媒体分享时指出:“公司1993年成立时&am…

Facebook宕机背后,我们该如何及时发现DNS问题

简介: 国庆期间,Facebook 及其旗下 Instagram 和 WhatsApp 等应用全网宕机,停机时间将近 7 小时 5 分钟,Facebook 市值损失 643 亿美元。针对Facebook的宕机问题,我们该如何未雨绸缪,看看云拨测如何帮助客户…

KubeVela 1.1 发布,开启混合环境应用交付新里程碑

简介: KubeVela 作为一个开箱即用、面向现代微服务架构的应用交付与管理平台,今天正式发布了 1.1 版本,以更加用户友好和完善的功能集,开启了“让混合环境应用交付更加简单高效”的重要里程碑。 在云原生理念迅速普及的今天&…

云原生消息、事件、流超融合平台——RocketMQ 5.0 初探

简介: 今天分享的主题是云原生消息事件流超融合平台 RocketMQ 5.0 初探,内容主要分为三个部分: 首先,带大家回顾业务消息领域首选 RocketMQ 4 发展历史以及 4.x 版本的演进与发展。 其次,会为大家详细介绍 RocketMQ 5.…

mysql查找无根节点sql_SQL 双亲节点查找所有子节点的实现方法

怎么保存树状结构的数据呢?在 SQL 中常用的是双亲节点法。创建表如下CREATE TABLE category ( id LONG, parentId LONG, name String(20) )INSERT INTO category VALUES ( 1, NULL, Root )INSERT INTO category VALUES ( 2, 1, Branch1 )INSERT INTO category VALUE…

一文看懂微服务背后的技术演进与应用实践

简介: 2021年7月2日,阿里云用户组(AUG)第一次线下活动在济南召开。阿里云云原生资深专家李国强结合自身微服务领域经验,现场跟数十家山东企业分享了云原生的代表技术之一“微服务”的演进和应用实践。本文根据作者的现…

1 分钟记住 docker 镜像和容器常用基本命令

作者 | xiaochuhe来源 | CSDN博客镜像常用基本命令查看自己服务器中docker 镜像列表docker images搜索镜像docker search 镜像名 docker search --filterSTARS9000 mysql 搜索 STARS >9000的 mysql 镜像拉取镜像docker pull 镜像名 docker pull 镜像名:tag运行镜像docker ru…

业界首个机密计算容器运行时—Inclavare Containers正式进入CNCF!

简介: Inclavare Containers 通过云原生计算基金会(CNCF)TOC 投票正式成为 CNCF 官方沙箱项目。 作者|彦荣 2021 年 9月 15 日,Inclavare Containers 通过云原生计算基金会(CNCF)TOC 投票正式成…