Bean的管理與SpringBoot自動(dòng)裝配原理解讀
1、Bean管理
默認(rèn)情況下,SpringBoot項(xiàng)目在啟動(dòng)的時(shí)候會(huì)自動(dòng)的創(chuàng)建IOC容器,并且在啟動(dòng)的過程當(dāng)中會(huì)自動(dòng)的將bean對象都創(chuàng)建好,存放在IOC容器當(dāng)中。
應(yīng)用程序在運(yùn)行時(shí)需要依賴什么bean對象,就直接進(jìn)行依賴注入就可以了。
1.1 獲取Bean
要想主動(dòng)從IOC容器中獲取到bean對象,需要先拿到IOC容器
想獲取到IOC容器,直接將IOC容器對象注入進(jìn)來就可以了
@Autowired private ApplicationContext applicationContext; //IOC容器對象
在IOC容器中提供了一些方法,可以主動(dòng)從IOC容器中獲取到bean對象,
介紹3種常用方式:
1.根據(jù)name獲取bean
Object getBean(String name)
2.根據(jù)類型獲取bean
<T> T getBean(Class<T> requiredType)
3.根據(jù)name獲取bean(帶類型轉(zhuǎn)換)
<T> T getBean(String name, Class<T> requiredType)
@SpringBootTest class SpringbootTheoryApplicationTests { @Autowired private ApplicationContext applicationContext; //IOC容器對象 //獲取bean對象 @Test public void testGetBean(){ //根據(jù)bean的名稱獲取 MyController bean1 = (MyController) applicationContext.getBean("myController"); System.out.println(bean1); //根據(jù)bean的類型獲取 MyController bean2 = applicationContext.getBean(MyController.class); System.out.println(bean2); //根據(jù)bean的名稱 及 類型獲取 MyController bean3 = applicationContext.getBean("myController", MyController.class); System.out.println(bean3); } }
運(yùn)行結(jié)果:
com.wbr.controller.MyController@2fa47368
com.wbr.controller.MyController@2fa47368
com.wbr.controller.MyController@2fa47368
可以看到三個(gè)bean的打印結(jié)果都是一樣的,說明IOC容器當(dāng)中的bean對象只有一個(gè)。
默認(rèn)情況下,IOC中的bean對象是單例
1.2 Bean的作用域
IOC容器中,默認(rèn)bean對象是單例模式(只有一個(gè)實(shí)例對象)。
想要設(shè)置bean對象為非單例就需要設(shè)置bean的作用域。
在Spring中支持五種作用域,后三種在web環(huán)境才生效:
作用域 | 說明 |
---|---|
singleton | 容器內(nèi)同名稱的bean只有一個(gè)實(shí)例(單例)(默認(rèn)) |
prototype | 每次使用該bean時(shí)會(huì)創(chuàng)建新的實(shí)例(非單例) |
request | 每個(gè)請求范圍內(nèi)會(huì)創(chuàng)建新的實(shí)例 |
session | 每個(gè)會(huì)話范圍內(nèi)會(huì)創(chuàng)建新的實(shí)例 |
application | 每個(gè)應(yīng)用范圍內(nèi)會(huì)創(chuàng)建新的實(shí)例 |
可以借助Spring中的@Scope注解來進(jìn)行配置作用域
測試一:
Controller類
//當(dāng)我們未加@Scope注解時(shí),默認(rèn)bean的作用域?yàn)椋篅Scope("singleton") (單例模式) @Lazy //延遲加載(第一次使用bean對象時(shí),才會(huì)創(chuàng)建bean對象并交給ioc容器管理) @RestController public class MyController { @Autowired private MyService myService; public MyController(){ System.out.println("MyController 構(gòu)造方法執(zhí)行了..."); } }
測試類
@SpringBootTest class SpringbootTheoryApplicationTests { @Autowired private ApplicationContext applicationContext; //IOC容器對象 //獲取bean對象 @Test public void testGetBean(){ //根據(jù)bean的名稱獲取 MyController bean1 = (MyController) applicationContext.getBean("myController"); System.out.println(bean1); //根據(jù)bean的類型獲取 MyController bean2 = applicationContext.getBean(MyController.class); System.out.println(bean2); //根據(jù)bean的名稱 及 類型獲取 MyController bean3 = applicationContext.getBean("myController", MyController.class); System.out.println(bean3); } }
運(yùn)行結(jié)果:
MyController 構(gòu)造方法執(zhí)行了...
com.wbr.controller.MyController@3835d3fd
com.wbr.controller.MyController@3835d3fd
com.wbr.controller.MyController@3835d3fd
注意事項(xiàng):
- IOC容器中的bean默認(rèn)使用的作用域:singleton (單例)
- 默認(rèn)singleton的bean,在容器啟動(dòng)時(shí)被創(chuàng)建,可以使用@Lazy注解來延遲初始化(延遲到第一次使用時(shí))
測試二:
給MyController加上@Scope("prototype")注解:
@Lazy //延遲加載(第一次使用bean對象時(shí),才會(huì)創(chuàng)建bean對象并交給ioc容器管理) @Scope("prototype") @RestController public class MyController { @Autowired private MyService myService; public MyController(){ System.out.println("MyController 構(gòu)造方法執(zhí)行了..."); } }
再次運(yùn)行測試方法得到以下結(jié)果:
MyController 構(gòu)造方法執(zhí)行了...
com.wbr.controller.MyController@73aae7a
MyController 構(gòu)造方法執(zhí)行了...
com.wbr.controller.MyController@3856d0cb
MyController 構(gòu)造方法執(zhí)行了...
com.wbr.controller.MyController@2125535d
注意事項(xiàng):
- prototype的bean,每一次使用該bean的時(shí)候都會(huì)創(chuàng)建一個(gè)新的實(shí)例
- 實(shí)際開發(fā)當(dāng)中,絕大部分的Bean是單例的,也就是說絕大部分Bean不需要配置scope屬性
1.3 第三方Bean
情況1:在自定義類上加上@Component以及它的這三個(gè)衍生注解@Controller、@Service、@Repository,用來聲明這個(gè)bean對象。
情況2:這個(gè)類它不是我們自己編寫的,而是我們引入的第三方依賴當(dāng)中提供的。
我們可以使用 ObjectMapper
作為第三方依賴。ObjectMapper
是 Jackson 庫中的核心類,用于在 Java 對象和 JSON 之間進(jìn)行轉(zhuǎn)換。
當(dāng)我們需要使用到ObjectMapper
對象時(shí),應(yīng)該如何進(jìn)行依賴注入?
由于第三方提供的類是只讀的。無法在第三方類上添加@Component注解或衍生注解。
解決方案1:在啟動(dòng)類上添加用@Bean標(biāo)識的方法
啟動(dòng)類:
@SpringBootApplication public class SpringbootTheoryApplication { public static void main(String[] args) { SpringApplication.run(SpringbootTheoryApplication.class, args); } //聲明第三方bean @Bean //將當(dāng)前方法的返回值對象交給IOC容器管理, 成為IOC容器bean public ObjectMapper objectMapper(){ return new ObjectMapper(); } }
測試類:
@SpringBootTest class SpringbootWebConfig2ApplicationTests { @Autowired private ObjectMapper objectMapper; // 第三方bean的管理 @Test public void testThirdBean() throws Exception { // 將 JSON 字符串轉(zhuǎn)換為 Java 對象 String json = "{\"name\":\"zhangsan\",\"age\":21}"; Person person = objectMapper.readValue(json, Person.class); System.out.println(person.getName() + " : " + person.getAge()); } // 用于測試的簡單 POJO 類 static class Person { private String name; private int age; public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } } }
運(yùn)行結(jié)果:
zhangsan : 21
注意事項(xiàng):
- 以上在啟動(dòng)類中聲明第三方Bean的作法,不建議使用(項(xiàng)目中要保證啟動(dòng)類的純粹性)
解決方案2:在配置類中定義@Bean標(biāo)識的方法
- 如果需要定義第三方Bean時(shí), 通常會(huì)單獨(dú)定義一個(gè)配置類
@Configuration //聲明一個(gè)配置類 public class ObjectMapperConfig { @Bean public ObjectMapper objectMapper() { return new ObjectMapper(); } }
注釋掉SpringBoot啟動(dòng)類中創(chuàng)建第三方bean對象的代碼,重新執(zhí)行測試方法:
zhangsan : 21
在方法上加上一個(gè)@Bean注解,Spring 容器在啟動(dòng)的時(shí)候,它會(huì)自動(dòng)的調(diào)用這個(gè)方法,并將方法的返回值聲明為Spring容器當(dāng)中的Bean對象。
注意事項(xiàng) :
- 通過@Bean注解的name或value屬性可以聲明bean的名稱,如果不指定,默認(rèn)bean的名稱就是方法名。
- 如果第三方bean需要依賴其它bean對象,直接在bean定義方法中設(shè)置形參即可,容器會(huì)根據(jù)類型自動(dòng)裝配。
2、SpringBoot原理
SpringBoot框架之所以使用起來更簡單更快捷,是因?yàn)镾pringBoot框架底層提供了兩個(gè)非常重要的功能:一個(gè)是起步依賴,一個(gè)是自動(dòng)配置。
2.1 起步依賴
假如沒有使用SpringBoot,用的是Spring框架進(jìn)行web程序的開發(fā),此時(shí)我們就需要引入web程序開發(fā)所需要的一些依賴。
- spring-webmvc依賴:這是Spring框架進(jìn)行web程序開發(fā)所需要的依賴
- servlet-api依賴:Servlet基礎(chǔ)依賴
- jackson-databind依賴:JSON處理工具包
如果要使用AOP,還需要引入aop依賴、aspect依賴
項(xiàng)目中所引入的這些依賴,還需要保證版本匹配,否則就可能會(huì)出現(xiàn)版本沖突問題。
使用了SpringBoot,就不需要像上面這么繁瑣的引入依賴了。由于Maven的依賴傳遞。我們只需要引入一個(gè)依賴就可以了,那就是web開發(fā)的起步依賴:springboot-starter-web。
在web開發(fā)的起步依賴當(dāng)中,就集成了web開發(fā)中常見的依賴:json、web、webmvc、tomcat等。我們只需要引入這一個(gè)起步依賴,其他的依賴都會(huì)自動(dòng)的通過Maven的依賴傳遞進(jìn)來。
2.2 SpringBoot自動(dòng)裝配
話不多說,上源碼跟蹤圖?。?/strong>
2.3 @Conditional
我們在跟蹤SpringBoot自動(dòng)配置的源碼的時(shí)候,在自動(dòng)配置類聲明bean的時(shí)候,除了在方法上加了一個(gè)@Bean注解以外,還會(huì)經(jīng)常用到一個(gè)注解,就是以Conditional開頭的這一類的注解。
以Conditional開頭的這些注解都是條件裝配的注解。下面我們就來介紹下條件裝配注解。
@Conditional注解:
- 作用:按照一定的條件進(jìn)行判斷,在滿足給定條件后才會(huì)注冊對應(yīng)的bean對象到Spring的IOC容器中。
- 位置:方法、類
@Conditional本身是一個(gè)父注解,派生出大量的子注解:
- @ConditionalOnClass:判斷環(huán)境中有對應(yīng)字節(jié)碼文件,才注冊bean到IOC容器。
- @ConditionalOnMissingBean:判斷環(huán)境中沒有對應(yīng)的bean(類型或名稱),才注冊bean到IOC容器。
- @ConditionalOnProperty:判斷配置文件中有對應(yīng)屬性和值,才注冊bean到IOC容器。
下面我們通過代碼來演示下Conditional注解的使用:
- @ConditionalOnClass注解
@Configuration public class HeaderConfig { ? @Bean @ConditionalOnClass(name="io.jsonwebtoken.Jwts")//環(huán)境中存在指定的這個(gè)類,才會(huì)將該bean加入IOC容器 public HeaderParser headerParser(){ return new HeaderParser(); } }
- 測試類
@SpringBootTest public class AutoConfigurationTests { @Autowired private ApplicationContext applicationContext; ? @Test public void testHeaderParser(){ System.out.println(applicationContext.getBean(HeaderParser.class)); } }
執(zhí)行testHeaderParser()測試方法:
因?yàn)閕o.jsonwebtoken.Jwts字節(jié)碼文件在啟動(dòng)SpringBoot程序時(shí)已存在,所以創(chuàng)建HeaderParser對象并注冊到IOC容器中。
- @ConditionalOnMissingBean注解
@Configuration public class HeaderConfig { ? @Bean @ConditionalOnMissingBean //不存在該類型的bean,才會(huì)將該bean加入IOC容器 public HeaderParser headerParser(){ return new HeaderParser(); } }
執(zhí)行testHeaderParser()測試方法:
SpringBoot在調(diào)用@Bean標(biāo)識的headerParser()前,IOC容器中是沒有HeaderParser類型的bean,所以HeaderParser對象正常創(chuàng)建,并注冊到IOC容器中。
再次修改@ConditionalOnMissingBean注解:
@Configuration public class HeaderConfig { ? @Bean @ConditionalOnMissingBean(name="deptController2")//不存在指定名稱的bean,才會(huì)將該bean加入IOC容器 public HeaderParser headerParser(){ return new HeaderParser(); } }
執(zhí)行testHeaderParser()測試方法:
因?yàn)樵赟pringBoot環(huán)境中不存在名字叫deptController2的bean對象,所以創(chuàng)建HeaderParser對象并注冊到IOC容器中。
再次修改@ConditionalOnMissingBean注解:
@Configuration public class HeaderConfig { ? @Bean @ConditionalOnMissingBean(HeaderConfig.class)//不存在指定類型的bean,才會(huì)將bean加入IOC容器 public HeaderParser headerParser(){ return new HeaderParser(); } }
@SpringBootTest public class AutoConfigurationTests { @Autowired private ApplicationContext applicationContext; ? @Test public void testHeaderParser(){ System.out.println(applicationContext.getBean(HeaderParser.class)); } }
執(zhí)行testHeaderParser()測試方法:
因?yàn)镠eaderConfig類中添加@Configuration注解,而@Configuration注解中包含了@Component,所以SpringBoot啟動(dòng)時(shí)會(huì)創(chuàng)建HeaderConfig類對象,并注冊到IOC容器中。
當(dāng)IOC容器中有HeaderConfig類型的bean存在時(shí),不會(huì)把創(chuàng)建HeaderParser對象注冊到IOC容器中。而IOC容器中沒有HeaderParser類型的對象時(shí),通過getBean(HeaderParser.class)方法獲取bean對象時(shí),引發(fā)異常:NoSuchBeanDefinitionException
總結(jié)
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
SpringBoot實(shí)現(xiàn)優(yōu)雅停機(jī)的流程步驟
優(yōu)雅停機(jī)(Graceful Shutdown) 是指在服務(wù)器需要關(guān)閉或重啟時(shí),能夠先處理完當(dāng)前正在進(jìn)行的請求,然后再停止服務(wù)的操作,本文給大家介紹了SpringBoot實(shí)現(xiàn)優(yōu)雅停機(jī)的流程步驟,需要的朋友可以參考下2024-03-03Java中的Random()函數(shù)及兩種構(gòu)造方法
Java中存在著兩種Random函數(shù)分別是java.lang.Math.Random和java.util.Random,文中給大家介紹了random()的兩種構(gòu)造方法,感興趣的朋友跟隨小編一起看看吧2018-11-11SpringBoot項(xiàng)目啟動(dòng)后立馬自動(dòng)關(guān)閉的解決方案
這篇文章主要介紹了SpringBoot項(xiàng)目啟動(dòng)后立馬自動(dòng)關(guān)閉的解決方案,具有很好的參考價(jià)值,希望對大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-03-03基于Redis分布式鎖Redisson及SpringBoot集成Redisson
這篇文章主要介紹了基于Redis分布式鎖Redisson及SpringBoot集成Redisson,文章圍繞主題展開詳細(xì)的內(nèi)容介紹,具有一定的參考價(jià)值,需要的小小伙伴可以參考一下2022-09-09SrpingDruid數(shù)據(jù)源加密數(shù)據(jù)庫密碼的示例代碼
本篇文章主要介紹了SrpingDruid數(shù)據(jù)源加密數(shù)據(jù)庫密碼的示例代碼,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2017-10-10springboot-mybatis/JPA流式查詢的多種實(shí)現(xiàn)方式
這篇文章主要介紹了springboot-mybatis/JPA流式查詢,本文給大家分享三種方式,每種方式結(jié)合示例代碼給大家講解的非常詳細(xì),需要的朋友可以參考下2022-12-12java動(dòng)態(tài)代理(jdk與cglib)詳細(xì)解析
靜態(tài)代理:由程序員創(chuàng)建或特定工具自動(dòng)生成源代碼,再對其編譯。在程序運(yùn)行前,代理類的.class文件就已經(jīng)存在了2013-09-09