第四章-詳述Spring配置和Spring Boot
概覽:
本章主要講述了bean的生命周期,FactoryBean,Spring的event消息,以及PropertyEditor
bean的生命周期
對于bean的生命周期,之前自己的理解僅僅局限init-method和destroy-method,知道在這個過程中會注入依賴項,最終執(zhí)行到init-method然后銷毀的時候執(zhí)行destroy-method,對于整個bean的生命周期缺少一個完整詳細(xì)的理解,整個呈現(xiàn)出碎片化,所以在這里先整理一下。
在本書中作者將Bean的整個生命周期概括為了4個階段:bean實例化和Di、檢查Spring Awareness、創(chuàng)建bean的生命周期回調(diào)、銷毀bean生命周期回調(diào),具體如下圖:

從上面的圖中可以看到整個bean的整個生命周期是從掃描bean開始到自動裝配,屬性設(shè)置最后調(diào)用初始化方法以及調(diào)用銷毀方法,在這個過程中有一個階段叫檢查Spring Awareness,那么Spring的Awareness到底是一個什么樣的存在呢?第一反應(yīng)是在開發(fā)中有看見過Aware結(jié)尾的類,但是一直不知道其具體的作用,帶著好奇心到Spring5.x的源碼中去搜索了一番,從BeanNameAware類得知Spring源碼中有一個名字叫Aware的空接口,代碼如下:
/**
* A marker superinterface indicating that a bean is eligible to be notified by the
* Spring container of a particular framework object through a callback-style method.
* The actual method signature is determined by individual subinterfaces but should
* typically consist of just one void-returning method that accepts a single argument.
*
* @author Chris Beams
* @author Juergen Hoeller
* @since 3.1
*/
public interface Aware {
}
可以看到Aware是一個空接口,但是在Spring5.x的源碼中其實現(xiàn)類有多達(dá)695個,這里就不一一分析了,從接口的原注釋中可以得知Aware是一個空的接口,其實際的方法應(yīng)該尤其子類去實現(xiàn),同時子類的實現(xiàn)方法應(yīng)該是一個返回void的單參數(shù)的方法,并且Aware接口是Spring內(nèi)置的一些功能供Spring內(nèi)部使用。因為這些功能的存在,使得我們可以去獲取,修改beanName,事件發(fā)布等等,Aware的原譯:意識到的;知道的;有....方面知識的,所以Aware的實際意義是讓Spring 容器內(nèi)的bean可以感知到一些Sping內(nèi)部的屬性,在這里我嘗試使用一個簡單bean去實現(xiàn)BeanNameAware接口,具體代碼如下:
Spring 配置文件:配置文件中僅僅存在兩個簡單bean,分別是AwareDemoOne,和AwareDemoTwo
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="AwareDemoOneId" class="com.liuningyun.pojo.AwareDemoOne"/>
<bean id="AwareDemoTwoId" class="com.liuningyun.pojo.AwareDemoTwo"/>
</beans>
AwareDemoOne和AwareDemoTwo的實現(xiàn):兩個類的屬性都只有簡單的name,age,address三個屬性,同時都重寫了toString(這里重寫toString僅僅是為了后面的日志),唯一的區(qū)別在于AwareDemoTwo實現(xiàn)了BeanNameAware接口,并重寫了setBeanName方法,在setBeanName方法中對name進(jìn)行了一個賦值
public class AwareDemoOne {
private String name;
private String age;
private String address;
//忽略getter/setter
@Override
public String toString() {
return new StringJoiner(", ", AwareDemoOne.class.getSimpleName() + "[", "]")
.add("name='" + name + "'")
.add("age='" + age + "'")
.add("address='" + address + "'")
.toString();
}
}
public class AwareDemoTwo implements BeanNameAware {
private String name;
private String age;
private String address;
//忽略getter/setter
@Override
public void setBeanName(String name) {
this.name = name;
}
@Override
public String toString() {
return new StringJoiner(", ", AwareDemoTwo.class.getSimpleName() + "[", "]")
.add("name='" + name + "'")
.add("age='" + age + "'")
.add("address='" + address + "'")
.toString();
}
}
測試main方法:在main方法中僅僅進(jìn)行了getBean并將兩個awareDemo實例打印出來
public class Demo {
public static void main(String[] args) {
GenericXmlApplicationContext ctx = new GenericXmlApplicationContext();
ctx.load("classpath:/demo-application.xml");
ctx.refresh();
AwareDemoOne awareDemoOne = (AwareDemoOne) ctx.getBean("AwareDemoOneId");
AwareDemoTwo awareDemoTwo = (AwareDemoTwo) ctx.getBean("AwareDemoTwoId");
System.out.println(awareDemoOne);
System.out.println(awareDemoTwo);
}
}
打印結(jié)果:
AwareDemoOne[name='null', age='null', address='null']
AwareDemoTwo[name='AwareDemoTwoId', age='null', address='null']
我們可以看到實現(xiàn)了BeanNameAware接口的AwareDemoTwo實例獲取到了Spring容器內(nèi)的beanId而沒有實現(xiàn)這個接口的AwareDemoOne則打印出了null,所以BeanNameAware在這里的作用就是讓AwareDemoTwo感知到了Spring容器內(nèi)的ID屬性。
在Spring內(nèi)部對于Aware的實現(xiàn)還有比較典型的BeanClassLoaderAware(用來感知bean的類加載器),ApplicationContextAware(用來感知bean的上下文)等等,在這里就不一一去分析了,了解到Aware提供了讓bean感知內(nèi)部屬性的能力就算達(dá)成目標(biāo)了。
所以作者所概括的檢查Spring Awareness階段其實是在bean創(chuàng)建之后檢查是bean是否需要進(jìn)行Spring內(nèi)部屬性的注入,在這個階段如果發(fā)現(xiàn)bean類型有實現(xiàn)某些Aware接口,那么將會在這個階段調(diào)用對應(yīng)的實現(xiàn)來完成一些內(nèi)部屬性的暴露。
分析了Spring Awareness階段下面來看創(chuàng)建bean的生命周期階段,創(chuàng)建bean的生命階段算是比較熟悉的一塊了,如果把bean的生命周期創(chuàng)建階段比喻成女人生孩子,那么@PostConstruct就好比進(jìn)入產(chǎn)房等待分娩的階段,調(diào)用InitializingBean接口的afterPropertiesSet()方法就好比醫(yī)生對小孩剪去臍帶的階段,最后醫(yī)生將小孩抱出來見你的時候就是調(diào)用init-method的階段了,至于調(diào)用destroy-method的階段,我這里就不解釋了,有人云:人固有一X。
還是貼上一段代碼來證實一下:
配置文件:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:conext="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
//這里加入注解配置是因為需要使用到@PostConstruct注解
<conext:annotation-config/>
//注意這里的init-method指定了initMethod方法,啟用lazy-init是為了防止日志打印過早
<bean id="child" class="com.liuningyun.pojo.Child" init-method="initMethod" lazy-init="true"/>
</beans>
Child實例實現(xiàn):
public class Child implements InitializingBean {
private String name;
private String age;
private String address;
//忽略getter/setter
@PostConstruct
public void init(){
System.out.println("小王子誕生了...");
}
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("醫(yī)生剪掉了小王子的臍帶...");
}
public void initMethod(){
System.out.println("小王子被醫(yī)生抱了出來...");
}
可以看到該實例實現(xiàn)了InitializingBean接口,同時實現(xiàn)了afterPropertiesSet()方法,那么按照上面的說法,期望的調(diào)用順序是:init()-》afterPropertiesSet()-》initMethod(),在這里忽略了main方法的代碼,因為就只有一行簡單的getBean觸發(fā)了該實例的加載而已,結(jié)果如下:
小王子誕生了...
23:58:56.818 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Invoking afterPropertiesSet() on bean with name 'child'
醫(yī)生剪掉了小王子的臍帶...
23:58:56.818 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Invoking init method 'initMethod' on bean with name 'child'
小王子被醫(yī)生抱了出來...
23:58:56.819 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Finished creating instance of bean 'child'
所以在bean的整個生命周期中其實有著嚴(yán)格的解析順序,這里摘抄書中一段原話:
所有初始化機(jī)制都可以在同一個bean實例上使用,在這種情況下,Spring首先調(diào)用使用了@PostConstruct注解的方法,然后調(diào)用afterPropertiesSet()方法,最后調(diào)用配置文件中指定的初始化方法。該順序是由一個技術(shù)原因決定的,從圖上我們可以注意到bean在創(chuàng)建過程中主要完成以下步驟:
(1)首先調(diào)用構(gòu)造函數(shù)來創(chuàng)建bean
(2)注入依賴項(調(diào)用setter)
(3)現(xiàn)在bean已經(jīng)存在并提供了依賴項,預(yù)初始化的BeanPostProcessor基礎(chǔ)結(jié)構(gòu)bean將被查詢,以查看它們是否想從創(chuàng)建的bean中調(diào)用任何東西。這些都是特性于Spring的基礎(chǔ)架構(gòu)bean,它們在創(chuàng)建后執(zhí)行bean修改操作,@PosConstruct注解由CommonAnnotaionBeanPostProcessor注冊,所以該bean將調(diào)用使用了@PostConstruct注解的方法,該方法在bean被創(chuàng)建之后,在類被投入使用之前且在bean的實際初始化之前(即在afterPropertiesSet()和init-method方法之前)執(zhí)行
(4)InitializingBean的afterPropertiesSet()方法在注入依賴項后立即執(zhí)行,如果BeanFactory設(shè)置了提供的所有Bean屬性并且滿足BeanFactoryAware和ApplicationContextAware,將會調(diào)用afterPropertiesSet()方法
(5)最后執(zhí)行init-method屬性,這是因為它是bean的實際初始化方法
PostConstruct官方文檔:https://docs.oracle.com/javaee/7/api/javax/annotation/PostConstruct.html
本章對于Spring event的講解非常基礎(chǔ),僅僅只是提到了實現(xiàn)ApplicationListener接口以及MessageEvent就一筆帶過了,待后面詳解的時候我再順便結(jié)合JMS來做SpringEvent的筆記,關(guān)于SpringBoot相關(guān)的也僅僅是講了基礎(chǔ)的配置例如xml配置的lazy-init="true" 等同于@Lazy注解等,而FactoryBean相關(guān)的在第三章的拓展中已經(jīng)分析過了,這里也不再記錄了。