<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>

          面試被問SpringBoot自動裝配原理,怎么破?

          共 20336字,需瀏覽 41分鐘

           ·

          2022-05-21 12:44

          往期熱門文章:

          1、五個刁鉆的String面試問題及解答

          2、IntelliJ平臺將完全停止使用Log4j

          3、神操作!我把 3000 行代碼重構(gòu)成 15 行!

          4、我用Java幾分鐘處理完30億個數(shù)據(jù)...

          5、一款自動生成單元測試的 IDEA 插件

          來源:blog.csdn.net/weixin_43826242?type=blog

          前言

          學(xué)習(xí)SpringBoot,絕對避不開自動裝配這個概念,這也是SpringBoot的關(guān)鍵之一

          本人也是SpringBoot的初學(xué)者,下面的一些總結(jié)都是結(jié)合個人理解和實踐得出的,如果有錯誤或者疏漏,請一定一定一定(不是歡迎,是一定)幫我指出,在評論區(qū)回復(fù)即可,一起學(xué)習(xí)!

          篇幅較長,希望你可以有耐心.

          如果只關(guān)心SpringBoot裝配過程,可以直接跳到第7部分

          想要理解spring自動裝配,需要明確兩個含義:

          • 裝配,裝配什么?

          • 自動,怎么自動?

          1. Warm up

          在開始之前,讓我們先來看點簡單的開胃菜:spring中bean注入的三種形式

          首先我們先來一個Person類,這里為了篇幅長度考慮使用了lombok

          如果你不知道lombok是什么,那就最好不要知道,加了幾個注解之后我的pojo類Person就完成了

          /**
          ?*?@author?dzzhyk
          ?*/
          @Data
          @NoArgsConstructor
          @AllArgsConstructor
          public?class?Person?{
          ????private?String?name;
          ????private?Integer?age;
          ????private?Boolean?sex;
          }

          在Spring中(不是Spring Boot),要實現(xiàn)bean的注入,我們有3種注入方式:

          1.1 setter注入

          這是最基本的注入方式

          首先我們創(chuàng)建applicationContext.xml文件,在里面加入:


          "person"?class="pojo.Person">
          ????"name"?value="dzzhyk"/>
          ????"age"?value="20"/>
          ????"sex"?value="true"/>

          這里使用property為bean對象賦值

          緊接著我們會在test包下寫一個version1.TestVersion1類

          /**
          ?*?第一種bean注入實現(xiàn)方式?-?在xml文件中直接配置屬性
          ?*/
          public?class?TestVersion1?{
          ????@Test
          ????public?void?test(){
          ????????ApplicationContext?ca?=?new???ClassPathXmlApplicationContext("applicationContext.xml");
          ????????Person?person?=?ca.getBean("person",?Person.class);
          ????????System.out.println(person);
          ????}
          }

          這里我使用了ClassPathXmlApplicationContext來加載spring配置文件并且讀取其中定義的bean,然后使用getBean方法使用id和類來獲取這個Person的Bean對象,結(jié)果成功輸出:

          Person(name=dzzhyk,?age=20,?sex=true)

          1.2 構(gòu)造器注入

          接下來是使用構(gòu)造器注入,我們需要更改applicationContext.xml文件中的property為construct-arg


          "person"?class="pojo.Person">
          ????"0"?type="java.lang.String"?value="dzzhyk"?/>
          ????"1"?type="java.lang.Integer"?value="20"/>
          ????"2"?type="java.lang.Boolean"?value="true"/>

          version2.TestVersion2內(nèi)容不變:

          public?class?TestVersion2?{
          ????@Test
          ????public?void?test(){
          ????????ApplicationContext?ca?=?new?ClassPathXmlApplicationContext("applicationContext.xml");
          ????????Person?person?=?ca.getBean("person",?Person.class);
          ????????System.out.println(person);
          ????}
          }

          依然正常輸出結(jié)果:

          Person(name=dzzhyk,?age=20,?sex=true)

          1.3 屬性注入

          使用注解方式的屬性注入Bean是比較優(yōu)雅的做法

          首先我們需要在applicationContext.xml中開啟注解支持和自動包掃描:


          "pojo"/>

          在pojo類中對Person類加上@Component注解,將其標(biāo)記為組件,并且使用@Value注解為各屬性賦初值

          @Component
          public?class?Person?{
          ????
          ????@Value("dzzhyk")
          ????private?String?name;
          ????@Value("20")
          ????private?Integer?age;
          ????@Value("true")
          ????private?Boolean?sex;
          }

          然后添加新的測試類version3.TestVersion3

          public?class?TestVersion3?{
          ????@Test
          ????public?void?test(){
          ????????ApplicationContext?ac?=?new?ClassPathXmlApplicationContext("applicationContext.xml");
          ????????Person?person?=?ac.getBean("person",?Person.class);
          ????????System.out.println(person);
          ????}
          }

          運行也可以得到如下結(jié)果:

          Person(name=dzzhyk,?age=20,?sex=true)

          2. Warm up again

          什么?還有什么?接下來我們來聊聊Spring的兩種配置方式:基于XML的配置和基于JavaConfig類的配置方式,這對于理解SpringBoot的自動裝配原理是非常重要的。

          首先我們在Person的基礎(chǔ)上再創(chuàng)建幾個pojo類:這個Person有Car、有Dog

          public?class?Car?{
          ????private?String?brand;
          ????private?Integer?price;
          }

          public?class?Dog?{
          ????private?String?name;
          ????private?Integer?age;
          }

          public?class?Person?{
          ????private?String?name;
          ????private?Integer?age;
          ????private?Boolean?sex;
          ????private?Dog?dog;
          ????private?Car?car;
          }

          2.1 基于XML的配置

          接下來讓我們嘗試使用XML的配置方式來為一個Person注入

          "person"?class="pojo.Person">
          ????"name"?value="dzzhyk"/>
          ????"age"?value="20"/>
          ????"sex"?value="true"/>
          ????"dog"?ref="dog"/>
          ????"car"?ref="car"/>


          "dog"?class="pojo.Dog">
          ????"name"?value="旺財"/>
          ????"age"?value="5"?/>


          "car"?class="pojo.Car">
          ????"brand"?value="奧迪雙鉆"/>
          ????"price"?value="100000"/>

          然后跟普通的Bean注入一樣,使用ClassPathXmlApplicationContext來加載配置文件,然后獲取Bean

          /**
          ?*?使用XML配置
          ?*/
          public?class?TestVersion1?{
          ????@Test
          ????public?void?test(){
          ????????ClassPathXmlApplicationContext?ca?=?new?ClassPathXmlApplicationContext("applicationContext.xml");
          ????????Person?person?=?ca.getBean("person",?Person.class);
          ????????System.out.println(person);
          ????}
          }

          輸出結(jié)果如下:

          Person(name=dzzhyk,?age=20,?sex=true,?dog=Dog(name=旺財,?age=5),?car=Car(brand=奧迪雙鉆,?price=100000))

          2.2 基于JavaConfig類的配置

          想要成為JavaConfig類,需要使用@Configuration注解

          我們新建一個包命名為config,在config中新增一個PersonConfig類

          @Configuration
          @ComponentScan
          public?class?PersonConfig?{

          ????@Bean
          ????public?Person?person(Dog?dog,?Car?car){
          ????????return?new?Person("dzzhyk",?20,?true,?dog,?car);
          ????}

          ????@Bean
          ????public?Dog?dog(){
          ????????return?new?Dog("旺財",?5);
          ????}

          ????@Bean
          ????public?Car?car(){
          ????????return?new?Car("奧迪雙鉆",?100000);
          ????}
          }

          此時我們的XML配置文件可以完全為空了,此時應(yīng)該使用AnnotationConfigApplicationContext來獲取注解配置

          /**
          ?*?使用JavaConfig配置
          ?*/
          public?class?TestVersion2?{
          ????@Test
          ????public?void?test(){
          ????????AnnotationConfigApplicationContext?ac?=?new?AnnotationConfigApplicationContext(PersonConfig.class);
          ????????Person?person?=?ac.getBean("person",?Person.class);
          ????????System.out.println(person);
          ????}
          }

          仍然正常輸出了結(jié)果:

          Person(name=dzzhyk,?age=20,?sex=true,?dog=Dog(name=旺財,?age=5),?car=Car(brand=奧迪雙鉆,?price=100000))

          3. BeanDefinition

          AbstractBeanDefinition

          是spring中所有bean的抽象定義對象,我把他叫做bean定義

          當(dāng)bean.class被JVM類加載到內(nèi)存中時,會被spring掃描到一個map容器中:

          BeanDefinitionMap

          這個容器存儲了bean定義,但是bean此時還沒有進行實例化,在進行實例化之前,還有一個

          BeanFactoryPostProcessor

          可以對bean對象進行一些自定義處理

          我們打開BeanFactoryProcessor這個接口的源碼可以發(fā)現(xiàn)如下內(nèi)容:

          /*
          *?Modify?the?application?context's?internal?bean?factory?after?its?standard
          *?initialization.?All?bean?definitions?will?have?been?loaded,?but?no?beans
          *?will?have?been?instantiated?yet.?This?allows?for?overriding?or?adding
          *?properties?even?to?eager-initializing?beans.
          */

          在spring完成標(biāo)準(zhǔn)的初始化過程后,實現(xiàn)BeanFactoryPostProcessor接口的對象可以用于定制bean factory,所有的bean definition都會被加載,但是此時還沒有被實例化。這個接口允許對一些bean定義做出屬性上的改動。

          簡言之就是實現(xiàn)了BeanFactoryPostProcessor這個接口的類,可以在bean實例化之前完成一些對bean的改動。

          大致流程我畫了個圖:

          至此我們能總結(jié)出springIOC容器的本質(zhì):(我的理解)

          由BeanDefinitionMap、BeanFactoryPostProcessor、BeanPostProcessor、BeanMap等等容器共同組成、共同完成、提供依賴注入和控制反轉(zhuǎn)功能的一組集合,叫IOC容器。

          4. BeanDefinition結(jié)構(gòu)

          既然講到了BeanDefinition,我們來看一下BeanDefinition里面究竟定義了些什么

          讓我們點進AbstractBeanDefinition這個類,一探究竟:

          哇!好多成員變量,整個人都要看暈了@_@

          我們來重點關(guān)注以下三個成員:

          ??private?volatile?Object?beanClass;
          ??private?int?autowireMode?=?AUTOWIRE_NO;
          ??private?ConstructorArgumentValues?constructorArgumentValues;

          4.1 beanClass

          這個屬性決定了該Bean定義的真正class到底是誰,接下來我們來做點實驗

          我們定義兩個Bean類,A和B

          @Component
          public?class?A?{
          ????@Value("我是AAA")
          ????private?String?name;
          }
          @Component
          public?class?B?{
          ????@Value("我是BBB")
          ????private?String?name;
          }

          接下來我們實現(xiàn)上面的BeanFactoryPostProcessor接口,來創(chuàng)建一個自定義的bean后置處理器

          /**
          ?*?自定義的bean后置處理器
          ?*?通過這個MyBeanPostProcessor來修改bean定義的屬性
          ?*?@author?dzzhyk
          ?*/
          public?class?MyBeanPostProcessor?implements?BeanFactoryPostProcessor?{
          ????@Override
          ????public?void?postProcessBeanFactory(ConfigurableListableBeanFactory?beanFactory)?throws?BeansException?{
          ????????GenericBeanDefinition?defA?=?(GenericBeanDefinition)?beanFactory.getBeanDefinition("a");
          ????????System.out.println("這里是MyBeanPostProcessor,我拿到了:"?+?defA.getBeanClassName());
          ????}
          }

          最后在XML配置文件中開啟包掃描

          "pojo"/>

          注意:這里不要使用JavaConfig類來配置bean,不然會報如下錯誤

          ConfigurationClassBeanDefinition?cannot?be?cast?to?org.springframework.beans.factory.support.GenericBeanDefinition

          這個錯誤出自這一句:

          GenericBeanDefinition?defA?=?(GenericBeanDefinition)?beanFactory.getBeanDefinition("a");

          最后,我們創(chuàng)建一個測試類:

          public?class?Test?{
          [email protected]
          ????public?void?test(){
          ????????ClassPathXmlApplicationContext?ca?=?new?ClassPathXmlApplicationContext("applicationContext.xml");
          ????????A?aaa?=?ca.getBean("a",?A.class);
          ????????System.out.println("最終拿到了==>?"?+?aaa);
          ????}
          }

          測試運行!

          這里是MyBeanPostProcessor,我拿到了:pojo.A
          最終拿到了==>?A(name=我是AAA,?b=B(name=我是BBB))

          可以看到MyBeanPostProcessor成功拿到了A的Bean定義,并且輸出了提示信息

          接下來讓我們做點壞事

          我們在MyBeanPostProcessor中修改A的Bean對象,將A的beanClass修改為B.class

          System.out.println("這里是MyBeanPostProcessor,我修改了:"+?defA.getBeanClassName()?+?"?的class為?B.class");
          //?把A的class改成B
          defA.setBeanClass(B.class);

          重新運行Test類,輸出了一些信息后:報錯了!

          這里是MyBeanPostProcessor,我拿到了:pojo.A
          這里是MyBeanPostProcessor,我修改了:pojo.A 的class為 B.class

          BeanNotOfRequiredTypeException:?
          ?Bean?named?'a'?is?expected?to?be?of?type?'pojo.A'?but?was?actually?of?type?'pojo.B'

          我要拿到一個A類對象,你怎么給我一個B類對象呢?這明顯不對

          綜上所述,我們可以得出beanClass屬性控制bean定義的類

          4.2 autowireMode

          我們繼續(xù)看第二個屬性:autowireMode,自動裝配模式

          我們在AbstractBeanDefinition源碼中可以看到:

          private?int?autowireMode?=?AUTOWIRE_NO;

          自動裝配模式默認(rèn)是AUTOWIRE_NO,就是不開啟自動裝配

          可選的常量值有以下四種:不自動裝配,通過名稱裝配,通過類型裝配,通過構(gòu)造器裝配

          • AUTOWIRE_NO

          • AUTOWIRE_BY_NAME

          • AUTOWIRE_BY_TYPE

          • AUTOWIRE_CONSTRUCTOR

          接下來我們來模擬一個自動裝配場景,仍然是A和B兩個類,現(xiàn)在在A類中添加B類對象

          @Component
          public?class?A?{
          ????@Value("我是AAA")
          ????private?String?name;
          ????@Autowired
          ????private?B?b;
          }

          我們希望b對象能夠自動裝配,于是我們給他加上了@Autowired注解,其他的完全不變,我們自定義的MyBeanPostProcessor中也不做任何操作,讓我們運行測試類:

          這里是MyBeanPostProcessor,我拿到了:pojo.A
          最終拿到了==>?A(name=我是AAA,?b=B(name=我是BBB))

          自動裝配成功了!我們拿到的A類對象里面成功注入了B類對象b

          現(xiàn)在問題來了,如果我把@Autowired注解去掉,自動裝配會成功嗎?

          這里是MyBeanPostProcessor,我拿到了:pojo.A
          最終拿到了==>?A(name=我是AAA,?b=null)

          必然是不成功的

          但是我就是想要不加@Autowired注解,仍然可以實現(xiàn)自動裝配,需要怎么做?

          這時就要在我們的MyBeanPostProcessor中做文章了,加入如下內(nèi)容:

          defA.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_NAME);

          再輸出結(jié)果:

          這里是MyBeanPostProcessor,我拿到了:pojo.A
          最終拿到了==>?A(name=我是AAA,?b=B(name=我是BBB))

          自動裝配成功了!這次我們可沒加@Autowired,在我們的自定義的bean后置處理器中設(shè)置了autowireMode屬性,也實現(xiàn)了自動裝配

          綜上,autowireMode屬性是用來控制自動裝配模式的,默認(rèn)值是AUTOWIRE_NO,即不自動裝配

          4.3 constructorArgumentValues

          constructorArgumentValues的字面含義是構(gòu)造器參數(shù)值

          改變這個參數(shù)值,我們可以做到在實例化對象時指定特定的構(gòu)造器

          話不多說,show me your code:

          因為要研究構(gòu)造器,只能先”忍痛“關(guān)掉lombok插件,手寫一個pojo.Student類

          /**
          ?*?Student類
          ?*?@author?dzzhyk
          ?*/
          @Component
          public?class?Student?{
          ????private?String?name;
          ????private?Integer?age;

          ????public?Student()?{
          ????????System.out.println("==>使用空參構(gòu)造器?Student()");
          ????}

          ????public?Student(String?name,?Integer?age)?{
          ????????this.name?=?name;
          ????????this.age?=?age;
          ????????System.out.println("==>使用雙參數(shù)構(gòu)造器?Student(String?name,?Integer?age)");
          ????}
          }

          我們都知道,spring在實例化對象時使用的是對象的默認(rèn)空參構(gòu)造器:

          我們新建一個測試方法test

          @Test
          public?void?test(){
          ????ApplicationContext?ca?=?new?ClassPathXmlApplicationContext("applicationContext.xml");
          ????Student?student?=?ca.getBean("stu",?Student.class);
          ????System.out.println("==>"?+?student);
          }

          運行可以得到下面結(jié)果:

          這里是MyBeanPostProcessor,我拿到了:pojo.Student
          ==>使用空參構(gòu)造器?Student()
          ==>pojo.Student@402e37bc

          可以看到,確實使用了空參構(gòu)造器

          但是如何指定(自定義)使用哪個構(gòu)造器呢?我根本看不見摸不著,Spring全幫我做了,實在是太貼心了。

          接下來就聊聊constructorArgumentValues的使用:

          我們在MyBeanPostProcessor中加入如下內(nèi)容,對獲取到的pojo.Student的bean定義進行操作:

          ConstructorArgumentValues?args?=?new?ConstructorArgumentValues();
          args.addIndexedArgumentValue(0,?"我指定的姓名");
          args.addIndexedArgumentValue(1,?20);
          defStu.setConstructorArgumentValues(args);

          再次運行test:

          這里是MyBeanPostProcessor,我拿到了:pojo.Student
          ==>使用雙參數(shù)構(gòu)造器?Student(String?name,?Integer?age)
          ==>pojo.Student@2f177a4b

          可以看到這次使用了雙參數(shù)構(gòu)造器

          有人會好奇ConstructorArgumentValues到底是個什么東西,我點進源碼研究一番,結(jié)果發(fā)現(xiàn)這個類就是一個普通的包裝類,包裝的對象是ValueHolder,里面一個List一個Map

          而ValueHolder這個對象繼承于BeanMetadataElement,就是構(gòu)造器參數(shù)的一個包裝類型

          通過這個例子我們可以看到ConstructorArgumentValues就是用來管控構(gòu)造器參數(shù)的,指定這個值會在進行bean注入的時候選擇合適的構(gòu)造器。

          5. 裝配對象

          現(xiàn)在我們把目光放回到SpringBoot的自動裝配上來,原來在真正進行bean實例化對象前,我們前面還有這些過程,尤其是存在使用后置處理器BeanFactoryPostProcessor來對bean定義進行各種自定義修改的操作。

          經(jīng)過上面我們漫長的研究過程,我們終于可以回答第一個問題了:

          自動裝配的對象:Bean定義 (BeanDefinition)

          6. My自動裝配

          看到這里又自然會產(chǎn)生疑問:不會吧,上面可都是自動裝配啊,我在配置文件或者使用注解都配置了變量的值,然后加個@Autowired注解就OK了,spring也是幫我自動去裝配。

          再高端一點話,我就把XML文件寫成JavaConfig配置類,然后使用@Configuration注解,這樣也能自動裝配,這不是很nice了嗎?

          6.1 自動裝配之再思考

          我的理解,上面的自動裝配,我們至少要寫一個配置文件,無論是什么形式,我們都至少需要一個文件把它全部寫下來,就算這個文件的內(nèi)容是固定的,但是為了裝配這個對象,我們不得不寫。

          我們甚至都可以做成模板了,比如我在學(xué)習(xí)spring框架整合時,把經(jīng)常寫的都搞成了模板:

          有了這些模板,我們只需要點點點,再進行修改,就能用了。

          這樣做確實很好,可是對于越來越成型的項目體系,我們每次都搞一些重復(fù)動作,是會厭煩的。而且面對這么多xml配置文件,我太難了。

          于是我有了一個想說但不敢說的問題:

          我一個配置文件都不想寫,程序還能照樣跑,我只關(guān)心有我需要的組件就可以了,我只需要關(guān)注我的目標(biāo)就可以了,我想打開一個工程之后可以1秒進入開發(fā)狀態(tài),而不是花3小時寫完配置文件(2.5小時找bug)希望有個東西幫我把開始之前的準(zhǔn)備工作全做了,即那些套路化的配置,這樣在我接完水之后回來就可以直接進行開發(fā)。

          說到這里,想必大家都懂了:SpringBoot

          6.2 一個例子

          讓我們在偷懶的道路上繼續(xù)前進。

          來看下面這個例子:

          仍然是A類和B類,其中A類仍然引用了B類,我們給A類組件起id=“a”,B類組件起id=“b”

          @Component("a")
          public?class?A?{
          ????@Value("我是AAA")
          ????private?String?name;
          ????@Autowired
          ????private?B?b;
          }

          @Component("b")
          public?class?B?{
          ????@Value("我是BBB")
          ????private?String?name;
          }

          可以看到我們使用了@Autowired注解來自動注入b,測試類如下:

          @Test
          public?void?test(){
          AnnotationConfigApplicationContext?ac?=?new?AnnotationConfigApplicationContext(MyAutoConfig.class);
          A?aaa?=?ac.getBean("a",?A.class);
          System.out.println(aaa);
          }

          細心的同學(xué)已經(jīng)發(fā)現(xiàn)了:我們這里使用了AnnotationConfigApplicationContext這個JavaConfig配置類會使用到的加載類,于是我們順利成章地點開它所加載的MyAutoConfig類文件

          文件內(nèi)容如下:

          @Configuration
          @MyEnableAutoConfig
          public?class?MyAutoConfig?{
          ????//?bean?都去哪了????
          }

          what? 我要聲明的Bean對象都去哪了(注意:這里的applicationContext.xml是空的)?

          讓我們運行test:

          A(name=我是AAA,?b=B(name=我是BBB))

          竟然運行成功了,這究竟是為什么?(元芳,你怎么看?)

          細心的同學(xué)已經(jīng)發(fā)現(xiàn)了:@MyEnableAutoConfig是什么注解?我怎么沒有這個注解

          讓我們點進@MyEnableAutoConfig一探究竟:

          @Target(ElementType.TYPE)
          @Retention(RetentionPolicy.RUNTIME)
          @Documented
          @Import(MyImportSelector.class)???//?導(dǎo)入bean定義
          public?@interface?MyEnableAutoConfig?{
          }

          原來如此!你是用了@Import注解導(dǎo)入了Bean定義對吧,注釋都寫著呢!

          可是客官,@Import導(dǎo)入bean定義是沒錯,但是它導(dǎo)入的是MyImportSelector這個bean,不是A也不是B啊…

          6.3 @Import注解

          @Import的功能就是獲取某個類的bean對象,他的使用形式大致如下:

          @Import(A.class)
          @Import(MyImportBeanDefinitionRegister.class)
          @Import(MyImportSelector.class)

          6.3.1 @Import(A.class)

          第一種形式@Import(A.class),是最簡單易懂的形式

          我們需要哪個Bean定義,直接Import他的class即可

          6.3.2 @Import(MyImportBeanDefinitionRegister.class)

          第二種形式@Import(MyImportBeanDefinitionRegister.class)

          傳遞了一個bean定義注冊器,這個注冊器的具體內(nèi)容如下:

          public?class?MyImportBeanDefinitionRegister?implements?ImportBeanDefinitionRegistrar?{
          ????@Override
          ????public?void?registerBeanDefinitions(AnnotationMetadata?importingClassMetadata,?BeanDefinitionRegistry?registry)?{
          ????????
          ????????RootBeanDefinition?aDef?=?new?RootBeanDefinition(A.class);
          ????????registry.registerBeanDefinition("a",?aDef);
          ????????
          ????}
          }

          這個注冊器實現(xiàn)了ImportBeanDefinitionRegistrar接口,并且重寫了里面的registerBeanDefinitions方法

          看他做了什么事:創(chuàng)建了一個新的bean定義,他的類型就是A,然后把這個bean定義注冊到BeanDefinitionMap(還記得吧!)里面,key值我們可以人為設(shè)置,這里就設(shè)置成"a"

          這樣在傳遞一個注冊器的時候,我們就可以把注冊器中新增的bean定義注冊進來使用

          6.3.3 @Import(MyImportSelector.class)

          可以看到,這種使用方式就是我們剛才的注解中使用的方式

          他傳遞了一個叫MyImportSelector的類,這個類依然是我們自己定義的,具體內(nèi)容如下:

          public?class?MyImportSelector?implements?ImportSelector?{
          ????@Override
          ????public?String[]?selectImports(AnnotationMetadata?importingClassMetadata)?{
          ????????//?導(dǎo)入配置類
          ????????return?new?String[]{"config.MyConfig"};
          ????}
          }

          這個類實現(xiàn)了ImportSelector接口,并且重寫了selectImports方法,返回一個字符串?dāng)?shù)組

          我們可以看到,返回的字符串?dāng)?shù)組中是我們要導(dǎo)入類的全類名

          這個Importer返回的類如果是組件bean對象,就會被加載進來使用;如果是一個配置類,就會加載這個配置類

          第三種和第二種的區(qū)別是第三種可以一次性寫很多類,而且比較簡潔,只需要清楚類的全包名即可。而第二種方式需要自己清楚包類名,手動創(chuàng)建bean定義,然后手動加入BeanDefinitionMap。

          6.4 例子的研究

          我們打開MyImportSelector,發(fā)現(xiàn)里面赫然寫著幾個大字:

          return?new?String[]{"config.MyConfig"};

          然后我們找到config.MyConfig類,發(fā)現(xiàn)這個類竟然就是我們剛才寫的JavaConfig版本的配置文件:

          @Configuration
          public?class?MyConfig?{
          ????@Bean
          ????public?A?a(){
          ????????return?new?A();
          ????}

          ????@Bean
          ????public?B?b(){
          ????????return?new?B();
          ????}
          }

          加載這個MyConfig配置類,就相當(dāng)于加載了A和B兩個Bean定義

          喂!你是不是搞我!繞了一大圈,怎么還是加載這個配置文件啊!這個配置文件明明就是我自己寫的。

          總結(jié)一下,我們這個例子大概繞了這些過程:

          6.5 將偷懶進行到底

          "沒有會偷懶的人解決不掉的問題“ —— 魯迅

          上面的例子也沒有多大優(yōu)化啊,我怎么覺得更加麻煩了?不但繞了一大圈,定義了許多新東西,到最后還是加載了我寫好的JavaConfig類,說到底我不是還在寫javaConfig類嗎…

          但是你注意到?jīng)]有:有了上面的機制,我只需要把JavaConfig類寫一次,然后放在某個地方,在MyImportSelector中加入這個地方的全包名路徑,下次用的時候直接導(dǎo)入最頂層的MyAutoConfig類,所有有關(guān)這個部件我需要的東西,就全部自動整理好了,甚至比鼠標(biāo)點點點添加代碼模板還要快!

          我突然有了個很棒的想法,不知道你有了沒有 。

          如果你開始有點感覺了,就會自然提出另一個問題:我這樣做確實可以提高效率,但是一段代碼里寫入我自己定制的內(nèi)容,每次更改起來不是太費勁了嗎?

          想到這里,我就不禁回想起使用JDBC的時候,在代碼里改SQL語句的痛苦了,那真是生不如死…這種情況就構(gòu)成了硬編碼的行為,是不好的。

          我們自然會想到:要是我創(chuàng)建一個配置文件properties來專門保存我這個需求所使用的bean對象,然后使用的時候在MyImportSelector中讀取配置文件并且返回全包名,不就更加nice了嗎?

          于是MyImportSelector中的代碼又改成了下面這樣:

          public?class?MyImportSelector?implements?ImportSelector?{
          ????@Override
          ????public?String[]?selectImports(AnnotationMetadata?importingClassMetadata)?{
          ????
          ????????Properties?properties?=?MyPropertyReader.readPropertyForMe("/MyProperty.properties");
          ????????String?strings?=?(String)?properties.get(MyEnableAutoConfig.class.getName());
          ???????
          ????????return?new?String[]{strings};
          ????}
          }

          其中MyPropertyReader是我們自己新創(chuàng)建的用于讀取properties文件的工具類

          之所以要自己再定義這樣一個工具類,是為了以后在其中可以做一些其他操作(比如:去重、預(yù)檢查)

          public?class?MyPropertyReader?{
          ????public?static?Properties?readPropertyForMe(String?path){
          ????????Properties?properties?=?new?Properties();
          ????????try(InputStream?sin?=?MyPropertyReader.class.getResourceAsStream(path)){
          ????????????properties.load(sin);
          ????????}catch?(IOException?e){
          ????????????e.printStackTrace();
          ????????????System.out.println("讀取異常...");
          ????????}
          ????????return?properties;
          ????}
          }

          我們的配置文件里面這么寫:

          anno.MyEnableAutoConfig=config.MyConfig

          可以看到,key是注解@MyEnableAutoConfig的類名,也就是根據(jù)這個注解,就會導(dǎo)入后面的MyConfig這個Bean,這個Bean就是我們的配置文件

          如此一來我們讀取這個配置文件,然后加載跟這個注解名稱相符的value(即JavaConfig配置文件),就相當(dāng)于我們在代碼里手寫的"config.MyConfig",只不過現(xiàn)在的形式已經(jīng)發(fā)生了巨大的變化:我們添加或者刪除一個配件,完全只需要修改MyProperty.properties這個配置文件就行了!

          至此,無論是添加或者刪除組件,無非是在配置文件中加上或者刪除一行的問題了。

          讓我們在更新之后運行程序,可以看到成功拿到了配置文件的全類名

          程序的運行當(dāng)然也是沒問題的:

          A(name=我是AAA,?b=B(name=我是BBB))

          到此,我仿佛又領(lǐng)悟了一些東西。。。

          我的配置文件好像活了,在我需要的時候他會出現(xiàn),在我不需要的時候只需要在配置文件里面給他”打個叉“,他自己就跑開了

          7. 自動裝配源碼分析

          終于來到了大家喜聞樂見的部分:源碼分析

          在我們前面6節(jié)學(xué)習(xí)了各種”招式“之后,讓我們請出對手:SpringBoot

          現(xiàn)在在你面前的是一個SpringBoot”空項目“,沒有添加任何依賴包和starter包

          啟動項目:

          正常啟動,讓我們從@SpringBootApplication開始研究

          7.1 @SpringBootConfiguration

          會看到@SpringBootApplication這個注解由好多注解組成

          主要的有以下三個:

          @SpringBootConfiguration
          @EnableAutoConfiguration
          @ComponentScan

          先來看第一個:@SpringBootConfiguration

          進入這個注解之后會發(fā)現(xiàn)

          原來你就是一個@Configuration啊,一個JavaConfig配置類

          那我們使用JavaConfig不就是用來配置bean嗎,所以有了這個注解之后我們可以在SpringBoot運行的主類中使用@Bean標(biāo)簽配置類了,如下圖所示:

          7.2 @ComponentScan

          這個注解相信大家都認(rèn)識了,組件掃描

          這個掃描的范圍是:SpringBoot主啟動類的同級路徑及子路徑

          7.3 @EnableAutoConfiguration

          來看這個注解,也是最核心的內(nèi)容

          這個注解怎么這么眼熟啊,還記得剛才的@MyEnableAutoConfig注解嗎?就是我們自己寫的那個注解

          進入@EnableAutoConfiguration:

          看圖中紅圈位置的注解:@Import(AutoConfigurationImportSelector.class)

          是不是跟我們上面自己寫的內(nèi)容一樣!

          這里的作用便是導(dǎo)入了 AutoConfigurationImportSelector 這個類的bean定義

          我們都知道,如果這個類實現(xiàn)了ImportSelector接口,那他肯定重寫了一個方法,就是我們上面重寫過的selectImports方法:

          果然,在這個類里面確實有這個selectImports方法:

          我的天,好長的一串代碼,一行都放不下!

          此時此刻,我又回想起了在家鄉(xiāng)的母親,夏天的蟬鳴,池塘的荷花…

          等等等等,這個類我們當(dāng)時返回的是什么?是一個字符串?dāng)?shù)組String[ ],那這個類無論多么長,返回的肯定就是一個字符串?dāng)?shù)組,不信你自己看:

          這個字符串?dāng)?shù)組存放的內(nèi)容我們是否清楚呢?當(dāng)然清楚了!我們返回的是要加載的Config配置文件的全包名,通過返回這個全包名,我們就能自動裝配上這些配置文件下定義的bean對象,從而達到了自動裝配的目的!

          根據(jù)剛才我們自己實現(xiàn)的selectImports方法,我們是通過注解類的名字來查找,并且最終得到需要加載的Config類的全類名,最后返回的。

          因此,這里必然有一個根據(jù)注解類名字來查找相應(yīng)的Config文件的操作

          我們繼續(xù)反推,看到返回時的定義如下:

          我們發(fā)現(xiàn)autoConfigurationEntry中保存著我們需要的配置信息,它是通過getAutoConfigurationEntry方法獲取的,于是我們繼續(xù)深入,進入getAutoConfigurationEntry方法

          這一段代碼真是把人難住了,好大一片,不知道在做什么

          此時此刻,我又回想起了在家鄉(xiāng)的母親,夏天的蟬鳴,池塘的荷花…

          回家!有了!我們先想這個方法應(yīng)該返回什么,根據(jù)我們前面的經(jīng)驗,這里應(yīng)該返回一個類似于Entry的保存了我們需要的配置信息的對象

          這個方法返回的是新建的AutoConfigurationEntry對象,根據(jù)最后一行的構(gòu)造函數(shù)來看,給他了兩個參數(shù):

          configurations,?exclusions

          configurations顯然使我們需要的配置文件,也是我們最關(guān)心的,而exclusions字面意思是排除,也就是不需要的,那我們接下來應(yīng)該關(guān)注configurations到底是怎么來的

          根據(jù)我們前面的經(jīng)驗,我們是根據(jù)注解類名來從一個配置文件中讀取出我們需要的Config配置類,這里configurations就代表了Config配置類,那么我們應(yīng)該找到一個入口,這個入口跟注解相關(guān),并且返回了configurations這個參數(shù)。

          正如我們所料,這個方法的參數(shù)確實傳遞過來了一個東西,跟注解有關(guān):

          看見那個大大的Annotation(注解)了嗎!

          那么根據(jù)這條”線索“,我們按圖索驥,找到了三行代碼,范圍進一步縮小了!

          此時再加上返回了configurations,我們最終確定了一行代碼:

          就是這個getCandidateConfigurations方法,符合我們的要求!

          從字面意思上分析,獲取候選的配置,確實是我們需要的方法

          OK,讓我們繼續(xù)前進,進入這個方法:

          這個方法是不是也似曾相識呢?我們之前寫過一個專門用于讀取配置文件的類MyPropertyReader,還記得嗎?

          如果你還記得的話,我們自己寫的工具類里面也是一個靜態(tài)方法readPropertyForMe來幫我讀取配置文件

          但是我們的配置文件路徑一定是需要指定的,不能亂放。

          從這個loadFactoryNames方法體來看,好像沒有給他傳遞一個具體路徑

          但是從下面的Assert斷言中,我們發(fā)現(xiàn)了玄機:

          在META-INF/spring.factories文件中沒有找到自動配置類Config,你要檢查balabala。。。。

          根據(jù)我不太靈光的腦袋的判斷,他的這個配置文件就叫spring.factories,存放的路徑是META-INF/spring.factories

          于是我們打開spring boot自動裝配的依賴jar包:

          那這個配置文件里面的內(nèi)容,是不是跟我們想的一樣呢?

          原來如此。

          這里的EnableAutoConfiguration注解,正是我們此行的起點啊…

          到這里,自動裝配到底是什么,應(yīng)該比較清楚了,原來他是幫我們加載了各種已經(jīng)寫好的Config類文件,實現(xiàn)了這些JavaConfig配置文件的重復(fù)利用和組件化

          7.4 loadFactoryNames方法

          行程不能到此結(jié)束,學(xué)習(xí)不能淺嘗輒止。

          我們還有最后一塊(幾塊)面紗沒有解開,現(xiàn)在還不能善罷甘休。

          讓我們進入loadFactoryNames方法:

          這個方法非常簡短,因為他調(diào)用了真正實現(xiàn)的方法:loadSpringFactories

          這一行return代碼我復(fù)制在下面:

          loadSpringFactories(classLoader)
          ?????.getOrDefault(factoryTypeName,?Collections.emptyList());

          可以分析得出:loadSpringFactories方法的返回值又調(diào)用了一個getOrDefault方法,這明顯是一個容器類的方法,目的是從容器中拿點東西出來

          就此推測:loadSpringFactories返回了一個包含我們需要的Config全類名(字符串)的集合容器,然后從這個集合容器中拿出來的東西就是我們的configurations

          讓我們看這個loadSpringFactories方法:

          它確實返回了一個容器:Map> 這個容器的類型是:MultiValueMap

          這個數(shù)據(jù)結(jié)構(gòu)就非常牛逼了,多值集合映射(我自己的翻譯)簡單來說,一個key可以對應(yīng)多個value,根據(jù)他的返回值,我們可以看到在這個方法中一個String對應(yīng)了一個List

          那么不難想到MultiValueMap中存放的形式:是”注解的類名——多個Config配置類“ 讓我們打個斷點來驗證一下:

          果然是這樣,并且@EnableAutoConfiguration注解竟然加載了多達124個配置類!

          接下來我們繼續(xù)思考:我們來的目的是獲取configurations,所以無論你做什么,必須得讀取配置文件,拿到configurations

          于是我們在try方法體中果然發(fā)現(xiàn)了這個操作:

          他獲取了一個路徑urls,那么這個路徑是否就是我們前面驗證的META-INF/spring.factories呢?

          我們查看靜態(tài)常量FACTORIES_RESOURCE_LOCATION的值:

          果真如此,bingo!繼續(xù)往下看,果然他遍歷了urls中的內(nèi)容,從這個路徑加載了配置文件:終于看到了我們熟悉的loadProperties方法!

          那我們大概就知道了,他確實是通過找到路徑,然后根據(jù)路徑讀取了配置文件,然后返回了讀取的result

          這就是loadFactoryNames方法的內(nèi)部實現(xiàn)。

          7.5 cache探秘

          到這里有的人又要問了:是不是結(jié)束了?其實還遠沒有!

          細心地朋友已經(jīng)發(fā)現(xiàn)了玄機,隱藏在loadFactoryNames方法的開頭和結(jié)尾:

          喂喂,這個返回的result好像并不是直接new出來的哦

          它是從cache緩存中取出來的,你發(fā)現(xiàn)了沒有

          根據(jù)下面的if判斷,如果從緩存中讀取出來了result,并且result的結(jié)果不為空,就直接返回,不需要再進行下面的讀寫操作了,這樣減少了磁盤頻繁的讀寫I/O

          同理,在我更新完所有的配置文件資源之后,退出時也要更新緩存。

          7.6 getAutoConfigurationEntry再探

          關(guān)鍵部分已經(jīng)過去,讓我們反過頭來重新審視一下遺漏的內(nèi)容:

          還記得getAutoConfigurationEntry方法嗎?

          我們最后來研究一下這個類除了getCandidateConfigurations還干了哪些事情:

          • removeDuplicates
          • configurations.removeAll(exclusions)

          可以看到,這里對加載進來的配置進行了去重、排除的操作,這是為了使得用戶自定義的排除包生效,同時避免包沖突異常,在SpringBoot的入口函數(shù)中我們可以通過注解指定需要排除哪些不用的包:

          例如我不使用RabbitMQ的配置包,就把它的配置類的class傳給exclude

          @SpringBootApplication(exclude?=?{RabbitAutoConfiguration.class})

          8. 自動裝配本質(zhì)

          我的理解:

          • SpringBoot自動裝配的本質(zhì)就是通過Spring去讀取META-INF/spring.factories中保存的配置類文件然后加載bean定義的過程。
          • 如果是標(biāo)了@Configuration注解,就是批量加載了里面的bean定義
          • 如何實現(xiàn)”自動“:通過配置文件獲取對應(yīng)的批量配置類,然后通過配置類批量加載bean定義,只要有寫好的配置文件spring.factories就實現(xiàn)了自動。

          9. 總結(jié)

          Spring Boot的自動裝配特性可以說是Spring Boot最重要、最核心的一環(huán),正是因為這個特性,使得我們的生產(chǎn)復(fù)雜性大大降低,極大地簡化了開發(fā)流程,可以說是給我們帶來了巨大的福音了~~

          筆者本人對源碼的理解仍然沒有那么深刻,只是喜歡分享自己的一些學(xué)習(xí)經(jīng)驗,希望能和大家共同學(xué)習(xí),畢竟掌握一門新技術(shù)的快感嘛… 大家都懂的!

          寫這篇文章耗費了巨大的精力,每一個字均是手碼,真的希望喜歡的朋友可以點贊收藏關(guān)注支持一波,這就是對我這個未出世的學(xué)生的最大激勵了!

          最后,我畫了一份Spring Boot自動裝配詳細流程圖,分享給大家。

          Spring Boot自動裝配詳細流程圖:

          版權(quán)申明:內(nèi)容來源網(wǎng)絡(luò),版權(quán)歸原創(chuàng)者所有。除非無法確認(rèn),都會標(biāo)明作者及出處,如有侵權(quán),煩請告知,我們會立即刪除并致歉!

          最近熱文閱讀:

          1、五個刁鉆的String面試問題及解答
          2、IntelliJ平臺將完全停止使用Log4j
          3、神操作!我把 3000 行代碼重構(gòu)成 15 行!
          4、我用Java幾分鐘處理完30億個數(shù)據(jù)...
          5、一款自動生成單元測試的 IDEA 插件
          6、微軟 10 大最受歡迎 GitHub 項目,最高 Star 數(shù)量 13 萬
          7、Spring Boot 中實現(xiàn)跨域的 5 種方式,你一定要知道!
          8、Mybatis批處理踩坑,糾正一些錯誤寫法
          9、Java 8?Java之父都不用!
          10、生產(chǎn)環(huán)境,清理大文件不生效?應(yīng)該這樣做!
          關(guān)注公眾號,你想要的Java都在這里

          瀏覽 45
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <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>
                  日批网站在线 | 爱爱无码免费视频 | 精品秘 无码一区二区三区老师 | 亚州女人性开放视频 | 三级日本三级网站三级网站在线 |