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