SpringBoot事件的發佈和監聽

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)方法就會被調用,我們的邏輯代碼就會寫在此處。這樣我們的目的就達到了

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章