Spring事件機制詳解

1、前言

  說來慚愧,對應Spring事件機制以前只知道實現 ApplicationListener 接口,就能夠基於Spring自帶的事件作一些事情(如ContextRefreshedEvent),可是最近看公司的wiki基於Spring事件的領域驅動才發現原來還有這麼多東西。html

2、訂閱/發佈(觀察者模式)

2.1簡介

Spring是基於事件驅動模型的,咱們經常使用的MQ就是基於觀察者模式設計的。
事件驅動模型也就是咱們常說的觀察者,或者發佈-訂閱模型;理解它的幾個關鍵點:java

  1. 首先是一種對象間的一對多的關係;最簡單的如交通訊號燈,信號燈是目標(一方),行人注視着信號燈(多方)。
  2. 當目標發送改變(發佈),觀察者(訂閱者)就能夠接收到改變。
  3. 觀察者如何處理(如行人如何走,是快走/慢走/不走,目標不會管的),目標無需干涉;因此就鬆散耦合了它們之間的關係。

Java API實現和自定義實現觀察者模式:git

Java提供了兩個接口java.util.Observablejava.util.Observer,代碼可參考https://github.com/2YSP/design-pattern/tree/master/src/cn/sp/observergithub

3、Spring類圖分析

 

類圖
類圖

 

事件

  1. ApplicationEvent 繼承自 JDK 的 EventObject,JDK要求全部事件將繼承它,並經過source獲得事件源,好比AWT事件體系也是繼承它。
  2. 系統默認提供了以下ApplicationEvent事件實現:

 

類圖
類圖

 

事件發佈者

具體表明者是:ApplicationEventPublisherApplicationEventMulticaster,系統默認提供了以下實現:spring

 

ApplicationEventPublisher類圖
ApplicationEventPublisher類圖

 

 

ApplicationEventMulticaster類圖
ApplicationEventMulticaster類圖

 

  1. ApplicationContext 接口繼承了 ApplicationEventPublisher,並在 AbstractApplicationContext 實現了具體代碼,實際執行是委託給ApplicationEventMulticaster(能夠認爲是多播)
    代碼以下:

 

AbstractApplicationContext發佈事件代碼
AbstractApplicationContext發佈事件代碼

 

 

ApplicationEventMulticaster初始化邏輯
ApplicationEventMulticaster初始化邏輯

2.根據上面代碼能夠看出 ApplicationContext 會自動在本地容器找一個ApplicationEventMulticaster的實現,若是沒有本身new一個SimpleApplicationEventMulticaster,其中SimpleApplicationEventMulticaster發佈事件的代碼以下:

 

 

發佈事件方法
發佈事件方法

能夠看出若是給它一個executor,它就能夠實現異步發佈事件了,不然就是同步發送。

 

監聽器

ApplicationListener 接口提供了onApplicationEvent方法,可是咱們須要在該方法實現內部判斷事件類型來處理,也沒有提供按順序觸發監聽器的語義,因此Spring提供了另外一個接口,SmartApplicationListener:bash

public interface SmartApplicationListener extends ApplicationListener<ApplicationEvent>, Ordered {
     //若是實現支持該事件類型 那麼返回true
    boolean supportsEventType(Class<? extends ApplicationEvent> eventType);
  
        //若是實現支持「目標」類型,那麼返回true
    boolean supportsSourceType(Class<?> sourceType);
       
     //順序,即監聽器執行的順序,值越小優先級越高
    int getOrder();

}

源碼分析到這裏,下面說說怎麼使用。app

4、簡單實現(實現ApplicationListener接口)

場景是咱們保存一個訂單後發佈事件通知,以便作一些其餘操做好比鎖定商品。
訂單實體類:框架

public class Order {

    private String orderNo;

    private String orderStatus;

    private String goods;

    private Date createTime;
    
    //省略get、set、toString方法
}

訂單建立事件OrderCreateEvent異步

public class OrderCreateEvent extends ApplicationEvent {

    private final Order order;

    public OrderCreateEvent(Object source,Order order) {
        super(source);
        this.order = order;
    }

    public Order getOrder(){
        return order;
    }
}

