Spring的事件
對於SpringApplicationContext(BeanFactory)而言,在整個應用運行過程中(包括應用的啓動、銷燬), 會發布各種應用事件。開發者也可以實現自己的事件, 從而起到擴展spring框架的作用 。
Spring的事件(Application Event)爲 Bean與 Bean之間的消息通信提供了支持。當一個Bean處理完一個任務之後,希望另外一個 Bean知道並能做相應的處理, 這時我們就需要讓另外一個 Bean監聽當前 Bean所發送的事件。
sprjng藉助於 org.springframewofk.context.event.ApplicationEvent抽象類及其子類實現事件的發佈,與此同時, 藉助於 org.springframework.context.ApplicationListener接口及其實現者實現事件的監聽,這兩者構成了觀察者 ( observer) 模式 。
- ApplicationEvent就是Spring的事件接口
- ApplicationListener就是Spring的事件監聽器接口,所有的監聽器都實現該接口
- ApplicationEventPublisher是Spring的事件發佈接口,ApplicationContext實現了該接口
- ApplicationEventMulticaster就是Spring事件機制中的事件廣播器,默認實現SimpleApplicationEventMulticaster
在Spring中通常是ApplicationContext本身擔任監聽器註冊表的角色,在其子類AbstractApplicationContext中就聚合了事件廣播器ApplicationEventMulticaster和事件監聽器ApplicationListnener,並且提供註冊監聽器的addApplicationListnener方法。
其執行的流程大致爲:
當一個事件源產生事件時,它通過事件發佈器ApplicationEventPublisher發佈事件,然後事件廣播器ApplicationEventMulticaster會去事件註冊表ApplicationContext中找到事件監聽器ApplicationListnener,並且逐個執行監聽器的onApplicationEvent方法,從而完成事件監聽器的邏輯。
在Spring中,使用註冊監聽接口,除了繼承ApplicationListener接口外,還可以使用註解@EventListener來監聽一個事件,同時該註解還支持SpEL表達式,來觸發監聽的條件,比如只接受編碼爲001的事件,從而實現一些個性化操作。
SpringBoot的默認啓動事件
在SpringBoot的1.5.x中,提供了幾種事件,供我們在開發過程中進行更加便捷的擴展及差異化操作。
- ApplicationStartingEvent:springboot啓動開始的時候執行的事件
- ApplicationEnvironmentPreparedEvent:spring boot對應Enviroment已經準備完畢,但此時上下文context還沒有創建。在該監聽中獲取到ConfigurableEnvironment後可以對配置信息做操作,例如:修改默認的配置信息,增加額外的配置信息等等。
- ApplicationPreparedEvent:spring boot上下文context創建完成,但此時spring中的bean是沒有完全加載完成的。在獲取完上下文後,可以將上下文傳遞出去做一些額外的操作。值得注意的是:在該監聽器中是無法獲取自定義bean並進行操作的。
- ApplicationReadyEvent:springboot加載完成時候執行的事件。
- ApplicationFailedEvent:spring boot啓動異常時執行事件。
從官網文檔中,我們可以知道,由於一些事件是在上下文爲加載完觸發的,所以無法使用註冊bean的方式來聲明,文檔中可以看出,可以通過SpringApplication.addListeners(…)或者SpringApplicationBuilder.listeners(…)來添加,或者添加META-INF/spring.factories文件中添加監聽類也是可以的,這樣會自動加載。
org.springframework.context.ApplicationListener=com.example.project.MyListener
來用代碼說明一切,我們創建一個ApplicationStartingEvent事件監聽類。
@Slf4j
public class MyApplicationStartingEventListener implements ApplicationListener<ApplicationStartingEvent> {
@Override
public void onApplicationEvent(ApplicationStartingEvent event) {
// TODO Auto-generated method stub
//由於 log相關還未加載 使用了也輸出不了的
log.info("ApplicationStartingEvent事件發佈:{}", event);
System.out.println("ApplicationStartingEvent事件發佈:" + event.getTimestamp());
}
}
啓動類中添加:
@SpringBootApplication
public class Application {
public static void main(String[] args){
SpringApplication app =new SpringApplication(Application.class);
app.addListeners(new MyApplicationStartingEventListener());//加入自定義的監聽類
app.run(args);
}
}
啓動應用,控制檯可以看出,在啓動時,我們監聽到了ApplicationStartingEvent事件
所以在需要的時候,可以通過適當的監聽以上事件,來完成一些業務操作。
自定義事件發佈和監聽
爲實現 ApplicationEvent事件的發佈,開發者需要藉助於 ApplicationContext,它提供了 publishEvent方法。Spring 的事件需要遵循如下流程:
(1) 自定義事件, 繼承 ApplicaltionEvent。
(2) 定義事件監聽器,實現ApplicationListener。
(3) 使用容器發佈事件。
通過以上的介紹,我們來定義一個自定義事件的發佈和監聽。
自定義事件源
public class DemoEvent extends ApplicationEvent{
private static final long serialVersionUID = 1L;
private String msg;
public DemoEvent(Object source,String msg) {
super(source);
this.msg = msg;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
}
當然實際應用中,我們的消息可能不僅是字符串,所以我們也可以定義一個消息實體
spring提供瞭如下三種常見 ApplicationEvent事件實現,它們的含義如下:
- org.sprjngframework.web.context.support.RequestHandledEvent: 開發者必須注意到,在 spring 的 webApplicationContext 中, 一旦客戶請求處理完成, 將發佈RequestHandledEvent事件。 當然, 如果需要, 開發者也可以在企業應用代碼中拋出這種事件。
- org.springframework,context.event.ContextRefreshedEvent: 開發者必須注意到, 在spring Applicationcontext 容器初始化完成或刷新時, Spring 框架本身將發佈ContextRefreshedEvent事件。
- org.springframework.context.event.ContextClosedEvent:開發者必須注意到,在關閉spring Appljcatjoncontext容器時, Spring框架本身將發佈 ContextRefreshedEvent事件 。
事件監聽器
1、繼承ApplicationListener接口
/**
* 事件監聽器
* 實現 ApplicationListener接口, 並指定監聽的事件類型
*/
@Component
public class DemoListener implements ApplicationListener<DemoEvent> {
/**
* @param event
* 使用 onApplicationEvent方法對消息進行接受處理
*/
public void onApplicationEvent(DemoEvent event) {
String msg = event.getMsg();
System.out.println("我(bean-demoListener)接受到了bean-demoPublisher發佈的消息:"
+ msg);
}
}
在Spring中,使用註冊監聽接口,除了繼承ApplicationListener接口外,還可以使用註解@EventListener來監聽一個事件,同時該註解還支持SpEL表達式,來觸發監聽的條件,比如只接受編碼爲001的事件,從而實現一些個性化操作。下文示例中會簡單舉例下。
2、使用@EventListener方式
/**
* 監聽配置類
* @author oKong
*/
@Configuration
@Slf4j
public class EventListenerConfig {
@EventListener
public void handleEvent(Object event) {
//監聽所有事件 可以看看 系統各類時間 發佈了哪些事件
//可根據 instanceof 監聽想要監聽的事件
// if(event instanceof DemoEvent) {
// }
log.info("事件:{}", event);
}
@EventListener
public void handleCustomEvent(DemoEvent demoEvent) {
//監聽 CustomEvent事件
log.info("監聽到CustomEvent事件,消息爲:{}, 發佈時間:{}", demoEvent.getMsg(), demoEvent.getTimestamp());
}
/**
* 監聽 code爲oKong的事件
*/
@EventListener(condition="#demoEvent.msg == 'xmr'")
public void handleCustomEventByCondition(DemoEvent demoEvent) {
//監聽 CustomEvent事件
log.info("監聽到msg爲'xmr'的DemoEvent事件,消息爲:{}, 發佈時間:{}", demoEvent.getMsg(), demoEvent.getTimestamp());
}
}
事件發佈類
@Component
public class DemoPublisher {
@Autowired
ApplicationContext applicationContext;//注入 AppllcationContext用來發布事件
/**
* @param msg
* 使用 AppllicationContext的 publishEvent方法來發布
*/
public void publish(String msg){
applicationContext.publishEvent(new DemoEvent(this, msg));
}
}
Spring中,事件源不強迫繼承ApplicationEvent接口的,也就是可以直接發佈任意一個對象類。但內部其實是使用PayloadApplicationEvent類進行包裝了一層。這點和guava的eventBus類似。而且,使用@EventListener的condition可以實現更加精細的事件監聽,condition支持SpEL表達式,可根據事件源的參數來判斷是否監聽。
編寫控制類,示例發佈事件
@RestController
@RequestMapping("/event")
@Slf4j
public class EventController {
/**
* 注入事件發佈類
*/
@Autowired
ApplicationEventPublisher eventPublisher;
/**
* 參數默認註解式@RequestParam
* @param message
* @return
*/
@GetMapping("/msg")
public String push(@RequestParam("message") String message) {
log.info("發佈applicationEvent事件:{}", message);
eventPublisher.publishEvent(new DemoEvent(this, message));
return "事件發佈成功!";
}
@GetMapping("/obj")
public String pushObject(String message) {
log.info("發佈對象事件:{}", message);
eventPublisher.publishEvent(message);
return "對象事件發佈成功!";
}
}
異步監聽處理
默認情況下,監聽事件都是同步執行的。在需要異步處理時,可以在方法上加上@Async進行異步化操作。此時,可以定義一個線程池,同時開啓異步功能,加入@EnableAsync。
/**
* 監聽msg爲xmr的事件
*/
@Async
@EventListener(condition="#demoEvent.msg == 'xmr'")
public void handleCustomEventByCondition(DemoEvent demoEvent) {
//監聽 CustomEvent事件
log.info("監聽到msg爲'xmr'的DemoEvent事件,消息爲:{}, 發佈時間:{}", demoEvent.getMsg(), demoEvent.getTimestamp());
}
關於事務綁定事件
當一些場景下,比如在用戶註冊成功後,即數據庫事務提交了,之後再異步發送郵件等,不然會發生數據庫插入失敗,但事件卻發佈了,也就是郵件發送成功了的情況。此時,我們可以使用@TransactionalEventListener註解或者TransactionSynchronizationManager類來解決此類問題,也就是:事務成功提交後,再發布事件。當然也可以利用返回上層(事務提交後)再發布事件的方式了,只是不夠優雅而已罷了
ApplicationListener和ContextRefreshedEvent
很多時候我們想要在某個類加載完畢時幹某件事情,但是使用了spring管理對象,我們這個類引用了其他類(可能是更復雜的關聯),所以當我們去使用這個類做事情時發現包空指針錯誤,這是因爲我們這個類有可能已經初始化完成,但是引用的其他類不一定初始化完成,所以發生了空指針錯誤,解決方案如下:
1、寫一個類繼承spring的ApplicationListener監聽,並監控ContextRefreshedEvent事件(容易初始化完成事件)
ApplicationListener和ContextRefreshedEvent一般都是成對出現的
在IOC的容器的啓動過程,當所有的bean都已經處理完成之後,spring ioc容器會有一個發佈事件的動作。從 AbstractApplicationContext 的源碼中就可以看出
當ioc容器加載處理完相應的bean之後,也給我們提供了一個機會(先有InitializingBean,後有ApplicationListener<ContextRefreshedEvent>),可以去做一些自己想做的事。其實這也就是spring ioc容器給提供的一個擴展的地方。
一個最簡單的方式就是,讓我們的bean實現ApplicationListener接口,這樣當發佈事件時,[spring]的ioc容器就會以容器的實例對象作爲事件源類,並從中找到事件的監聽者,此時ApplicationListener接口實例中的onApplicationEvent(E event)方法就會被調用,我們的邏輯代碼就會寫在此處。這樣我們的目的就達到了