以前 Spring 源碼系列文章中大可能是底層源碼的分析,經過源碼可讓咱們可以清晰的瞭解 Spring 究竟是什麼,而不是停留於表面的認知。好比當咱們要使用 @Autowired 註解時,能夠拿到咱們想要的 bean ,可是爲何能夠是值得思考的。-- 關於閱讀源碼php
Spring源碼的閱讀結合平常的使用,能夠幫助咱們更好的掌握這個龐大的技術體系,實際的開發工做中有不少地方能夠借鑑它的一些思想來幫助咱們更好的實現本身的業務邏輯。本篇將以擴展點爲切入點,來了解下在Spring生命週期中擴展Spring中的Bean功能。java
ApplicationListener
實際上是 spring
事件通知機制中核心概念;在java的事件機制中,通常會有三個概念:git
ApplicationListener
繼承自 java.util.EventListener
,提供了對於Spring
中事件機制的擴展。github
ApplicationListener
在實際的業務場景中使用的很是多,好比我通常喜歡在容器初始化完成以後來作一些資源載入或者一些組件的初始化。這裏的容器指的就是Ioc
容器,對應的事件是ContextRefreshedEvent
。spring
@Component
public class StartApplicationListener implements ApplicationListener<ContextRefreshedEvent> {
@Override
public void onApplicationEvent(ContextRefreshedEvent contextRefreshedEvent) {
//初始化資源文件
//初始化組件 如:cache
}
}
複製代碼
上面這段代碼會在容器刷新完成以後來作一些事情。下面經過自定義事件來看看怎麼使用,在看具體的demo
以前,先來了解下一些關注點。bash
平常工做了,若是要使用 Spring
事件傳播機制,咱們須要關注的點有如下幾點:app
ApplicationEvent
ApplicationListener
接口ApplicationContextAware
接口。Spring
容器。那麼下面就按照這個思路來看下demo
的具體實現。ide
UserRegisterEvent
,用戶註冊事件;這裏做爲事件對象,繼承自 ApplicationEvent
。this
/** * @description: 用戶註冊事件 * @email: <a href="glmapper_2018@163.com"></a> * @author: guolei.sgl * @date: 18/7/25 */
public class UserRegisterEvent extends ApplicationEvent {
public String name;
public UserRegisterEvent(Object o) {
super(o);
}
public UserRegisterEvent(Object o, String name) {
super(o);
this.name=name;
}
}
複製代碼
用戶註冊服務,這裏須要在用戶註冊時將註冊事件發佈出去,因此經過實現ApplicationEventPublisherAware
接口,使UserService
具備事件發佈能力。spa
ApplicationEventPublisherAware:發佈事件,也就是把某個事件告訴的全部與這個事件相關的監聽器。
/** * @description: 用戶註冊服務,實現ApplicationEventPublisherAware接口 ,代表自己具備事件發佈能力 * @email: <a href="glmapper_2018@163.com"></a> * @author: guolei.sgl * @date: 18/7/25 */
public class UserService implements ApplicationEventPublisherAware {
private ApplicationEventPublisher applicationEventPublisher;
public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
this.applicationEventPublisher = applicationEventPublisher;
}
public void register(String name) {
System.out.println("用戶:" + name + " 已註冊!");
applicationEventPublisher.publishEvent(new UserRegisterEvent(name));
}
}
複製代碼
這裏的UserService
其實是做爲事件源存在的,經過register
將用戶註冊事件傳播出去。那麼下面就是須要定義如何來監聽這個事件,而且將事件進行消費處理掉,這裏就是經過ApplicationListener
來完成。
當用戶觸發註冊操做時,向積分服務發送消息,爲用戶初始化積分。
/** * @description: BonusServerListener 積分處理,當用戶註冊時,給當前用戶增長初始化積分 * @email: <a href="glmapper_2018@163.com"></a> * @author: guolei.sgl * @date: 18/7/25 */
public class BonusServerListener implements ApplicationListener<UserRegisterEvent> {
public void onApplicationEvent(UserRegisterEvent event) {
System.out.println("積分服務接到通知,給 " + event.getSource() +
" 增長積分...");
}
}
複製代碼
<?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:context="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">
<bean id="userService" class="com.glmapper.extention.UserService"/>
<bean id="bonusServerListener" class="com.glmapper.extention.BonusServerListener"/>
</beans>
複製代碼
/** * @description: 客戶端類 * @email: <a href="glmapper_2018@163.com"></a> * @author: guolei.sgl * @date: 18/7/25 */
public class MainTest {
public static void main(String[] args) {
ApplicationContext context =new
ClassPathXmlApplicationContext("beans.xml");
UserService userService = (UserService)
context.getBean("userService");
//註冊事件觸發
userService.register("glmapper");
}
}
複製代碼
客戶端類中,註冊一個name
爲glmapper
的用戶,執行結果:
用戶:glmapper 已註冊!
積分服務接到通知,給 glmapper 增長積分...
複製代碼
如今來考慮另一個問題,增長一個功能,用戶註冊以後給用戶發一個郵件。這個其實就是增長一個監聽類就能夠,前提是這個監聽者是監聽當前事件的。
/** * @description: 郵件服務監聽器,當監聽到用戶的註冊行爲時, 給用戶發送郵件通知 * @email: <a href="glmapper_2018@163.com"></a> * @author: guolei.sgl * @date: 18/7/25 */
public class EmailServerListener implements ApplicationListener<UserRegisterEvent> {
public void onApplicationEvent(UserRegisterEvent event) {
System.out.println("郵件服務接到通知,給 " + event.getSource() +
" 發送郵件...");
複製代碼
這裏若是將UserRegisterEvent
換成UserLoginEvent
,那麼郵件服務將不會有任何行爲。
增長髮送郵件監聽類以後的執行結果:
用戶:glmapper 已註冊!
郵件服務接到通知,給 glmapper 發送郵件...
積分服務接到通知,給 glmapper 增長積分...
複製代碼
Spring
的事件傳播機制是基於觀察者模式(Observer
)實現的,它能夠將 Spring Bean
的改變定義爲事件 ApplicationEvent
,經過 ApplicationListener
監聽 ApplicationEvent
事件,一旦Spring Bean
使用 ApplicationContext.publishEvent( ApplicationEvent event )
發佈事件後,Spring
容器會通知註冊在 容器中全部 ApplicationListener
接口的實現類,最後 ApplicationListener
接口實現類判斷是否處理剛發佈出來的 ApplicationEvent
事件。
ApplicationContextAware
中只有一個setApplicationContext
方法。實現了ApplicationContextAware
接口的類,能夠在該Bean
被加載的過程當中獲取Spring
的應用上下文ApplicationContext
,經過ApplicationContext
能夠獲取 Spring
容器內的不少信息。
這種通常在須要手動獲取Bean
的注入實例對象時會使用到。下面經過一個簡單的demo
來了解下。
GlmapperApplicationContext
持有ApplicationContext
對象,經過實現 ApplicationContextAware
接口來給ApplicationContext
作賦值。
/** * @description: GlmapperApplicationContext * @email: <a href="glmapper_2018@163.com"></a> * @author: guolei.sgl * @date: 18/7/29 */
public class GlmapperApplicationContext implements ApplicationContextAware {
private ApplicationContext applicationContext;
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext=applicationContext;
}
public ApplicationContext getApplicationContext(){
return applicationContext;
}
}
複製代碼
須要手動獲取的bean
:
/** * @description: HelloService * @email: <a href="glmapper_2018@163.com"></a> * @author: guolei.sgl * @date: 18/7/29 */
public class HelloService {
public void sayHello(){
System.out.println("Hello Glmapper");
}
}
複製代碼
在配置文件中進行配置:
<bean id="helloService" class="com.glmapper.extention.applicationcontextaware.HelloService"/>
<bean id="glmapperApplicationContext" class="com.glmapper.extention.applicationcontextaware.GlmapperApplicationContext"/>
複製代碼
客戶端類調用:
public class MainTest {
public static void main(String[] args) {
ApplicationContext context = new
ClassPathXmlApplicationContext("beans.xml");
HelloService helloService = (HelloService)
context.getBean("helloService");
helloService.sayHello();
//這裏經過實現ApplicationContextAware接口的類來完成bean的獲取
GlmapperApplicationContext glmapperApplicationContext =
(GlmapperApplicationContext) context.getBean("glmapperApplicationContext");
ApplicationContext applicationContext =
glmapperApplicationContext.getApplicationContext();
HelloService glmapperHelloService = (HelloService)
applicationContext.getBean("helloService");
glmapperHelloService.sayHello();
}
}
複製代碼
咱們知道BeanFactory
是整個Ioc
容器最頂層的接口,它規定了容器的基本行爲。實現BeanFactoryAware
接口就代表當前類具體BeanFactory
的能力。
BeanFactoryAware
接口中只有一個setBeanFactory
方法。實現了BeanFactoryAware
接口的類,能夠在該Bean
被加載的過程當中獲取加載該Bean
的BeanFactory
,同時也能夠獲取這個BeanFactory
中加載的其它Bean
。
來想一個問題,咱們爲何須要經過BeanFactory
的getBean
來獲取Bean
呢?Spring已經提供了不少便捷的注入方式,那麼經過BeanFactory
的getBean
來獲取Bean
有什麼好處呢?來看一個場景。
如今有一個HelloService
,這個HelloService
就是打招呼,咱們須要經過不一樣的語言來實現打招呼,好比用中文,用英文。通常的作法是:
public interface HelloService {
void sayHello();
}
//英文打招呼實現
public class GlmapperHelloServiceImpl implements HelloService {
public void sayHello() {
System.out.println("Hello Glmapper");
}
}
//中文打招呼實現
public class LeishuHelloServiceImpl implements HelloService {
public void sayHello() {
System.out.println("你好,磊叔");
}
}
複製代碼
客戶端類來調用務必會出現下面的方式:
if (condition=="英文"){
glmapperHelloService.sayHello();
}
if (condition=="中文"){
leishuHelloService.sayHello();
}
複製代碼
若是有一天,老闆說咱們要作國際化,要實現全球全部的語言來問候。你是說好的,仍是控制不住要動手呢?
那麼有沒有什麼方式能夠動態的去決定個人客戶端類到底去調用哪種語言實現,而不是用過if-else方式來羅列呢?是的,對於這些須要動態的去獲取對象的場景,BeanFactoryAware
就能夠很好的搞定。OK,來看代碼改造:
引入BeanFactoryAware
:
/** * @description: 實現BeanFactoryAware ,讓當前bean自己具備 BeanFactory 的能力 * * 實現 BeanFactoηAware 接口的 bean 能夠直接訪問 Spring 容器,被容器建立之後, * 它會擁有一個指向 Spring 容器的引用,能夠利用該bean根據傳入參數動態獲取被spring工廠加載的bean * * @email: <a href="glmapper_2018@163.com"></a> * @author: guolei.sgl * @date: 18/7/29 */
public class GlmapperBeanFactory implements BeanFactoryAware {
private BeanFactory beanFactory;
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
this.beanFactory=beanFactory;
}
/** * 提供一個execute 方法來實現不一樣業務實現類的調度器方案。 * @param beanName */
public void execute(String beanName){
HelloService helloService=(HelloService)
beanFactory.getBean(beanName);
helloService.sayHello();
}
}
複製代碼
這裏爲了邏輯方便理解,再加入一個HelloFacade
類,這個類的做用就是持有一個BeanFactoryAware
的實例對象,而後經過HelloFacade
實例對象的方法來屏蔽底層BeanFactoryAware
實例的實現細節。
public class HelloFacade {
private GlmapperBeanFactory glmapperBeanFactory;
//調用glmapperBeanFactory的execute方法
public void sayHello(String beanName){
glmapperBeanFactory.execute(beanName);
}
public void setGlmapperBeanFactory(GlmapperBeanFactory beanFactory){
this.glmapperBeanFactory = beanFactory;
}
}
複製代碼
客戶端類
public class MainTest {
public static void main(String[] args) {
ApplicationContext context = new
ClassPathXmlApplicationContext("beans.xml");
HelloFacade helloFacade = (HelloFacade)
context.getBean("helloFacade");
GlmapperBeanFactory glmapperBeanFactory = (GlmapperBeanFactory)
context.getBean("glmapperBeanFactory");
//這裏其實能夠不經過set方法注入到helloFacade中,
//能夠在helloFacade中經過autowired
//注入;這裏在使用main方法來執行驗證,因此就手動set進入了
helloFacade.setGlmapperBeanFactory(glmapperBeanFactory);
//這個只須要傳入不一樣HelloService的實現類的beanName,
//就能夠執行不一樣的業務邏輯
helloFacade.sayHello("glmapperHelloService");
helloFacade.sayHello("leishuHelloService");
}
}
複製代碼
能夠看到在調用者(客戶端)類中,只須要經過一個beanName
就能夠實現不一樣實現類的切換,而不是經過一堆if-else來判斷。另外有的小夥伴可能會說,程序怎麼知道用哪一個beanName
呢?其實這個也很簡單,這個參數咱們能夠經過一些途徑來拼接獲得,好比使用一個prefix
用來指定語言,prefix
+HelloService
就能夠肯定惟一的beanName
。
原本想着在一篇文章裏面把擴展點都寫一下的,可是實在太長了。後面差很少還有兩篇。本系列中全部的demo
能夠在github
獲取,也歡迎小夥伴把可以想到的擴展點pr過來。