OrderServiceide

  1. @Service 
  2. public class OrderService implements ApplicationEventPublisherAware
  3.  
  4. private ApplicationEventPublisher applicationEventPublisher; 
  5.  
  6. /** 
  7. * 訂單保存 
  8. */ 
  9. public void save()
  10. String orderNo = getOrderNo(); 
  11. Order order = new Order(); 
  12. order.setOrderNo(orderNo); 
  13. order.setOrderStatus("待付款"); 
  14. order.setCreateTime(new Date()); 
  15. order.setGoods("手機"); 
  16. System.out.println("訂單保存成功:" + order.toString()); 
  17.  
  18. //發佈事件 
  19. applicationEventPublisher.publishEvent(new OrderCreateEvent(this,order)); 
  20. System.out.println("================"); 
  21.  
  22.  
  23. private String getOrderNo()
  24. String millis = String.valueOf(System.currentTimeMillis()); 
  25. String str = millis.substring(millis.length() - 7, millis.length() - 1); 
  26. return LocalDate.now().format(DateTimeFormatter.ofPattern("yyyyMMdd")) + str; 
  27.  
  28.  
  29. @Override 
  30. public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher)
  31. this.applicationEventPublisher = applicationEventPublisher; 

監聽器OrderCreateEventListener

  1. @Component 
  2. public class OrderCreateEventListener implements ApplicationListener<OrderCreateEvent>
  3.  
  4.  
  5. @Override 
  6. public void onApplicationEvent(OrderCreateEvent event)
  7. System.out.printf(this.getClass().getName()+ " -- ApplicationListener 接口實現,訂單號[%s]:,鎖定商品[%s]\n"
  8. event.getOrder().getOrderNo(), event.getOrder().getGoods()); 

運行測試類:

@RunWith(SpringRunner.class)
@SpringBootTest
public class ApplicationEventDemoApplicationTests {

    @Autowired
    OrderService orderService;

    @Test
    public void contextLoads() {
        orderService.save();
    }

}

控制檯打印以下則表示成功實現了監聽。

訂單保存成功:Order{orderNo='20190601983801', orderStatus='待付款', goods='手機', createTime=Sat Jun 01 00:23:58 CST 2019}
2019-06-01 00:23:58.069  INFO 15060 --- [           main] o.s.s.concurrent.ThreadPoolTaskExecutor  : Initializing ExecutorService 'applicationTaskExecutor'
cn.sp.listener.OrderCreateEventListener -- ApplicationListener 接口實現,訂單號[20190601983801]:,鎖定商品[手機]
================

5、註解驅動@EventListener

接着上面的,自定義的監聽器必定要實現ApplicationListener接口嗎?不是,Spring還提供了註解的方式 @EventListener,使用示例以下:

@Component
public class OrderCreateEventListenerAnnotation {

    @EventListener
    public void createOrderEvent(OrderCreateEvent event){
        System.out.println(this.getClass().getName()+"--訂單建立事件,@EventListener註解實現,orderNo:"+event.getOrder().getOrderNo());
    }
}

注意:@EventListener有個condition屬性,還能夠支持條件判斷(定義布爾SpEL表達式),只有知足條件纔會觸發,後面泛型支持那裏有示例。

6、異步事件

上面的監聽事件都是同步觸發的,若是想異步的怎麼辦?
只須要兩步:

  1. 啓動類上添加 @EnableAsync註解,開啓異步支持。
  2. 監聽方法上添加 @Async註解
@Async
    @EventListener
    public void createOrderEvent(OrderCreateEvent event){
        System.out.println(this.getClass().getName()+"--訂單建立事件,@EventListener註解實現,orderNo:"+event.getOrder().getOrderNo());
    }

7、泛型支持

事件類必定要繼承ApplicationEvent嗎?
固然不是,咱們還能夠自定義泛型類實現事件調度(這個是我認爲最厲害的地方了)。

  1. 寫個通用泛型類事件
/** * 能夠自定義泛型類實現事件調度 * Created by 2YSP on 2019/5/30. */
public class GenericEvent<T> {

    private  T data;
    private boolean success;

