龍行有風,向虛擬機注冊鉤子,實現Bean對象的初始化和銷毀方法
目錄
- 一、前言
- 二、目標
- 三、設計
- 四、實現
- 1. 工程結構
- 2. 定義初始化和銷毀方法的接口
- 3. Bean屬性定義新增初始化和銷毀
- 4. 執行 Bean 對象的初始化方法
- 5. 定義銷毀方法適配器(接口和配置)
- 6. 創建Bean時注冊銷毀方法對象
- 7. 虛擬機關閉鉤子注冊調用銷毀方法
- 五、測試
- 1. 事先準備
- 2. 配置文件
- 3. 單元測試
- 六、總結
- 七、系列推薦
一、前言
有什么方式,能給代碼留條活路?
有人說:人人都是產品經理,那你知道嗎,人人也都可以是碼農程序員!就像:
- 編程就是;定義屬性、創建方法、調用展示
- Java 和 PHP 就像男人和女人,前者在乎架構化模塊后,后者在乎那個顏色我喜歡
- 用心寫,但不要不做格式化
- 初次和產品對接的三個寶:磚頭、鐵鍬、菜刀,分別保證有用、可用、好用
- 從一行代碼到一噸代碼,開發越來越難,壁壘也越來越高
其實學會寫代碼并不難,但學會寫好代碼卻很難。從易閱讀上來說你的代碼要有準確的命名和清晰的注釋、從易使用上來說你的代碼要具備設計模式的包裝讓對外的服務調用更簡單、從易擴展上來說你的代碼要做好業務和功能的實現分層。在易閱讀、易使用、易擴展以及更多編碼規范的約束下,還需要在開發完成上線后的交付結果上滿足;高可用、高性能、高并發,與此同時你還會接到現有項目中層出不窮來自產品經理新增的需求。
怎么辦?知道你在碼磚,不知道你在蓋哪個豬圈!
就算碼的磚是蓋的豬圈,也得因為豬多擴面積、改水槽、加飼料呀,所以根本沒法保證你寫完的代碼就不會加需求。那么新加的需求如果是以破壞了你原有的封裝了非常完美500行的 ifelse 咋辦,拆了重蓋嗎?
兄嘚,給代碼留條活路吧!你的代碼用上了定義接口嗎、接口繼承接口嗎、接口由抽象類實現嗎、類繼承的類實現了接口方法嗎,而這些操作都是為了讓你的程序邏輯做到分層、分區、分塊,把核心邏輯層和業務封裝層做好隔離,當有業務變化時候,只需要做在業務層完成裝配,而底層的核心邏輯服務并不需要頻繁變化,它們所增加的接口也更原子化,不具備業務語意。所以這樣的實現方式才能給你的代碼留條活路。如果還不是太理解,可以多看看《重學Java設計模式》和現在編寫的《手擼Spring》,這里面都有大量的設計模式應用實踐
二、目標
當我們的類創建的 Bean 對象,交給 Spring 容器管理以后,這個類對象就可以被賦予更多的使用能力。就像我們在上一章節已經給類對象添加了修改注冊Bean定義未實例化前的屬性信息修改和實例化過程中的前置和后置處理,這些額外能力的實現,都可以讓我們對現有工程中的類對象做相應的擴展處理。
那么除此之外我們還希望可以在 Bean 初始化過程,執行一些操作。比如幫我們做一些數據的加載執行,鏈接注冊中心暴漏RPC接口以及在Web程序關閉時執行鏈接斷開,內存銷毀等操作。如果說沒有Spring我們也可以通過構造函數、靜態方法以及手動調用的方式實現,但這樣的處理方式終究不如把諸如此類的操作都交給 Spring 容器來管理更加合適。 因此你會看到到 spring.xml 中有如下操作:
需要滿足用戶可以在 xml 中配置初始化和銷毀的方法,也可以通過實現類的方式處理,比如我們在使用 Spring 時用到的 InitializingBean, DisposableBean 兩個接口。-其實還可以有一種是注解的方式處理初始化操作,不過目前還沒有實現到注解的邏輯,后續再完善此類功能。
三、設計
可能面對像 Spring 這樣龐大的框架,對外暴露的接口定義使用或者xml配置,完成的一系列擴展性操作,都讓 Spring 框架看上去很神秘。其實對于這樣在 Bean 容器初始化過程中額外添加的處理操作,無非就是預先執行了一個定義好的接口方法或者是反射調用類中xml中配置的方法,最終你只要按照接口定義實現,就會有 Spring 容器在處理的過程中進行調用而已。整體設計結構如下圖:
- 在 spring.xml 配置中添加 init-method、destroy-method 兩個注解,在配置文件加載的過程中,把注解配置一并定義到 BeanDefinition 的屬性當中。這樣在 initializeBean 初始化操作的工程中,就可以通過反射的方式來調用配置在 Bean 定義屬性當中的方法信息了。另外如果是接口實現的方式,那么直接可以通過 Bean 對象調用對應接口定義的方法即可,((InitializingBean) bean).afterPropertiesSet(),兩種方式達到的效果是一樣的。
- 除了在初始化做的操作外,destroy-method 和 DisposableBean 接口的定義,都會在 Bean 對象初始化完成階段,執行注冊銷毀方法的信息到 DefaultSingletonBeanRegistry 類中的 disposableBeans 屬性里,這是為了后續統一進行操作。這里還有一段適配器的使用,因為反射調用和接口直接調用,是兩種方式。所以需要使用適配器進行包裝,下文代碼講解中參考 DisposableBeanAdapter 的具體實現-關于銷毀方法需要在虛擬機執行關閉之前進行操作,所以這里需要用到一個注冊鉤子的操作,如:Runtime.getRuntime().addShutdownHook(new Thread(() -> System.out.println("close!"))); 這段代碼你可以執行測試,另外你可以使用手動調用 ApplicationContext.close 方法關閉容器。
四、實現
1. 工程結構
- small-spring-step-07
- └── src
- ├── main
- │ └── java
- │ └── cn.bugstack.springframework
- │ ├── beans
- │ │ ├── factory
- │ │ │ ├── factory
- │ │ │ │ ├── AutowireCapableBeanFactory.java
- │ │ │ │ ├── BeanDefinition.java
- │ │ │ │ ├── BeanFactoryPostProcessor.java
- │ │ │ │ ├── BeanPostProcessor.java
- │ │ │ │ ├── BeanReference.java
- │ │ │ │ ├── ConfigurableBeanFactory.java
- │ │ │ │ └── SingletonBeanRegistry.java
- │ │ │ ├── support
- │ │ │ │ ├── AbstractAutowireCapableBeanFactory.java
- │ │ │ │ ├── AbstractBeanDefinitionReader.java
- │ │ │ │ ├── AbstractBeanFactory.java
- │ │ │ │ ├── BeanDefinitionReader.java
- │ │ │ │ ├── BeanDefinitionRegistry.java
- │ │ │ │ ├── CglibSubclassingInstantiationStrategy.java
- │ │ │ │ ├── DefaultListableBeanFactory.java
- │ │ │ │ ├── DefaultSingletonBeanRegistry.java
- │ │ │ │ ├── DisposableBeanAdapter.java
- │ │ │ │ ├── InstantiationStrategy.java
- │ │ │ │ └── SimpleInstantiationStrategy.java
- │ │ │ ├── support
- │ │ │ │ └── XmlBeanDefinitionReader.java
- │ │ │ ├── BeanFactory.java
- │ │ │ ├── ConfigurableListableBeanFactory.java
- │ │ │ ├── DisposableBean.java
- │ │ │ ├── HierarchicalBeanFactory.java
- │ │ │ ├── InitializingBean.java
- │ │ │ └── ListableBeanFactory.java
- │ │ ├── BeansException.java
- │ │ ├── PropertyValue.java
- │ │ └── PropertyValues.java
- │ ├── context
- │ │ ├── support
- │ │ │ ├── AbstractApplicationContext.java
- │ │ │ ├── AbstractRefreshableApplicationContext.java
- │ │ │ ├── AbstractXmlApplicationContext.java
- │ │ │ └── ClassPathXmlApplicationContext.java
- │ │ ├── ApplicationContext.java
- │ │ └── ConfigurableApplicationContext.java
- │ ├── core.io
- │ │ ├── ClassPathResource.java
- │ │ ├── DefaultResourceLoader.java
- │ │ ├── FileSystemResource.java
- │ │ ├── Resource.java
- │ │ ├── ResourceLoader.java
- │ │ └── UrlResource.java
- │ └── utils
- │ └── ClassUtils.java
- └── test
- └── java
- └── cn.bugstack.springframework.test
- ├── bean
- │ ├── UserDao.java
- │ └── UserService.java
- └── ApiTest.java
工程源碼:公眾號「bugstack蟲洞棧」,回復:Spring 專欄,獲取完整源碼
Spring 應用上下文和對Bean對象擴展機制的類關系,如圖 8-4
圖 8-4
- 以上整個類圖結構描述出來的就是本次新增 Bean 實例化過程中的初始化方法和銷毀方法。
- 因為我們一共實現了兩種方式的初始化和銷毀方法,xml配置和定義接口,所以這里既有 InitializingBean、DisposableBean 也有需要 XmlBeanDefinitionReader 加載 spring.xml 配置信息到 BeanDefinition 中。
- 另外接口 ConfigurableBeanFactory 定義了 destroySingletons 銷毀方法,并由 AbstractBeanFactory 繼承的父類 DefaultSingletonBeanRegistry 實現 ConfigurableBeanFactory 接口定義的 destroySingletons 方法。這種方式的設計可能數程序員是沒有用過的,都是用的誰實現接口誰完成實現類,而不是把實現接口的操作又交給繼承的父類處理。所以這塊還是蠻有意思的,是一種不錯的隔離分層服務的設計方式
- 最后就是關于向虛擬機注冊鉤子,保證在虛擬機關閉之前,執行銷毀操作。Runtime.getRuntime().addShutdownHook(new Thread(() -> System.out.println("close!")));
2. 定義初始化和銷毀方法的接口
cn.bugstack.springframework.beans.factory.InitializingBean
- public interface InitializingBean {
- /**
- * Bean 處理了屬性填充后調用
- *
- * @throws Exception
- */
- void afterPropertiesSet() throws Exception;
- }
cn.bugstack.springframework.beans.factory.DisposableBean
- public interface DisposableBean {
- void destroy() throws Exception;
- }
- InitializingBean、DisposableBean,兩個接口方法還是比較常用的,在一些需要結合 Spring 實現的組件中,經常會使用這兩個方法來做一些參數的初始化和銷毀操作。比如接口暴漏、數據庫數據讀取、配置文件加載等等。
3. Bean屬性定義新增初始化和銷毀
cn.bugstack.springframework.beans.factory.config.BeanDefinition
- public class BeanDefinition {
- private Class beanClass;
- private PropertyValues propertyValues;
- private String initMethodName;
- private String destroyMethodName;
- // ...get/set
- }
- 在 BeanDefinition 新增加了兩個屬性:initMethodName、destroyMethodName,這兩個屬性是為了在 spring.xml 配置的 Bean 對象中,可以配置 init-method="initDataMethod" destroy-method="destroyDataMethod" 操作,最終實現接口的效果是一樣的。只不過一個是接口方法的直接調用,另外是一個在配置文件中讀取到方法反射調用
4. 執行 Bean 對象的初始化方法
cn.bugstack.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory
- public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFactory implements AutowireCapableBeanFactory {
- private InstantiationStrategy instantiationStrategy = new CglibSubclassingInstantiationStrategy();
- @Override
- protected Object createBean(String beanName, BeanDefinition beanDefinition, Object[] args) throws BeansException {
- Object bean = null;
- try {
- bean = createBeanInstance(beanDefinition, beanName, args);
- // 給 Bean 填充屬性
- applyPropertyValues(beanName, bean, beanDefinition);
- // 執行 Bean 的初始化方法和 BeanPostProcessor 的前置和后置處理方法
- bean = initializeBean(beanName, bean, beanDefinition);
- } catch (Exception e) {
- throw new BeansException("Instantiation of bean failed", e);
- }
- // ...
- addSingleton(beanName, bean);
- return bean;
- }
- private Object initializeBean(String beanName, Object bean, BeanDefinition beanDefinition) {
- // 1. 執行 BeanPostProcessor Before 處理
- Object wrappedBean = applyBeanPostProcessorsBeforeInitialization(bean, beanName);
- // 執行 Bean 對象的初始化方法
- try {
- invokeInitMethods(beanName, wrappedBean, beanDefinition);
- } catch (Exception e) {
- throw new BeansException("Invocation of init method of bean[" + beanName + "] failed", e);
- }
- // 2. 執行 BeanPostProcessor After 處理
- wrappedBean = applyBeanPostProcessorsAfterInitialization(bean, beanName);
- return wrappedBean;
- }
- private void invokeInitMethods(String beanName, Object bean, BeanDefinition beanDefinition) throws Exception {
- // 1. 實現接口 InitializingBean
- if (bean instanceof InitializingBean) {
- ((InitializingBean) bean).afterPropertiesSet();
- }
- // 2. 配置信息 init-method {判斷是為了避免二次執行銷毀}
- String initMethodName = beanDefinition.getInitMethodName();
- if (StrUtil.isNotEmpty(initMethodName)) {
- Method initMethod = beanDefinition.getBeanClass().getMethod(initMethodName);
- if (null == initMethod) {
- throw new BeansException("Could not find an init method named '" + initMethodName + "' on bean with name '" + beanName + "'");
- }
- initMethod.invoke(bean);
- }
- }
- }
- 抽象類 AbstractAutowireCapableBeanFactory 中的 createBean 是用來創建 Bean 對象的方法,在這個方法中我們之前已經擴展了 BeanFactoryPostProcessor、BeanPostProcessor 操作,這里我們繼續完善執行 Bean 對象的初始化方法的處理動作。
- 在方法 invokeInitMethods 中,主要分為兩塊來執行實現了 InitializingBean 接口的操作,處理 afterPropertiesSet 方法。另外一個是判斷配置信息 init-method 是否存在,執行反射調用 initMethod.invoke(bean)。這兩種方式都可以在 Bean 對象初始化過程中進行處理加載 Bean 對象中的初始化操作,讓使用者可以額外新增加自己想要的動作。
5. 定義銷毀方法適配器(接口和配置)
cn.bugstack.springframework.beans.factory.support.DisposableBeanAdapter
- public class DisposableBeanAdapter implements DisposableBean {
- private final Object bean;
- private final String beanName;
- private String destroyMethodName;
- public DisposableBeanAdapter(Object bean, String beanName, BeanDefinition beanDefinition) {
- this.bean = bean;
- this.beanName = beanName;
- this.destroyMethodName = beanDefinition.getDestroyMethodName();
- }
- @Override
- public void destroy() throws Exception {
- // 1. 實現接口 DisposableBean
- if (bean instanceof DisposableBean) {
- ((DisposableBean) bean).destroy();
- }
- // 2. 配置信息 destroy-method {判斷是為了避免二次執行銷毀}
- if (StrUtil.isNotEmpty(destroyMethodName) && !(bean instanceof DisposableBean && "destroy".equals(this.destroyMethodName))) {
- Method destroyMethod = bean.getClass().getMethod(destroyMethodName);
- if (null == destroyMethod) {
- throw new BeansException("Couldn't find a destroy method named '" + destroyMethodName + "' on bean with name '" + beanName + "'");
- }
- destroyMethod.invoke(bean);
- }
- }
- }
- 可能你會想這里怎么有一個適配器的類呢,因為銷毀方法有兩種甚至多種方式,目前有實現接口 DisposableBean、配置信息 destroy-method,兩種方式。而這兩種方式的銷毀動作是由 AbstractApplicationContext 在注冊虛擬機鉤子后看,虛擬機關閉前執行的操作動作。
- 那么在銷毀執行時不太希望還得關注都銷毀那些類型的方法,它的使用上更希望是有一個統一的接口進行銷毀,所以這里就新增了適配類,做統一處理。
6. 創建Bean時注冊銷毀方法對象
cn.bugstack.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory
- public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFactory implements AutowireCapableBeanFactory {
- private InstantiationStrategy instantiationStrategy = new CglibSubclassingInstantiationStrategy();
- @Override
- protected Object createBean(String beanName, BeanDefinition beanDefinition, Object[] args) throws BeansException {
- Object bean = null;
- try {
- bean = createBeanInstance(beanDefinition, beanName, args);
- // 給 Bean 填充屬性
- applyPropertyValues(beanName, bean, beanDefinition);
- // 執行 Bean 的初始化方法和 BeanPostProcessor 的前置和后置處理方法
- bean = initializeBean(beanName, bean, beanDefinition);
- } catch (Exception e) {
- throw new BeansException("Instantiation of bean failed", e);
- }
- // 注冊實現了 DisposableBean 接口的 Bean 對象
- registerDisposableBeanIfNecessary(beanName, bean, beanDefinition);
- addSingleton(beanName, bean);
- return bean;
- }
- protected void registerDisposableBeanIfNecessary(String beanName, Object bean, BeanDefinition beanDefinition) {
- if (bean instanceof DisposableBean || StrUtil.isNotEmpty(beanDefinition.getDestroyMethodName())) {
- registerDisposableBean(beanName, new DisposableBeanAdapter(bean, beanName, beanDefinition));
- }
- }
- }
在創建 Bean 對象的實例的時候,需要把銷毀方法保存起來,方便后續執行銷毀動作進行調用。
那么這個銷毀方法的具體方法信息,會被注冊到 DefaultSingletonBeanRegistry 中新增加的 Map
在注冊銷毀方法的時候,會根據是接口類型和配置類型統一交給 DisposableBeanAdapter 銷毀適配器類來做統一處理。實現了某個接口的類可以被 instanceof 判斷或者強轉后調用接口方法
7. 虛擬機關閉鉤子注冊調用銷毀方法
cn.bugstack.springframework.context.ConfigurableApplicationContext
- public interface ConfigurableApplicationContext extends ApplicationContext {
- void refresh() throws BeansException;
- void registerShutdownHook();
- void close();
- }
- 首先我們需要在 ConfigurableApplicationContext 接口中定義注冊虛擬機鉤子的方法 registerShutdownHook 和手動執行關閉的方法 close。
cn.bugstack.springframework.context.support.AbstractApplicationContext
- public abstract class AbstractApplicationContext extends DefaultResourceLoader implements ConfigurableApplicationContext {
- // ...
- @Override
- public void registerShutdownHook() {
- Runtime.getRuntime().addShutdownHook(new Thread(this::close));
- }
- @Override
- public void close() {
- getBeanFactory().destroySingletons();
- }
- }
- 這里主要體現了關于注冊鉤子和關閉的方法實現,上文提到過的 Runtime.getRuntime().addShutdownHook,可以嘗試驗證。在一些中間件和監控系統的設計中也可以用得到,比如監測服務器宕機,執行備機啟動操作。
五、測試
1. 事先準備
cn.bugstack.springframework.test.bean.UserDao
- public class UserDao {
- private static Map<String, String> hashMap = new HashMap<>();
- public void initDataMethod(){
- System.out.println("執行:init-method");
- hashMap.put("10001", "小傅哥");
- hashMap.put("10002", "八杯水");
- hashMap.put("10003", "阿毛");
- }
- public void destroyDataMethod(){
- System.out.println("執行:destroy-method");
- hashMap.clear();
- }
- public String queryUserName(String uId) {
- return hashMap.get(uId);
- }
- }
cn.bugstack.springframework.test.bean.UserService
- public class UserService implements InitializingBean, DisposableBean {
- private String uId;
- private String company;
- private String location;
- private UserDao userDao;
- @Override
- public void destroy() throws Exception {
- System.out.println("執行:UserService.destroy");
- }
- @Override
- public void afterPropertiesSet() throws Exception {
- System.out.println("執行:UserService.afterPropertiesSet");
- }
- // ...get/set
- }
UserDao,修改了之前使用 static 靜態塊初始化數據的方式,改為提供 initDataMethod 和 destroyDataMethod 兩個更優雅的操作方式進行處理。
UserService,以實現接口 InitializingBean, DisposableBean 的兩個方法 destroy()、afterPropertiesSet(),處理相應的初始化和銷毀方法的動作。afterPropertiesSet,方法名字很好,在屬性設置后執行
2. 配置文件
基礎配置,無BeanFactoryPostProcessor、BeanPostProcessor,實現類
- <?xml version="1.0" encoding="UTF-8"?>
- <beans>
- <bean id="userDao" class="cn.bugstack.springframework.test.bean.UserDao" init-method="initDataMethod" destroy-method="destroyDataMethod"/>
- <bean id="userService" class="cn.bugstack.springframework.test.bean.UserService">
- <property name="uId" value="10001"/>
- <property name="company" value="騰訊"/>
- <property name="location" value="深圳"/>
- <property name="userDao" ref="userDao"/>
- </bean>
- </beans>
- 配置文件中主要是新增了,init-method="initDataMethod" destroy-method="destroyDataMethod",這樣兩個配置。從源碼的學習中可以知道,這兩個配置是為了加入到 BeanDefinition 定義類之后寫入到類 DefaultListableBeanFactory 中的 beanDefinitionMap 屬性中去。
3. 單元測試
- @Test
- public void test_xml() {
- // 1.初始化 BeanFactory
- ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:spring.xml");
- applicationContext.registerShutdownHook();
- // 2. 獲取Bean對象調用方法
- UserService userService = applicationContext.getBean("userService", UserService.class);
- String result = userService.queryUserInfo();
- System.out.println("測試結果:" + result);
- }
- 測試方法中新增加了一個,注冊鉤子的動作。applicationContext.registerShutdownHook();
測試結果
執行:init-method
執行:UserService.afterPropertiesSet
測試結果:小傅哥,騰訊,深圳
執行:UserService.destroy
執行:destroy-method
Process finished with exit code 0
- 從測試結果可以看到,我們的新增加的初始和銷毀方法已經可以如期輸出結果了。
六、總結
- 本文主要完成了關于初始和銷毀在使用接口定義 implements InitializingBean, DisposableBean 和在spring.xml中配置 init-method="initDataMethod" destroy-method="destroyDataMethod" 的兩種具體在 AbstractAutowireCapableBeanFactory 完成初始方法和 AbstractApplicationContext 處理銷毀動作的具體實現過程。
- 通過本文的實現內容,可以看到目前這個 Spring 框架對 Bean 的操作越來越完善了,可擴展性也不斷的增強。你既可以在Bean注冊完成實例化前進行 BeanFactoryPostProcessor 操作,也可以在Bean實例化過程中執行前置和后置操作,現在又可以執行Bean的初始化方法和銷毀方法。所以一個簡單的Bean對象,已經被賦予了各種擴展能力。
- 在學習和動手實踐 Spring 框架學習的過程中,特別要注意的是它對接口和抽象類的把握和使用,尤其遇到類似,A繼承B實現C時,C的接口方法由A繼承的父類B實現,這樣的操作都蠻有意思的。也是可以復用到通常的業務系統開發中進行處理一些復雜邏輯的功能分層,做到程序的可擴展、易維護等特性。