Spring 8:一些常用的 Spring Bean 擴充套件介面
前言
Spring是一款非常強大的框架,可以說是幾乎所有的企業級Java專案使用了Spring,而Bean又是Spring框架的核心。
Spring框架運用了非常多的設計模式,從整體上看,它的設計嚴格遵循了OCP—-開閉原則,即:
1、保證對修改關閉,即外部無法修改Spring整個運作的流程
2、提供對擴充套件開放,即可以通過繼承、實現Spring提供的眾多抽象類與介面來改變類載入的行為
開卷有益,閱讀Spring原始碼(無需每個類都看得很細,大體流程能梳理出來即可)對於個人水平的提升是幫助非常大的,同時也能在工作中即使發現和解決一些不常見的Spring問題。
不過,本文的目的不是整理Spring的流程,而是通過介紹一些常用的Spring Bean工具類,來讓我們可以更好地使用Spring提供給開發者的多種特性,下面讓我們開始吧。
InitialingBean和DisposableBean
InitialingBean是一個介面,提供了一個唯一的方法afterPropertiesSet()。
DisposableBean也是一個介面,提供了一個唯一的方法destory()。
這兩個介面是一組的,功能類似,因此放在一起:前者顧名思義在Bean屬性都設定完畢後呼叫afterPropertiesSet()方法做一些初始化的工作,後者在Bean生命週期結束前呼叫destory()方法做一些收尾工作。下面看一下例子,為了能明確地知道afterPropertiesSet()方法的呼叫時機,加上一個屬性,給屬性set方法,在set方法中列印一些內容:
/** * @author 五月的倉頡 http://www.cnblogs.com/xrq730/p/5721366.html */ public class LifecycleBean implements InitializingBean, DisposableBean { @SuppressWarnings("unused") private String lifeCycleBeanName; public void setLifeCycleBeanName(String lifeCycleBeanName) { System.out.println("Enter LifecycleBean.setLifeCycleBeanName(), lifeCycleBeanName = " + lifeCycleBeanName); this.lifeCycleBeanName = lifeCycleBeanName; } public void destroy() throws Exception { System.out.println("Enter LifecycleBean.destroy()"); } public void afterPropertiesSet() throws Exception { System.out.println("Enter LifecycleBean.afterPropertiesSet()"); } public void beanStart() { System.out.println("Enter LifecycleBean.beanStart()"); } public void beanEnd() { System.out.println("Enter LifecycleBean.beanEnd()"); } }
配置一個spring.xml:
<?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-4.1.xsd"> <bean id="lifeCycleBean" class="org.xrq.bean.lifecycle.LifecycleBean"> <property name="lifeCycleBeanName" value="lifeCycleBean" /> </bean> </beans>
啟動Spring容器,LifecycleBean執行的結果為:
Enter LifecycleBean.setLifeCycleBeanName(), lifeCycleBeanName = lifeCycleBean Enter LifecycleBean.afterPropertiesSet() Enter LifecycleBean.beanStart() Enter LifecycleBean.destroy() Enter LifecycleBean.beanEnd()
執行結果和我們想的一樣,afterPropertiesSet()方法就如同它的名字所表示的那樣,是在Bean的屬性都被設定完畢之後,才會呼叫。
關於這兩個介面,我總結幾點:
1、InitializingBean介面、Disposable介面可以和init-method、destory-method配合使用,介面執行順序優先於配置
2、InitializingBean介面、Disposable介面底層使用型別強轉.方法名()進行直接方法呼叫,init-method、destory-method底層使用反射,前者和Spring耦合程度更高但效率高,後者解除了和Spring之間的耦合但是效率低,使用哪個看個人喜好
3、afterPropertiesSet()方法是在Bean的屬性設定之後才會進行呼叫,某個Bean的afterPropertiesSet()方法執行完畢才會執行下一個Bean的afterPropertiesSet()方法,因此不建議在afterPropertiesSet()方法中寫處理時間太長的方法
BeanNameAware、ApplicationContextAware和BeanFactoryAware
這三個介面放在一起寫,是因為它們是一組的,作用相似。
“Aware”的意思是”感知到的”,那麼這三個介面的意思也不難理解:
1、實現BeanNameAware介面的Bean,在Bean載入的過程中可以獲取到該Bean的id
2、實現ApplicationContextAware介面的Bean,在Bean載入的過程中可以獲取到Spring的ApplicationContext,這個尤其重要,ApplicationContext是Spring應用上下文,從ApplicationContext中可以獲取包括任意的Bean在內的大量Spring容器內容和資訊
3、實現BeanFactoryAware介面的Bean,在Bean載入的過程中可以獲取到載入該Bean的BeanFactory
看一下例子:
/** * @author 五月的倉頡 http://www.cnblogs.com/xrq730/p/5721366.html */ public class AwareBean implements BeanNameAware, BeanFactoryAware, ApplicationContextAware { private String beanName; private ApplicationContext applicationContext; private BeanFactory beanFactory; public void setBeanName(String beanName) { System.out.println("Enter AwareBean.setBeanName(), beanName = " + beanName + "\n"); this.beanName = beanName; } public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { System.out.println("Enter AwareBean.setApplicationContext(), applicationContext = " + applicationContext + "\n"); this.applicationContext = applicationContext; } public void setBeanFactory(BeanFactory beanFactory) throws BeansException { System.out.println("Enter AwareBean.setBeanFactory(), beanfactory = " + beanFactory + "\n"); this.beanFactory = beanFactory; } }
配置一個Spring.xml:
<?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-4.1.xsd"> <bean id="AwareBean" class="org.xrq.bean.aware.AwareBean" /> </beans>
啟動Spring容器後的執行結果為:
Enter AwareBean.setBeanName(), beanName = AwareBean Enter AwareBean.setBeanFactory(), beanfactory = org.springframework.beans.factory.support.DefaultListableBeanFactory@2747fda0: defining beans [AwareBean,org.springframework.context.annotation.internalConfigurationAnnotationProcessor,org.springframework.context.annotation.internalAutowiredAnnotationProcessor,org.springframework.context.annotation.internalRequiredAnnotationProcessor,org.springframework.context.annotation.internalCommonAnnotationProcessor,org.springframework.context.annotation.ConfigurationClassPostProcessor.importAwareProcessor,org.springframework.context.annotation.ConfigurationClassPostProcessor.enhancedConfigurationProcessor]; root of factory hierarchy Enter AwareBean.setApplicationContext(), applicationContext = org.springframework.context.support.GenericApplicationContext@5514cd80: startup date [Mon Aug 08 19:23:30 CST 2016]; root of context hierarchy
關於這三個介面以及上面的列印資訊,總結幾點:
1、如果你的BeanName、ApplicationContext、BeanFactory有用,那麼就自己定義一個變數將它們儲存下來,如果沒用,那麼只需要實現setXXX()方法,用一下Spring注入進來的引數即可
2、如果Bean同時還實現了InitializingBean,容器會保證BeanName、ApplicationContext和BeanFactory在呼叫afterPropertiesSet()方法被注入
FactoryBean
FactoryBean在Spring中是非常有用的,使用Eclipse/MyEclipse的朋友可以對FactoryBean使用ctrl+t檢視一下,FactoryBean這個介面在Spring容器中有大量的子實現。
傳統的Spring容器載入一個Bean的整個過程,都是由Spring控制的,換句話說,開發者除了設定Bean相關屬性之外,是沒有太多的自主權的。FactoryBean改變了這一點,開發者可以個性化地定製自己想要例項化出來的Bean,方法就是實現FactoryBean介面。
看一下程式碼例子,為了講清楚FactoryBean,內容相對多一些,首先定義一個介面Animal:
public interface Animal { public void move(); }
定義兩個實現類Monkey和Tiger:
public class Monkey implements Animal { public void move() { System.out.println("Monkey move!"); } }
public class Tiger implements Animal { public void move() { System.out.println("Tiger move!"); } }
寫一個實現類,實現FactoryBean介面:
/** * @author 五月的倉頡 http://www.cnblogs.com/xrq730/p/5721366.html */ public class AnimalFactoryBean implements FactoryBean<Animal> { private String animal; public Animal getObject() throws Exception { if ("Monkey".equals(animal)) { return new Monkey(); } else if ("Tiger".equals(animal)) { return new Tiger(); } else { return null; } } public Class<?> getObjectType() { return Animal.class; } public boolean isSingleton() { return true; } public void setAnimal(String animal) { this.animal = animal; } }
配置一個spring.xml,注入屬性Tiger:
<?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-4.1.xsd"> <bean id="animal" class="org.xrq.bean.factory.AnimalFactoryBean"> <property name="animal" value="Tiger"/> </bean> </beans>
寫一個JUnit的測試類:
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations = { "classpath*:spring.xml", }) public class BaseTest { @Resource private Animal animal; @Test public void aa() { animal.move(); } }
檢視一下執行結果:
Tiger move!
看到最後得到的並不是FactoryBean本身,而是FactoryBean的泛型物件,這就是FactoryBean的作用。FactoryBean的幾個方法:
1、getObject()方法是最重要的,控制Bean的例項化過程
2、getObjectType()方法獲取介面返回的例項的class
3、isSingleton()方法獲取該Bean是否為一個單例的Bean
像我這段程式碼的功能就是傳入一個String型別的引數,可以動態控制生成出來的是介面的哪種子類。有了FactoryBean,同樣的我們也可以靈活地操控Bean的生成。
BeanPostProcessor
之前的InitializingBean、DisposableBean、FactoryBean包括init-method和destory-method,針對的都是某個Bean控制其初始化的操作,而似乎沒有一種辦法可以針對每個Bean的生成前後做一些邏輯操作,PostProcessor則幫助我們做到了這一點,先看一個簡單的BeanPostProcessor。
網上有一張圖畫了Bean生命週期的過程,畫得挺好,原圖出處:
BeanPostProcess介面有兩個方法,都可以見名知意:
1、postProcessBeforeInitialization:在初始化Bean之前
2、postProcessAfterInitialization:在初始化Bean之後
值得注意的是,這兩個方法是有返回值的,不要返回null,否則getBean的時候拿不到物件。
寫一段測試程式碼,首先定義一個普通的Bean,為了後面能區分,給Bean加一個屬性:
public class CommonBean { private String commonName; public void setCommonName(String commonName) { this.commonName = commonName; } public void initMethod() { System.out.println("Enter CommonBean.initMethod(), commonName = " + commonName); } }
定義一個PostProcess,實現BeanPostProcess介面:
/** * @author 五月的倉頡 http://www.cnblogs.com/xrq730/p/5721366.html */ public class PostProcessorBean implements BeanPostProcessor { public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { System.out.println("Enter ProcessorBean.postProcessAfterInitialization()\n"); return bean; } public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { System.out.println("Enter ProcessorBean.postProcessBeforeInitialization()"); return bean; } }
配置一個spring.xml,給CommonBean的commonName賦予不同的值以區分:
<?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-4.1.xsd"> <bean id="common0" class="org.xrq.bean.common.CommonBean" init-method="initMethod"> <property name="commonName" value="common0"/> </bean> <bean id="common1" class="org.xrq.bean.common.CommonBean" init-method="initMethod"> <property name="commonName" value="common1"/> </bean> <bean id="postProcessorBean" class="org.xrq.bean.processor.PostProcessorBean" /> </beans>
執行一個Spring容器, 初始化結果為:
Enter ProcessorBean.postProcessBeforeInitialization() Enter CommonBean.initMethod(), commonName = common0 Enter ProcessorBean.postProcessAfterInitialization() Enter ProcessorBean.postProcessBeforeInitialization() Enter CommonBean.initMethod(), commonName = common1 Enter ProcessorBean.postProcessAfterInitialization() Enter ProcessorBean.postProcessBeforeInitialization() Enter ProcessorBean.postProcessAfterInitialization()
看到每個Bean初始化前後都會分別執行postProcessorBeforeInitiallization()方法與postProcessorAfterInitialization()方法,最後兩行出現原因是,PostProcessorBean本身也是一個Bean。
BeanFactoryPostProcessor
接下來看另外一個PostProcessor—-BeanFactoryPostProcessor。
Spring允許在Bean建立之前,讀取Bean的元屬性,並根據自己的需求對元屬性進行改變,比如將Bean的scope從singleton改變為prototype,最典型的應用應當是PropertyPlaceholderConfigurer,替換xml檔案中的佔位符,替換為properties檔案中相應的key對應的value,這將會在下篇文章中專門講解PropertyPlaceholderConfigurer的作用及其原理。
BeanFactoryPostProcessor就可以幫助我們實現上述的功能,下面來看一下BeanFactoryPostProcessor的使用,定義一個BeanFactoryPostProcessor的實現類:
/** * @author 五月的倉頡 http://www.cnblogs.com/xrq730/p/5721366.html */ public class FactoryPostProcessorBean implements BeanFactoryPostProcessor { public void postProcessBeanFactory(ConfigurableListableBeanFactory configurablelistablebeanfactory) throws BeansException { System.out.println("Enter FactoryPostProcessorBean.postProcessBeanFactory()\n"); } }
spring.xml裡面配置一下這個Bean,就不寫了,執行一下Spring容器,結果為:
Enter FactoryPostProcessorBean.postProcessBeanFactory() Enter ProcessorBean.postProcessBeforeInitialization() Enter CommonBean.initMethod(), commonName = common0 Enter ProcessorBean.postProcessAfterInitialization() Enter ProcessorBean.postProcessBeforeInitialization() Enter CommonBean.initMethod(), commonName = common1 Enter ProcessorBean.postProcessAfterInitialization() Enter ProcessorBean.postProcessBeforeInitialization() Enter ProcessorBean.postProcessAfterInitialization()
從執行結果中可以看出兩點:
1、BeanFactoryPostProcessor的執行優先順序高於BeanPostProcessor
2、BeanFactoryPostProcessor的postProcessBeanFactory()方法只會執行一次
注意到postProcessBeanFactory方法是帶了引數ConfigurableListableBeanFactory的,這就和我之前說的可以使用BeanFactoryPostProcessor來改變Bean的屬性相對應起來了。ConfigurableListableBeanFactory功能非常豐富,最基本的,它攜帶了每個Bean的基本資訊,比如我簡單寫一段程式碼:
/** * @author 五月的倉頡 http://www.cnblogs.com/xrq730/p/5721366.html */ public void postProcessBeanFactory(ConfigurableListableBeanFactory configurablelistablebeanfactory) throws BeansException { BeanDefinition beanDefinition = configurablelistablebeanfactory.getBeanDefinition("common0"); MutablePropertyValues beanProperty = beanDefinition.getPropertyValues(); System.out.println("scope before change:" + beanDefinition.getScope()); beanDefinition.setScope("singleton"); System.out.println("scope after change:" + beanDefinition.getScope()); System.out.println("beanProperty:" + beanProperty); }
看一下執行結果:
scope before change: scope after change:singleton beanProperty:PropertyValues: length=1; bean property 'commonName'
這樣就獲取了Bean的生命週期以及重新設定了Bean的生命週期。ConfigurableListableBeanFactory還有很多的功能,比如新增BeanPostProcessor,可以自己去檢視。
InstantiationAwareBeanPostProcessor
最後寫一個叫做InstantiationAwareBeanPostProcessor的PostProcessor。
InstantiationAwareBeanPostProcessor又代表了Spring的另外一段生命週期:例項化。先區別一下Spring Bean的例項化和初始化兩個階段的主要作用:
1、例項化—-例項化的過程是一個建立Bean的過程,即呼叫Bean的建構函式,單例的Bean放入單例池中
2、初始化—-初始化的過程是一個賦值的過程,即呼叫Bean的setter,設定Bean的屬性
之前的BeanPostProcessor作用於過程(2)前後,現在的InstantiationAwareBeanPostProcessor則作用於過程(1)前後,看一下程式碼,給前面的CommonBean加上建構函式:
public class CommonBean { public CommonBean() { System.out.println("Enter CommonBean's constructor"); } private String commonName; public void setCommonName(String commonName) { System.out.println("Enter CommonBean.setCommonName(), commonName = " + commonName); this.commonName = commonName; } public void initMethod() { System.out.println("Enter CommonBean.initMethod(), commonName = " + commonName); } }
實現InstantiationAwareBeanPostProcessor介面:
/** * @author 五月的倉頡 http://www.cnblogs.com/xrq730/p/5721366.html */ public class InstantiationAwareBeanPostProcessorBean implements InstantiationAwareBeanPostProcessor { public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { System.out.println("Enter InstantiationAwareBeanPostProcessorBean.postProcessAfterInitialization()"); return bean; } public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { System.out.println("Enter InstantiationAwareBeanPostProcessorBean.postProcessBeforeInitialization()"); return bean; } public boolean postProcessAfterInstantiation(Object bean, String beanName) throws BeansException { System.out.println("Enter InstantiationAwareBeanPostProcessorBean.postProcessAfterInstantiation()"); return true; } public Object postProcessBeforeInstantiation(Class<?> bean, String beanName) throws BeansException { System.out.println("Enter InstantiationAwareBeanPostProcessorBean.postProcessBeforeInstantiation()"); return null; } public PropertyValues postProcessPropertyValues(PropertyValues pvs, PropertyDescriptor[] pd, Object bean, String beanName) throws BeansException { return pvs; } }
配置一下spring.xml:
<?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-4.1.xsd"> <bean id="common" class="org.xrq.bean.common.CommonBean"> <property name="commonName" value="common"/> </bean> <bean class="org.xrq.bean.processor.InstantiationAwareBeanPostProcessorBean" /> </beans>
啟動容器,觀察一下執行結果為:
Enter InstantiationAwareBeanPostProcessorBean.postProcessBeforeInstantiation() Enter CommonBean's constructor Enter InstantiationAwareBeanPostProcessorBean.postProcessAfterInstantiation() Enter CommonBean.setCommonName(), commonName = common Enter InstantiationAwareBeanPostProcessorBean.postProcessBeforeInitialization() Enter InstantiationAwareBeanPostProcessorBean.postProcessAfterInitialization() Enter InstantiationAwareBeanPostProcessorBean.postProcessAfterInstantiation() Enter InstantiationAwareBeanPostProcessorBean.postProcessBeforeInitialization() Enter InstantiationAwareBeanPostProcessorBean.postProcessAfterInitialization()
最後三行的執行結果不去關注,看到很明顯的,InstantiationAwareBeanPostProcessor作用的是Bean例項化前後,即:
1、Bean構造出來之前呼叫postProcessBeforeInstantiation()方法
2、Bean構造出來之後呼叫postProcessAfterInstantiation()方法
不過通常來講,我們不會直接實現InstantiationAwareBeanPostProcessor介面,而是會採用繼承InstantiationAwareBeanPostProcessorAdapter這個抽象類的方式來使用。
後記
如果只會寫個Bean,配置在xml檔案裡面,注入一下,那是最最基礎的Spring開發者。一箇中級、高階的Spring開發者,必然會對Spring中的多個擴充套件點有所瞭解,並利用這些擴充套件點更好地為專案服務,使得整個程式碼結構更加地優雅,並且可讀性、可維護性更好。
拋磚引玉,本文只是簡單地介紹一些常用的Spring Bean擴充套件介面以及它們的簡單用法,更深入的或者它們一些合適的使用場景,還需要留待網友朋友們自己去探索。
我不能保證寫的每個地方都是對的,但是至少能保證不復制、不黏貼,保證每一句話、每一行程式碼都經過了認真的推敲、仔細的斟酌。每一篇文章的背後,希望都能看到自己對於技術、對於生活的態度。我相信賈伯斯說的,只有那些瘋狂到認為自己可以改變世界的人才能真正地改變世界。面對壓力,我可以挑燈夜戰、不眠不休;面對困難,我願意迎難而上、永不退縮。其實我想說的是,我只是一個程式設計師,這就是我現在純粹人生的全部。
相關文章
- Spring8:一些常用的Spring Bean擴充套件介面SpringBean套件
- spring bean 擴充套件方式SpringBean套件
- Spring擴充套件介面(2):BeanDefinitionRegistryPostProcessorSpring套件Bean
- spring4.1.8擴充套件實戰之七:控制bean(BeanPostProcessor介面)Spring套件Bean
- Spring擴充套件介面(4):InstantiationAwareBeanPostProcessorSpring套件Bean
- spring4.1.8擴充套件實戰之五:改變bean的定義(BeanFactoryPostProcessor介面)Spring套件Bean
- Spring IoC 容器的擴充套件Spring套件
- spring4.1.8擴充套件實戰之六:註冊bean(BeanDefinitionRegistryPostProcessor)Spring套件Bean
- 擴充套件Spring——使用 Annotation將配置資源注入到Bean中套件SpringBean
- Spring容器擴充套件機制Spring套件
- 使用Kotlin擴充套件函式擴充套件Spring Data案例Kotlin套件函式Spring
- 聊聊spring的那些擴充套件機制Spring套件
- Spring擴充套件之二:ApplicationListenerSpring套件APP
- 使用Spring Session實現Spring Boot水平擴充套件SessionSpring Boot套件
- Spring中11個最常用的擴充套件點,你知道幾個?Spring套件
- 精盡Spring Boot原始碼分析 - Condition 介面的擴充套件Spring Boot原始碼套件
- spring4.1.8擴充套件實戰之二:Aware介面揭祕Spring套件
- 聊聊Spring中的那些擴充套件機制Spring套件
- Spring 中的 XML schema 擴充套件機制SpringXML套件
- 深入理解Spring 之 Spring 進階開發必知必會 之 Spring 擴充套件介面Spring套件
- spring4.1.8擴充套件實戰之四:感知spring容器變化(SmartLifecycle介面)Spring套件
- 譯 - Spring 核心技術之 Spring 容器擴充套件點Spring套件
- Spring系列-XML schema擴充套件機制SpringXML套件
- 聊聊Spring擴充套件點BeanPostProcessor和BeanFactoryPostProcessorSpring套件Bean
- Spring Cloud Gateway 擴充套件支援動態限流SpringCloudGateway套件
- 深入理解Spring IOC容器及擴充套件Spring套件
- Sentinel 的一些小擴充套件套件
- 分享一些好用的資源(擴充套件、介面、網站)套件網站
- Gorm 的使用心得和一些常用擴充套件 (一)GoORM套件
- 聊一聊 Spring 中的擴充套件機制(一)Spring套件
- Spring JPA 擴充Spring
- 聊聊 Spring 的 XML Schema 擴充套件機制的使用方式SpringXML套件
- Mybatis外掛擴充套件以及與Spring整合原理MyBatis套件Spring
- BeanDefinition註冊流程、spring 擴充套件點一(NamespaceHandler)BeanSpring套件namespace
- Spring(11) - Introductions進行類擴充套件方法Spring套件
- GNOME Shell Extension常用擴充套件套件
- Spring Boot中的Mongodb多資料來源擴充套件Spring BootMongoDB套件
- 聊一聊 Spring 中的擴充套件機制(二) - NamespaceHandlerSpring套件namespace