與應(yīng)用實(shí)戰(zhàn)》第 3 章 IOC 容器中的 Bean 閱讀筆記 1)
《SpringBoot 3入門(mén)與應(yīng)用實(shí)戰(zhàn)》第 3 章 IOC 容器中的 Bean 閱讀筆記 1IOC 容器中存放和管理的大多數(shù)是與程序運(yùn)行相關(guān)的 Bean在 Spring Framework 中對(duì)象被統(tǒng)稱為 Bean組件?即能被 Spring Framework 的 IOC 容器創(chuàng)建或管理的對(duì)象都可以稱為 Bean組件?。通過(guò)編寫(xiě) XML 配置文件或注解配置類(lèi)將第三方框架的核心 API 注冊(cè)為 IOC 容器中的 Bean這些核心 API 對(duì)象會(huì)在適當(dāng)?shù)奈恢冒l(fā)揮其作用以支撐項(xiàng)目的正常運(yùn)行。一個(gè) Bean 的定義中可以包含以下因素Bean 的 Name?Bean 的所屬類(lèi)/接口信息Bean的作用域單實(shí)例或多實(shí)例。Bean 內(nèi)部的依賴關(guān)系即 Bean 的內(nèi)部注入了哪些屬性和其他 Bean。Bean 的實(shí)例化方式即由構(gòu)造方法創(chuàng)建還是由工廠方法創(chuàng)建。Bean 的生命周期是否包含對(duì)象創(chuàng)建后的初始化動(dòng)作和對(duì)象被回收前的銷(xiāo)毀動(dòng)作。Bean 和對(duì)象有什么區(qū)別在學(xué)習(xí) Spring 時(shí)經(jīng)常遇見(jiàn) Bean 的概念Bean 和 Java 中的對(duì)象有什么區(qū)別。Bean 其實(shí)就是 Java 中的對(duì)象在 Spring Framework 中為了跟 Java new 出的對(duì)象區(qū)分把注入到 IOC 容器中的 Java 對(duì)象稱為 Bean。Spring Framework 的核心特性之一就是 IOC 控制反轉(zhuǎn)把創(chuàng)建和管理對(duì)象交給 Spring Framework 的 IOC 的容器我們直接使用 IOC 容器不用自己去創(chuàng)建和管理對(duì)象。3.1 Bean的類(lèi)型在 Spring Framework 中Bean 有兩種類(lèi)型普通 Bean 和工廠 Bean (FactoryBean)。3.1.1 普通 Bean普通 Bean 的定義非常好理解第 2 章中的所有 Bean 都是普通 Bean包括使用 Component 注解及其派生注解標(biāo)注的 Bean以及使用 標(biāo)簽、Bean 注解定義的 Bean。3.1.2 FactoryBean除了普通的 BeanSpring Framework 還考慮到特殊情況某些 Bean 的創(chuàng)建過(guò)程比較復(fù)雜使用 XML 配置文件或者注解聲明較難甚至無(wú)法完成。因而引入了 FactoryBean 機(jī)制借助工廠方法創(chuàng)建 Bean 對(duì)象。1.理解 FactoryBeanFactoryBean 是一個(gè)接口如果某個(gè)類(lèi)實(shí)現(xiàn)了 FactoryBean 接口它將不再是一個(gè)普通 Bean而是一個(gè)創(chuàng)建對(duì)象的工廠。2.FactoryBean 的使用通過(guò)簡(jiǎn)單示例演示 FactoryBean 的使用。假設(shè)小朋友要買(mǎi)玩具由玩具工廠給小朋友制造玩具。(1)聲明小朋友玩具首先聲明小朋友類(lèi) ChildChild類(lèi)中添加一個(gè) wantToy 的屬性指代小朋友想買(mǎi)的玩具類(lèi)別。之后我們?cè)賱?chuàng)建一個(gè)玩具的抽象類(lèi) Toy 和兩個(gè)具體的子類(lèi)球類(lèi) Ball 和玩具汽車(chē)類(lèi) Car。packagecom.yangjunbo.bean.examplea;publicclassChild{// 當(dāng)前小朋友想玩球privateStringwantToyball;publicStringgetWantToy(){returnwantToy;}}packagecom.yangjunbo.bean.examplea;publicabstractclassToy{privateStringname;publicToy(Stringname){this.namename;}}packagecom.yangjunbo.bean.examplea;publicclassBallextendsToy{// 球publicBall(Stringname){super(name);}}packagecom.yangjunbo.bean.examplea;publicclassCarextendsToy{// 玩具汽車(chē)publicCar(Stringname){super(name);}}(2)聲明玩具工廠接下來(lái)聲明一個(gè)玩具工廠類(lèi) ToyFactoryBean使其實(shí)現(xiàn) FactoryBean 接口并指定泛型為 Toy。由需求分析可知玩具工廠只有知道小朋友想買(mǎi)什么玩具才能對(duì)應(yīng)地生產(chǎn)從代碼邏輯上講玩具工廠應(yīng)當(dāng)依賴小朋友由小朋友為它提供玩具類(lèi)別由此可知 ToyFactoryBean 中應(yīng)當(dāng)注入 Child 對(duì)象。ToyFactoryBean 實(shí)現(xiàn) FactoryBean 接口時(shí)需要至少實(shí)現(xiàn)兩個(gè)方法包含生產(chǎn)目標(biāo)對(duì)象的類(lèi)型以及生產(chǎn)的邏輯。packagecom.yangjunbo.bean.examplea;importorg.springframework.beans.factory.FactoryBean;publicclassToyFactoryBeanimplementsFactoryBeanToy{privateChildchild;OverridepublicToygetObject()throwsException{switch(child.getWantToy()){caseball:returnnewBall(ball);casecar:returnnewCar(car);default:// Spring Framework 2.0開(kāi)始允許返回null之前的1.x版本不允許returnnull;}}OverridepublicClassToygetObjectType(){returnToy.class;}publicvoidsetChild(Childchild){this.childchild;}}(3)注冊(cè)玩具工廠使用注解配置類(lèi) BeanTypeConfiguration 將上述的 Child 和 ToyFactoryBean 注入到 IOC 容器中。packagecom.yangjunbo.bean.examplea;importorg.springframework.context.annotation.Bean;importorg.springframework.context.annotation.Configuration;ConfigurationpublicclassBeanTypeConfiguration{BeanpublicChildchild(){returnnewChild();}BeanpublicToyFactoryBeantoyFactory(){ToyFactoryBeantoyFactorynewToyFactoryBean();toyFactory.setChild(child());returntoyFactory;}}(4)測(cè)試效果編寫(xiě)測(cè)試啟動(dòng)類(lèi) BeanTypeAnnoApplication使用 BeanTypeConfiguration 配置類(lèi)驅(qū)動(dòng) IOC 容器并直接從容器中獲取 Toy 對(duì)象。由于ToyFactoryBean 實(shí)現(xiàn)了 FactoryBean 接口在獲取 Toy 對(duì)象時(shí)會(huì)觸發(fā) getObject 方法進(jìn)而生成一個(gè) Toy 對(duì)象。運(yùn)行 main 方法之后控制臺(tái)可以正確打印出 “ball”?證明 ToyFactoryBean 已經(jīng)創(chuàng)建出了 Ball 對(duì)象。packagecom.yangjunbo.bean.examplea;importorg.springframework.context.ApplicationContext;importorg.springframework.context.annotation.AnnotationConfigApplicationContext;publicclassBeanTypeAnnoApplication{publicstaticvoidmain(String[]args)throwsException{ApplicationContextctxnewAnnotationConfigApplicationContext(BeanTypeConfiguration.class);Toytoyctx.getBean(Toy.class);System.out.println(toy);}}3.FactoryBean 與 Bean 同時(shí)存在如果 IOC 容器中同時(shí)存在 FactoryBean 與對(duì)應(yīng)目標(biāo)類(lèi)型的 Bean則最終效果如何為了驗(yàn)證該效果我們?cè)?BeanTypeConfiguration 中使用 Bean 注解手動(dòng)注冊(cè)一個(gè) Ball 對(duì)象。接下來(lái)不修改 BeanTypeAnnoApplication 啟動(dòng)類(lèi)直接運(yùn)行 main 方法可以發(fā)現(xiàn)控制臺(tái)拋出 NoUniqueBeanDefinitionException 異常提示 IOC 容器中有兩個(gè) Toy 對(duì)象。由此得出一個(gè)結(jié)論FactoryBean 創(chuàng)建的 bean 對(duì)象會(huì)直接放在 IOC 容器中。如果修改一下 BeanTypeAnnoApplication 中 main 方法的代碼將獲取單個(gè) Toy 對(duì)象改為獲取全部 Toy 對(duì)象。重新運(yùn)行 main 方法可以發(fā)現(xiàn)控制臺(tái)打印了兩個(gè) toy通過(guò) ToyFactoryBean 創(chuàng)建出來(lái)的 Ball 對(duì)象的名稱叫 toyFactory。packagecom.yangjunbo.bean.examplea;importorg.springframework.context.annotation.Bean;importorg.springframework.context.annotation.Configuration;ConfigurationpublicclassBeanTypeConfiguration{BeanpublicChildchild(){returnnewChild();}BeanpublicToyFactoryBeantoyFactory(){ToyFactoryBeantoyFactorynewToyFactoryBean();toyFactory.setChild(child());returntoyFactory;}BeanpublicBallball(){returnnewBall(ball);}}packagecom.yangjunbo.bean.examplea;importorg.springframework.context.ApplicationContext;importorg.springframework.context.annotation.AnnotationConfigApplicationContext;importjava.util.Map;publicclassBeanTypeAnnoApplication{publicstaticvoidmain(String[]args)throwsException{ApplicationContextctxnewAnnotationConfigApplicationContext(BeanTypeConfiguration.class);//Toy toy ctx.getBean(Toy.class);//System.out.println(toy);MapString,Toytoysctx.getBeansOfType(Toy.class);toys.forEach((name,toy)-{System.out.println(toy name : name, toy.toString());});}}4.取出 FactoryBean 本體取出 FactoryBean 本體有兩種方式第一種是使用 FactoryBean 的類(lèi)型獲取即 ctx.getBean(ToyFactoryBean.class)第二種是在 Bean 的 id 前面加一個(gè) 符號(hào)即 ctx.getBean(“toyFactory”)。packagecom.yangjunbo.bean.examplea;importorg.springframework.context.ApplicationContext;importorg.springframework.context.annotation.AnnotationConfigApplicationContext;importjava.util.Map;publicclassBeanTypeAnnoApplication{publicstaticvoidmain(String[]args)throwsException{ApplicationContextctxnewAnnotationConfigApplicationContext(BeanTypeConfiguration.class);//Toy toy ctx.getBean(Toy.class);//System.out.println(toy);MapString,Toytoysctx.getBeansOfType(Toy.class);toys.forEach((name,toy)-{System.out.println(toy name : name, toy.toString());});System.out.println(ctx.getBean(ToyFactoryBean.class));System.out.println(ctx.getBean(toyFactory));}}5.FactoryBean 創(chuàng)建 Bean 的實(shí)例數(shù)FactoryBean 接口中有一個(gè)默認(rèn)方法 isSingleton返回值默認(rèn)為 true代表默認(rèn)是單實(shí)例的。連續(xù)兩次獲取 Toy 對(duì)象并對(duì)比內(nèi)存地址可以發(fā)現(xiàn)是同一個(gè)對(duì)象。如果在 ToyFactoryBean 中重寫(xiě) isSingleton 方法并返回 false再連續(xù)兩次獲取 Toy 對(duì)象內(nèi)存地址則不相同。packagecom.yangjunbo.bean.examplea;importorg.springframework.context.ApplicationContext;importorg.springframework.context.annotation.AnnotationConfigApplicationContext;importjava.util.Map;publicclassBeanTypeAnnoApplication{publicstaticvoidmain(String[]args)throwsException{ApplicationContextctxnewAnnotationConfigApplicationContext(BeanTypeConfiguration.class);//Toy toy ctx.getBean(Toy.class);//System.out.println(toy);MapString,Toytoysctx.getBeansOfType(Toy.class);toys.forEach((name,toy)-{System.out.println(toy name : name, toy.toString());});System.out.println(ctx.getBean(ToyFactoryBean.class));System.out.println(ctx.getBean(toyFactory));System.out.println(ctx.getBean(toyFactory));System.out.println(ctx.getBean(toyFactory));}}packagecom.yangjunbo.bean.examplea;importorg.springframework.beans.factory.FactoryBean;publicclassToyFactoryBeanimplementsFactoryBeanToy{privateChildchild;OverridepublicToygetObject()throwsException{switch(child.getWantToy()){caseball:returnnewBall(ball);casecar:returnnewCar(car);default:// Spring Framework 2.0開(kāi)始允許返回null之前的1.x版本不允許returnnull;}}OverridepublicClassToygetObjectType(){returnToy.class;}publicvoidsetChild(Childchild){this.childchild;}OverridepublicbooleanisSingleton(){returnfalse;}}6.FactoryBean 創(chuàng)建 Bean 的時(shí)機(jī)IOC 容器中的普通 Bean 都是隨 IOC 容器的初始化而一并創(chuàng)建的那么 FactoryBean 與創(chuàng)建的目標(biāo)對(duì)象是否同時(shí)創(chuàng)建我們可以在 Toy 和ToyFactoryBean 的內(nèi)部聲明無(wú)參構(gòu)造方法并添加控制臺(tái)打印。創(chuàng)建一個(gè)新的啟動(dòng)類(lèi) BeanTypeAnnoApplicationmain 方法中只有創(chuàng)建 ApplicationContext 一行代碼運(yùn)行main方法可以發(fā)現(xiàn)控制臺(tái)只打印了 ToyFactoryBean 的初始化文字而 Toy 的初始化文字并沒(méi)有被打印由此得出結(jié)論FactoryBean 本身的加載是隨 IOC 容器的初始化時(shí)機(jī)一并創(chuàng)建的。控制臺(tái)并沒(méi)有打印 “生產(chǎn)了一個(gè)” name說(shuō)明 FactoryBean 中要?jiǎng)?chuàng)建的 Bean 還沒(méi)有被加載**由此也就得出另一個(gè)結(jié)論FactoryBean 生產(chǎn)Bean 的機(jī)制是延遲生產(chǎn)。**如果在初始化 ApplicationContext 后再獲取 Toy 對(duì)象此時(shí)會(huì)打印“生產(chǎn)了一個(gè) ball”?印證了剛才的結(jié)論。packagecom.yangjunbo.bean.exampleb;importorg.springframework.context.ApplicationContext;importorg.springframework.context.annotation.AnnotationConfigApplicationContext;publicclassBeanTypeAnnoApplication{publicstaticvoidmain(String[]args)throwsException{ApplicationContextctxnewAnnotationConfigApplicationContext(BeanTypeConfiguration.class);ctx.getBean(Toy.class);}}7.BeanFactory 與 FactoryBean 的區(qū)別BeanFactorySpring Framework 中實(shí)現(xiàn) IOC 的底層容器?。FactoryBean用于創(chuàng)建 Bean 的工廠 Bean適合用來(lái)創(chuàng)建一些初始化流程比較復(fù)雜的 Bean。