做酒店管理網(wǎng)站的作用成都網(wǎng)絡(luò)推廣外包
在之前的文章 Java知識(shí)點(diǎn)整理 8 — Spring 簡(jiǎn)介?中介紹了 Spring 的兩大核心概念 IoC 和 AOP,但對(duì) Spring Bean 的介紹不全面,本文將補(bǔ)充 Spring 中 Bean 的概念。
一. 什么是 Spring Bean?
在 Spring 官方文檔中,對(duì) bean 的定義為:構(gòu)成應(yīng)用程序主干并由 Spring IoC 容器管理的對(duì)象稱為 bean。bean 是由 Spring IoC 容器實(shí)例化、組裝和管理的對(duì)象。
開發(fā)者需要告訴 IoC 容器協(xié)助管理哪些對(duì)象,這個(gè)是通過配置元數(shù)據(jù)來定義的。配置元數(shù)據(jù)可以是 XML文件(較老)、注解或者 Java 配置類。
二. 通過例子來理解
首先有一個(gè) Student 類,里面有兩個(gè)成員變量 id 和 name,并提供 set、get方法。
public class Student {private Integer id;private String name;public Integer getId() {return id;}public void setId(Integer id) {this.id = id;}public String getName() {return name;}public void setName(String name) {this.name = name;}
}
另一個(gè)類是 StudentManager,有一個(gè) Student 的對(duì)象,提供了 setStudent 方法初始化 Student 對(duì)象,并且它的 show 方法能夠打印這個(gè) student 的 id 和 name。
public class StudentManager {private Student student;public void setStudent(Student student) {this.student = student;}public void show() {System.out.println(student.getId());System.out.println(student.getName());}
}
分析以上兩段代碼發(fā)現(xiàn),兩個(gè)類之間高度耦合,后者依賴于前者。假如沒有及時(shí)對(duì) StudentManager 的 student 綁定對(duì)象,卻調(diào)用了 show 方法,那么程序就會(huì)報(bào)空指針異常的錯(cuò)誤。因此 Spring 提供了 IoC(控制反轉(zhuǎn))和 DI(依賴注入)進(jìn)行解耦。
在 Spring 中不需要自己創(chuàng)建對(duì)象,只需要告訴 Spring,哪些類需要?jiǎng)?chuàng)建,然后在啟動(dòng)項(xiàng)目時(shí) Spring 就會(huì)自動(dòng)幫助創(chuàng)建對(duì)應(yīng)的對(duì)象,并且只存在一個(gè)類的實(shí)例。這個(gè)類的實(shí)例也就是 Bean,而這種模式通常稱為單例模式,即一個(gè)類只有一個(gè)實(shí)例。
繼續(xù)思考,開發(fā)者該如何告訴 Spring 哪些類需要?jiǎng)?chuàng)建對(duì)象呢?
最簡(jiǎn)單最常用的方式就是 Java 注解配置。也就是將一個(gè)類聲明為 Bean 所需要的注解。
聲明 | 含義 |
@Component | 通用注解,可標(biāo)注任意類為 Spring 組件。如果一個(gè) Bean 不知道屬于哪個(gè)層,可以使用該注解標(biāo)注 |
@Repository | 當(dāng)前類在持久層(Dao層),主要用于數(shù)據(jù)庫相關(guān)操作。 |
@Service | 當(dāng)前類在業(yè)務(wù)邏輯層,主要涉及復(fù)雜的邏輯。 |
@Controller | 當(dāng)前類在控制層,主要用于接受用戶請(qǐng)求并調(diào)用 Service 層返回?cái)?shù)據(jù)給前端頁面。 |
其實(shí)以上四種聲明方式效果完全一致,使用不同的關(guān)鍵詞是讓開發(fā)者快速了解該類屬于哪一層。
例如,在剛才的 Student 類前加上 @Component 注解,就告訴 Spring:你要在項(xiàng)目創(chuàng)建運(yùn)行時(shí)幫我創(chuàng)建 Student 類的 Bean(對(duì)象)。
@Component
public class Student {private Integer id;private String name;public Integer getId() {return id;}public void setId(Integer id) {this.id = id;}public String getName() {return name;}public void setName(String name) {this.name = name;}
}
此時(shí)"依賴"添加完畢,但還沒結(jié)束。雖然讓 Spring 幫助我們創(chuàng)建了對(duì)象,但 StudentManager 怎么知道這個(gè)被創(chuàng)建的對(duì)象在哪呢?所以接下來要告訴?StudentManager 剛才 Spring 幫助創(chuàng)建的 Bean(對(duì)象)在哪,也就是注入 Bean。
注入注解 | |
聲明 | 含義 |
@Autowired | 根據(jù) Bean 的 Class 類型自動(dòng)裝配 |
@Inject | 字面意思注入 |
@Resource | 字面意思資源,根據(jù) Bean 的屬性名稱(id / name)自動(dòng)裝配 |
例如,在 StudentManager 類中聲明成員變量 Student 的前面加上 @Autowired,Spring 會(huì)自動(dòng)注入一個(gè) Bean。
@Component
public class StudentManager {@Autowiredprivate Student student;public void show() {System.out.println(student.getId());System.out.println(student.getName());}
}
三. @Bean 注解的使用
- @Bean 注解作用在方法上,產(chǎn)生一個(gè) Bean 對(duì)象,然后將其交給 Spring 管理。Spring 會(huì)將這個(gè) Bean 對(duì)象放在自己的 IoC 容器中。
- @Bean 方法名與返回類名一致,首字母小寫。
- @Component、@Repository、@Controller、@Service 這些注解只局限于自己編寫的類,@Bean 注解能把第三方庫中的類實(shí)例加入 IoC 容器并交給 Spring 管理。
- @Bean 一般和 @Component 或 @Configuration 一起使用。
四. @Component 和 @Bean 的區(qū)別
- @Component 注解作用于類,@Bean 作用于方法。
- @Component 通常是通過類路徑掃描來自動(dòng)偵測(cè)以及自動(dòng)裝配到 Spring 容器中,可以使用 @ComponentScan 注解定義要掃描的路徑,從中找出標(biāo)識(shí)了需要裝配的類,并自動(dòng)裝配到 Spring 的 bean 容器中。@Bean 注解通常是在標(biāo)有該注解的方法中定義產(chǎn)生這個(gè)?bean,它告訴了 Spring 這是某個(gè)類的實(shí)例,在需要時(shí)給我。
- @Bean 注解比 @Component 注解的自定義性更強(qiáng),而且很多地方只能通過@Bean 注解來注冊(cè) bean,比如第三方庫。
@Bean 注解的使用:
@Configuration //標(biāo)記該類為配置類,提供配置信息給 Spring 容器。
public class AppConfig {@Beanpublic TransferService transferService() {return new TransferServiceImpl();}
}
五. @Autowired 和 @Resource 的區(qū)別
@Autowired 屬于 Spring 內(nèi)置的注解,默認(rèn)注入方式為 byType,優(yōu)先根據(jù)接口類型去匹配并注入 Bean(接口的實(shí)現(xiàn)類)。
但如果一個(gè)接口存在多個(gè)實(shí)現(xiàn)類,byType 這種方式可能無法正確注入對(duì)象,因?yàn)?Spring 找到了多個(gè)滿足條件的選擇,默認(rèn)情況下不知道選哪一個(gè)。這種情況下,注入方式會(huì)變?yōu)?byName,即根據(jù)名稱匹配,這個(gè)名稱通常是類名,如下面的 SmsService。
// smsService 就是上面所說的名稱
@Autowired
private SmsService smsService;
如果 SmsService 接口有兩個(gè)實(shí)現(xiàn)類:SmsServiceImpl1 和?SmsServiceImpl2,且它們都被 Spring 容器管理。
// 報(bào)錯(cuò),byName 和 byType 都無法匹配到 bean
@Autowired
private SmsService smsService;
// 正確注入 SmsServiceImpl1 對(duì)象對(duì)應(yīng)的 bean
@Autowired
private SmsService smsServiceImpl1;
// 正確注入 SmsServiceImpl1 對(duì)象對(duì)應(yīng)的 bean
// smsServiceImpl1 就是我們上面所說的名稱
@Autowired
@Qualifier(value = "smsServiceImpl1")
private SmsService smsService;
通過 @Qualifier 注解能夠顯式指定名稱,而不是依賴變量名稱。
@Resource 屬于JDK提供的注解,默認(rèn)注入方式為 byName。如果無法通過名稱匹配到對(duì)應(yīng)的 Bean,注入方式會(huì)變?yōu)?byType。
@Resource 有兩個(gè)比較重要且日常開發(fā)常用的屬性:name(名稱)和 type(類型)。
// 報(bào)錯(cuò),byName 和 byType 都無法匹配到 bean
@Resource
private SmsService smsService;
// 正確注入 SmsServiceImpl1 對(duì)象對(duì)應(yīng)的 bean
@Resource
private SmsService smsServiceImpl1;
// 正確注入 SmsServiceImpl1 對(duì)象對(duì)應(yīng)的 bean(比較推薦這種方式)
@Resource(name = "smsServiceImpl1")
private SmsService smsService;
總結(jié)一下:
- @Autowired 是 Spring 提供的注解,@Resource 是 JDK 提供的注解。
- @Autowired 默認(rèn)的注入方式為 byType,@Resource 默認(rèn)的注入方式為 byName。
- 當(dāng)一個(gè)接口存在多個(gè)實(shí)現(xiàn)類時(shí),兩個(gè)注解都需要通過名稱才能正確匹配到對(duì)應(yīng)的 Bean。@Autowired 可以通過 @Qualifier 注解來顯示指定名稱,@Resource 可以通過 name 屬性來顯式指定名稱。
- @Autowired 支持在構(gòu)造函數(shù)、方法、字段和參數(shù)上使用。@Resource 主要用于字段和方法上的注入,不支持在構(gòu)造函數(shù)或參數(shù)上使用。
六. Bean 的作用域
通常有以下幾種:
作用域 | 含義 |
singleton(默認(rèn)) | IoC容器中只有唯一的 bean 實(shí)例。Spring 中的 bean 默認(rèn)都是單例的,單例模式的應(yīng)用。 |
prototype | 每次獲取都會(huì)創(chuàng)建一個(gè)新的 bean 實(shí)例。如果連續(xù) getBean() 兩次,得到的是不同的 Bean 實(shí)例。 |
request(僅 Web 應(yīng)用) | 每次 HTTP 請(qǐng)求都會(huì)產(chǎn)生一個(gè)新的 bean(請(qǐng)求 bean),該 bean 僅在當(dāng)前 HTTP request 內(nèi)有效。 |
session(僅 Web 應(yīng)用) | 每次來自新 session 的 HTTP 請(qǐng)求都會(huì)產(chǎn)生一個(gè)新的 bean(會(huì)話 bean),該 bean 僅在當(dāng)前 HTTP session 內(nèi)有效。 |
application(僅 Web 應(yīng)用) | 每個(gè) Web 應(yīng)用在啟動(dòng)時(shí)創(chuàng)建一個(gè) bean(應(yīng)用 bean),該 bean 僅在當(dāng)前應(yīng)用啟動(dòng)時(shí)間內(nèi)有效。 |
websocket(僅 Web 應(yīng)用) | 每次?WebSocket 會(huì)話產(chǎn)生一個(gè)新的 bean。 |
如何配置:
// 注解方式
@Bean
@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public Person personPrototype() {return new Person();
}
七. Bean 的生命周期
1. 創(chuàng)建 Bean 的實(shí)例:Bean 容器首先會(huì)找到配置文件中的 Bean 定義,然后使用 Java 反射 API 來創(chuàng)建 Bean 的實(shí)例。
2. Bean 屬性賦值/填充:為 Bean 設(shè)置相關(guān)屬性和依賴,例如 @Autowired 等注解注入對(duì)象、@Value 注入值、@Resource 注入各種資源等。
3. Bean 初始化:
- 如果 Bean 實(shí)現(xiàn)了 BeanNameAware?接口,調(diào)用 setBeanName()?方法,傳入 Bean 的名字。
- 如果 Bean 實(shí)現(xiàn)了 BeanClassLoaderAware?接口,調(diào)用 setBeanClassLoader()?方法,傳入 ClassLoader?對(duì)象的實(shí)例。
- 與上面的類似,如果實(shí)現(xiàn)了其他 *.Aware?接口,就調(diào)用相應(yīng)的方法。
- 如果有和加載這個(gè) Bean 的 Spring 容器相關(guān)的 BeanPostProcessor?對(duì)象,執(zhí)行postProcessBeforeInitialization()?方法
- 如果 Bean 實(shí)現(xiàn)了 InitializingBean?接口,執(zhí)afterPropertiesSet()?方法。
- 如果 Bean 在配置文件中的定義包含 init-method 屬性,執(zhí)行指定的方法。
- 如果有和加載這個(gè) Bean 的 Spring 容器相關(guān)的 BeanPostProcessor 對(duì)象,執(zhí)行postProcessAfterInitialization() 方法。
4. 銷毀 Bean:銷毀并不是說立刻把 Bean 銷毀掉,而是把 Bean 的銷毀方法先記錄下來,將來需要銷毀 Bean 或者銷毀容器的時(shí)候,就調(diào)用這些方法去釋放 Bean 所持有的資源。
- 如果 Bean 實(shí)現(xiàn)了 DisposableBean?接口,執(zhí)行 destroy() 方法。
- 如果 Bean 在配置文件中的定義包含 destroy-method 屬性,執(zhí)行指定的 Bean 銷毀方法。或者,也可以直接通過 @PreDestroy 注解標(biāo)記 Bean 銷毀之前執(zhí)行的方法。