日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

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

發布時間:2024/8/23 编程问答 42 豆豆
生活随笔 收集整理的這篇文章主要介紹了 啥情况?为什么我的 Service 无法注入进来? 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.


作者 | 敖丙

來源 | 敖丙

今天同事火急火燎的走了過來,說:快幫我看看這個錯誤,啥情況啊?

我一看報錯:

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容器中?

問題分析

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

我們首先需要對問題進行拆解:

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

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

回答了這兩個問題我們再進行猜想:以上過程是否可以進行自定義?如何自定義?否則就沒有辦法說明Mapper是如何注入到Spring容器中的。

@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

從上往下看,我們可以輕易的發現,這整個過程有一種層層遞進的關系:

下面我們再來看看這些角色的具體職責。

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流程圖

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。

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流程圖

5.BeanDefinition注冊器

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

思考

現在我們已經知道了掃描包的整體過程,再來回顧一下這個問題:Mybatis的Mapper是怎么注入到Spring容器中的?

像這種問題咋一看很難理解,常常在面試的情況發生,因為面試官是拿著答案問問題。

但是我們思考的話,就應該換個角度:怎么才能讓Mapper注冊到Spring中 -> 怎么才能讓自定義的注解標識的Class注冊到Spring中?

不知道這樣問是否簡單些呢?

方法

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進行測試

**注意:**此方式只能支持自定義注解標識在實體類的情況,如果將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中了。

但是,你仍然會收到一個錯誤:

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的內容了。

小結

本文借助一個開發時常見的問題進行分析,介紹了Spring的配置類解析與掃描過程,同時,還借助了Mybatis中的現象,思考怎么才能讓自定義的注解標識Class注冊到Spring中這一問題,并使用案例給出了一份較好的答案,希望大家能夠通過案例更加深入的了解該流程。

同樣,通過本次學習,來評論區回答以下問題吧~

1、什么是配置類?Spring中有哪幾種配置類?有什么區別?

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

往期推薦

Android 13 第一個開發者版本來了,網友直呼:Android 12 還沒玩透!

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

使用這個庫,讓你的服務操作 Redis 速度飛起

使用 Cilium 增強 Kubernetes 網絡安全

點分享

點收藏

點點贊

點在看

總結

以上是生活随笔為你收集整理的啥情况?为什么我的 Service 无法注入进来?的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。