<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          同事問(wèn)我:為什么我的Service無(wú)法注入進(jìn)來(lái)?

          共 6246字,需瀏覽 13分鐘

           ·

          2022-02-19 23:43

          前言

          同事火急火燎的走了過(guò)來(lái),說(shuō):敖丙快幫我看看這個(gè)錯(cuò)誤,啥情況啊?

          我一看報(bào)錯(cuò):

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

          我其實(shí)已經(jīng)知道是啥情況了,但是怕他不知道,所以還是耐心的跟她解釋了一下,她聽(tīng)完后說(shuō):能不能寫(xiě)下來(lái)啊,免得我下次還會(huì)忘。

          我這么有骨氣的人,想都不用想,于是就有了下文:

          這個(gè)錯(cuò)誤其實(shí)就是這個(gè)Bean在Spring容器中找不到,發(fā)生這種錯(cuò)誤時(shí),常見(jiàn)的有兩種情況:

          1、@ComponentScan注解里的掃描路徑?jīng)]包含這個(gè)類(lèi)

          2、這個(gè)類(lèi)的頭上沒(méi)加@Component注解

          那么問(wèn)題就來(lái)了:為什么@ComponentScan沒(méi)掃描到或者沒(méi)加@Component注解就注入不到Spring容器中?這個(gè)問(wèn)題有點(diǎn)無(wú)厘頭(沒(méi)加@Component注解你還想注入到Spring容器中?)

          我換種問(wèn)法:為什么@ComponentScan掃描到了并且加了@Component注解就能注入到Spring容器中?

          當(dāng)然你可以直接回答:因?yàn)镾pring規(guī)定這樣做的

          當(dāng)然我也會(huì)接著反問(wèn)你:Mybatis的Mapper就沒(méi)用@Component注解,憑啥它就能注入到Spring容器中?

          傻瓜,回答不了了吧?回答不了就趕緊往下看吧~

          問(wèn)題分析

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

          我們首先需要對(duì)問(wèn)題進(jìn)行拆解:

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

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

          回答了這兩個(gè)問(wèn)題我們?cè)龠M(jìn)行猜想:以上過(guò)程是否可以進(jìn)行自定義?如何自定義?否則就沒(méi)有辦法說(shuō)明Mapper是如何注入到Spring容器中的。

          @ComponentScan掃描是做了什么?

          這個(gè)過(guò)程大概是這樣的:Spring通過(guò)掃描指定包下的類(lèi),解析這些類(lèi)的信息,轉(zhuǎn)化成為BeanDefinition,注冊(cè)到beanDefinitionMap中。

          那么這個(gè)過(guò)程的詳情情況又是如何呢?

          我們先來(lái)了解一下這個(gè)過(guò)程中涉及到的角色:

          1、BeanDefinition:Bean定義,內(nèi)含Class的相關(guān)信息

          2、ConfigurationClassPostProcessor:配置類(lèi)處理器,查找配置類(lèi),創(chuàng)建配置類(lèi)解析器

          3、ConfigurationClassParser:配置類(lèi)解析器,解析配置類(lèi),創(chuàng)建@ComponentScan注解解析器

          4、ComponentScanAnnotationParser:@ComponentScan注解解析器,解析@ComponentScan注解,創(chuàng)建Bean定義掃描器

          5、ClassPathBeanDefinitionScanner:Bean定義掃描器,掃描指定包下的所有類(lèi),將符合的類(lèi)轉(zhuǎn)化為BeanDefinition

          6、BeanDefinitionRegistry:BeanDefinition注冊(cè)器,注冊(cè)BeanDefinition

          從上往下看,我們可以輕易的發(fā)現(xiàn),這整個(gè)過(guò)程有一種層層遞進(jìn)的關(guān)系:

          下面我們?cè)賮?lái)看看這些角色的具體職責(zé)。

          1.配置類(lèi)處理器

          配置類(lèi)處理器主要做了3件事

          1、查找配置類(lèi)

          2、創(chuàng)建配置類(lèi)解析器并調(diào)用

          3、加載配置類(lèi)解析器所返回的@Import與@Bean注解的類(lèi)

          1.1查找配置類(lèi)

          你可能會(huì)有疑惑,配置類(lèi)不是我們傳入的嗎?為什么還需要去查找配置類(lèi)呢?

          這是因?yàn)镾pring整個(gè)調(diào)用鏈路十分復(fù)雜,不可能說(shuō)把配置類(lèi)往下層層傳遞,而是一開(kāi)始時(shí)就將配置類(lèi)注冊(cè)到BeanDefinitonMap中了。

          查找配置類(lèi)大致有兩個(gè)過(guò)程:

          1、從BeanFactory中獲取到所有的BeanDefiniton信息

          2、判斷BeanDefiniton是否為配置類(lèi)

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

          而對(duì)于第二點(diǎn),首先我們要知道什么是配置類(lèi)?

          在Spring中,有兩種配置類(lèi):

          1、full類(lèi)型:標(biāo)識(shí)了@Configuration注解的類(lèi)

          2、lite類(lèi)型:標(biāo)識(shí)了@Component @ComponentScan @Import @ImportResource @Bean 注解的類(lèi)(其中之一就行)

          他們唯一的區(qū)別就在于:full類(lèi)型的類(lèi)會(huì)在后置處理步驟中進(jìn)行動(dòng)態(tài)代理

          還記得這個(gè)例子嘛?

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

          問(wèn):Wheel對(duì)象在Spring啟動(dòng)時(shí),被new了幾次?

          答案是一次,因?yàn)镸yConfiguration對(duì)象實(shí)際上會(huì)被進(jìn)行cglib動(dòng)態(tài)代理,所以就算被this.的方式調(diào)用依舊會(huì)觸發(fā)代理邏輯

          只有在這個(gè)情況下是這樣,平常我們進(jìn)行cglib代理時(shí)this調(diào)用依舊直接調(diào)用本類(lèi)方法。

          當(dāng)查找出所有的配置類(lèi)信息之后,緊接著就是創(chuàng)建配置類(lèi)解析器,并將所有的配置類(lèi)交由配置類(lèi)解析器進(jìn)行解析

          1.2流程圖

          2.配置類(lèi)解析器

          配置類(lèi)解析器的職責(zé)如下

          1、判斷該類(lèi)是否應(yīng)該跳過(guò)解析

          2、解析內(nèi)部類(lèi)信息

          3、解析@PropertySources注解信息

          4、解析@ComponentScan注解信息

          5、解析@Import注解信息

          6、解析@Bean注解信息

          2.1判斷該類(lèi)是否應(yīng)該跳過(guò)解析

          所謂判斷類(lèi)是否應(yīng)該跳過(guò)解析,其實(shí)就是判斷類(lèi)是否標(biāo)識(shí)了@Conditional注解并且是否滿(mǎn)足該條件。如果標(biāo)識(shí)了該注解并且不滿(mǎn)足條件,那么則跳過(guò)解析步驟。

          如我們常見(jiàn)的@Profile,@ConditionalOnMissBean等都是由此控制。

          2.2解析內(nèi)部類(lèi)信息

          有時(shí)候我們的配置類(lèi)里面有內(nèi)部類(lèi),并且內(nèi)部類(lèi)也是個(gè)配置類(lèi),那么就需要用此方式進(jìn)行解析。

          2.3解析@ComponentScan注解信息

          該步驟主要是利用**@ComponentScan注解解析器進(jìn)行解析@ComponentScan注解,從而獲取到BeanDefinition列表,再判斷這些BeanDefinition是否是個(gè)配置類(lèi),是則再次調(diào)用配置類(lèi)解析器**進(jìn)行遞歸解析。

          流程圖

          3.@ComponentScan注解解析器

          在該步驟中,Spring會(huì)將我們配置在@ComponentScan注解上的所有信息提取出來(lái),存入到Bean定義掃描器中,再利用Bean定義掃描器得到符合條件的BeanDefiniton。

          excludeFilter和includeFilter用于掃描時(shí)判斷class是否符合要求。

          默認(rèn)的excludeFilter:掃描時(shí)排除掉自己這個(gè)class

          默認(rèn)的includeFilter: 掃描時(shí)判斷該class是否標(biāo)識(shí)@Component注解

          4.Bean定義掃描器

          BeanDefinitionScanner主要做了三件事:

          1、掃描包路徑下的類(lèi)

          2、給BeanDefiniton設(shè)值

          3、使用BeanDefinition注冊(cè)器將BeanDefiniton注冊(cè)到容器中

          4.1掃描包路徑下的類(lèi)

          掃描包路徑的步驟可以簡(jiǎn)單理解為遍歷class文件的過(guò)程,遍歷包下的每個(gè)class,判斷該class是否滿(mǎn)足條件——標(biāo)識(shí)了@Component注解,將滿(mǎn)足條件的class轉(zhuǎn)化為BeanDefiniton,此時(shí)BeanDefiniton只有metedata信息,還沒(méi)有具體設(shè)值。

          4.2給BeanDefiniton設(shè)值

          如果我們?cè)陬?lèi)上加了類(lèi)似這些注解:@Lazy @Primary @DependsOn,那么就需要將這些注解轉(zhuǎn)化為實(shí)際的屬性設(shè)到BeanDefiniton中。

          4.3流程圖

          5.BeanDefinition注冊(cè)器

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

          思考

          現(xiàn)在我們已經(jīng)知道了掃描包的整體過(guò)程,再來(lái)回顧一下這個(gè)問(wèn)題:Mybatis的Mapper是怎么注入到Spring容器中的?

          像這種問(wèn)題咋一看很難理解,常常在面試的情況發(fā)生,因?yàn)槊嬖嚬偈悄弥鸢竼?wèn)問(wèn)題。

          但是我們思考的話,就應(yīng)該換個(gè)角度:怎么才能讓Mapper注冊(cè)到Spring中 -> 怎么才能讓自定義的注解標(biāo)識(shí)的Class注冊(cè)到Spring中?

          不知道這樣問(wèn)是否簡(jiǎn)單些呢?

          方法

          1.使用TypeFilter

          我們知道@Component注解是和默認(rèn)注冊(cè)的IncludeFilter配套使用的,那么同樣我們也可以使用一個(gè)自定義的IncludeFilter與我們的自定義注解配套使用

          自定義Mapper注解

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

          使用Mapper

          @Mapper
          public?class?MyMapper?{

          ?public?void?hello(){
          ??System.out.println("myMapper?hello");
          ?}
          }

          測(cè)試

          添加一個(gè)自定義的IncludeFilter進(jìn)行測(cè)試

          **注意:**此方式只能支持自定義注解標(biāo)識(shí)在實(shí)體類(lèi)的情況,如果將Mapper注解加在接口上,則你會(huì)收獲一個(gè)異常:No bean named 'myMapper' available

          答案很簡(jiǎn)單,因?yàn)榻涌诓荒軐?shí)例化,所以Spring默認(rèn)判斷如果該類(lèi)非實(shí)體類(lèi),則不注冊(cè)到容器中。

          那么我們?cè)趺床拍茏尲恿薓apper注解的接口能注冊(cè)到Spring中呢?

          2.自定義掃描器

          既然Spring的掃描器無(wú)法支持接口,那么我們就重寫(xiě)它——的判斷邏輯。

          開(kāi)源框架擴(kuò)展心得:繼承整體邏輯,重寫(xiě)一小塊邏輯。

          所以我們方式很簡(jiǎn)單:繼承ClassPathBeanDefinitionScanner,重寫(xiě)判斷Class是否符合的邏輯

          public?class?ClassPathMapperScanner?extends?ClassPathBeanDefinitionScanner?{

          ?@Override
          ?protected?boolean?isCandidateComponent(AnnotatedBeanDefinition?beanDefinition)?{
          ????//?重寫(xiě)判斷beanDefinition是否為接口邏輯,改為只有類(lèi)為接口時(shí)才允許注冊(cè)
          ??return?beanDefinition.getMetadata().isInterface()?&&?beanDefinition.getMetadata().isIndependent();
          ?}
          ??//省略構(gòu)造方法
          }

          邏輯已經(jīng)改好了,現(xiàn)在迎來(lái)一個(gè)新問(wèn)題:怎么讓Spring使用它?

          通過(guò)整體流程我們知道,Bean定義掃描器是在**@ComponentScan注解解析器**的解析流程中創(chuàng)建(new)出來(lái)的,我們又不能更改這個(gè)流程,所以, Game Over?

          但,為什么一定要在Spring的掃描流程中使用我們的掃描器呢?我們可以在Spring的掃描流程結(jié)束后,再掃描一遍不就好了嗎?

          還記得有什么方式可以做到這件事嗎?后置處理器!

          3.使用后置處理器

          我們通過(guò)使用BeanDefinitionRegistryPostProcessor,讓Spring的掃描流程結(jié)束之后,進(jìn)行一次后置處理。在后置處理中,創(chuàng)建出自定義的掃描器,進(jìn)行第二次掃描。

          @Component
          public?class?MapperScannerProcessor?implements?BeanDefinitionRegistryPostProcessor?{

          ?@Override
          ?public?void?postProcessBeanDefinitionRegistry(BeanDefinitionRegistry?registry)?throws?BeansException?{
          ????//?創(chuàng)建出自定義的掃描器
          ??ClassPathMapperScanner?classPathMapperScanner?=?new?ClassPathMapperScanner(registry,?false);
          ??//?添加filter,class添加了Mapper注解才注冊(cè)到Spring中
          ??classPathMapperScanner.addIncludeFilter(new?AnnotationTypeFilter(Mapper.class));
          ????//?這里可以改為從外部設(shè)值,不必寫(xiě)死
          ??classPathMapperScanner.scan("com.my.spring.test.custom");
          ?}
          }

          使用這種方式,你會(huì)發(fā)現(xiàn),我們的接口確實(shí)注冊(cè)到BeanDefinitionMap中了。

          但是,你仍然會(huì)收到一個(gè)錯(cuò)誤:Failed to instantiate [com.my.spring.test.custom.InterfaceMapper]: Specified class is an interface

          接口確實(shí)是無(wú)法實(shí)例化的,雖然我們把它注冊(cè)到了Spring中。但Mybatis又是怎么做的呢?

          答案是替換,Mybatis將圖中的beanClass替換成了FactoryBean: MapperFactoryBean,然后將原有的beanClass放入了它的mapperInterface屬性中

          它的getObject方法長(zhǎng)這樣

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

          如果你還記得Mybatis的原始使用方式,應(yīng)該對(duì)這行代碼并不陌生。

          好了,關(guān)于思考的內(nèi)容就到這里,我們只是借用Mybatis的現(xiàn)象進(jìn)行思考,再深入就是Mybatis的內(nèi)容了。

          小結(jié)

          本文借助一個(gè)開(kāi)發(fā)時(shí)常見(jiàn)的問(wèn)題進(jìn)行分析,介紹了Spring的配置類(lèi)解析與掃描過(guò)程,同時(shí),還借助了Mybatis中的現(xiàn)象,思考怎么才能讓自定義的注解標(biāo)識(shí)Class注冊(cè)到Spring中這一問(wèn)題,并使用案例給出了一份較好的答案,希望大家能夠通過(guò)案例更加深入的了解該流程。

          同樣,通過(guò)本次學(xué)習(xí),來(lái)回答一下以下問(wèn)題吧

          1、什么是配置類(lèi)?Spring中有哪幾種配置類(lèi)?有什么區(qū)別?

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

          你是不是心里想,好家伙敖丙還學(xué)會(huì)留可課后作業(yè)了?

          我是敖丙,你知道的越多,你不知道的越多,感謝各位人才的:點(diǎn)贊、收藏評(píng)論,我們下期見(jiàn)!

          瀏覽 43
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  五月丁香色婷婷 | 成人污污网站在线观看 | 青娱乐精品自拍偷拍 | 综合网大香蕉 | 爱爱91N在线观看 |