    public GenericEvent(T data,boolean success){
        this.data = data;
        this.success = success;
    }

    public T getData() {
        return data;
    }

    public void setData(T data) {
        this.data = data;
    }

    public boolean isSuccess() {
        return success;
    }

    public void setSuccess(boolean success) {
        this.success = success;
    }
}
  1. 寫個具體類型的事件
public class OrderGenericEvent extends GenericEvent<Order> {

    public OrderGenericEvent(Order data, boolean success) {
        super(data, success);
    }
}
  1. 在OrderService的save()方法中發佈事件

applicationEventPublisher.publishEvent(new OrderGenericEvent(order,true));

  1. 事件處理
@Component
public class OrderGenericEventListener {

    @EventListener(condition = "#event.success")
    public void orderListener(GenericEvent<Order> event){
        System.out.println(this.getClass().getName()+"--處理泛型條件事件。。。");
    }
}

測試結果代表,成功處理了事件。

 

success爲true時
success爲true時

咱們把發佈事件的代碼改成以下內容再測試,則不會收到事件通知。
applicationEventPublisher.publishEvent(new OrderGenericEvent(order,false));

 

8、事件傳播機制

當咱們監聽一個事件處理完成時,還須要發佈另外一個事件,通常咱們想到的是調用ApplicationEventPublisher#publishEvent發佈事件方法,但Spring提供了另外一種更加靈活的新的事件繼續傳播機制,監聽方法返回一個事件,也就是方法的返回值就是一個事件對象。
示例代碼:

public void save(){
        String orderNo = getOrderNo();
        Order order = new Order();
        order.setOrderNo(orderNo);
        order.setOrderStatus("待付款");
        order.setCreateTime(new Date());
        order.setGoods("手機");
        System.out.println("訂單保存成功:" + order.toString());

        //發佈事件
// applicationEventPublisher.publishEvent(new OrderCreateEvent(this,order));
        applicationEventPublisher.publishEvent(order);
// applicationEventPublisher.publishEvent(new OrderGenericEvent(order,true));
        System.out.println("================");

    }

訂單監聽器

@Component
public class OrderListener {

    @EventListener
    public void orderListener(Order order){
        System.out.println(this.getClass().getName() + " -- 監聽一個訂單");
    }

    @EventListener
    public OrderCreateEvent orderReturnEvent(Order order){
        System.out.println(this.getClass().getName() + " -- 監聽一個訂單,返回一個新的事件 OrderCreateEvent");
        return new OrderCreateEvent(this,order);
    }
}

啓動單元測試,就會發現OrderCreateEventListener也被觸發了。

 

enter description here
enter description here

固然還能夠返回多個事件,再也不舉例。

 

9、事物事件@TransactionalEventListener

從Spring 4.2開始,框架提供了一個新的@TransactionalEventListener註解,它是@EventListener的擴展,容許將事件的偵聽器綁定到事務的一個階段。綁定能夠進行如下事務階段:

  1. AFTER_COMMIT(默認的):在事務成功後觸發
  2. AFTER_ROLLBACK:事務回滾時觸發
  3. AFTER_COMPLETION:事務完成後觸發,不管是否成功
  4. BEFORE_COMMIT:事務提交以前觸發
@TransactionalEventListener(phase = BEFORE_COMMIT)
public void txEvent(Order order) {
    System.out.println("事物監聽");
}

上面代碼的意思是僅當存在事件生成器正在運行且即將提交的事務時,纔會調用此偵聽器。而且,若是沒有正在運行的事務,則根本不發送事件,除非咱們經過將fallbackExecution 屬性設置爲true來覆蓋它 ,即 @TransactionalEventListener(fallbackExecution = true)

10、總結

基於事件驅動模型能夠很方便的實現解耦,提升代碼的可讀性和可維護性,代碼地址:https://github.com/2YSP/application-event-demo
疑問: 泛型支持那裏若是不寫一個類繼承通用泛型事件,就跑不通這是爲何呢?
若是有人知道請告訴我,很是感謝。
參考:https://blog.csdn.net/sun_shaoping/article/details/84067446

相關文章
相關標籤/搜索