Spring事件機制詳解

一、前言

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

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

2.1簡介

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

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

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

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

三、Spring類圖分析

在這裏插入圖片描述

事件

  1. ApplicationEvent 繼承自 JDK 的 EventObject,JDK要求所有事件將繼承它,並通過source得到事件源,比如AWT事件體系也是繼承它。
  2. 系統默認提供瞭如下ApplicationEvent事件實現:

在這裏插入圖片描述

事件發佈者

具體代表者是:ApplicationEventPublisherApplicationEventMulticaster,系統默認提供瞭如下實現:

ApplicationEventPublisher類圖

ApplicationEventMulticaster類圖

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

AbstractApplicationContext發佈事件代碼

ApplicationEventMulticaster初始化邏輯
2.根據上面代碼可以看出 ApplicationContext 會自動在本地容器找一個ApplicationEventMulticaster的實現,如果沒有自己new一個SimpleApplicationEventMulticaster,其中SimpleApplicationEventMulticaster發佈事件的代碼如下:

發佈事件方法
可以看出如果給它一個executor,它就可以實現異步發佈事件了,否則就是同步發送。

監聽器

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

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

}

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

四、簡單實現(實現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;
    }
}

OrderService

@Service
public class OrderService implements ApplicationEventPublisherAware {

    private ApplicationEventPublisher applicationEventPublisher;

    /**
     * 訂單保存
     */
    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));
        System.out.println("================");

    }

    private String getOrderNo() {
        String millis = String.valueOf(System.currentTimeMillis());
        String str = millis.substring(millis.length() - 7, millis.length() - 1);
        return LocalDate.now().format(DateTimeFormatter.ofPattern("yyyyMMdd")) + str;
    }


    @Override
    public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
        this.applicationEventPublisher = applicationEventPublisher;
    }
}

監聽器OrderCreateEventListener

@Component
public class OrderCreateEventListener implements ApplicationListener<OrderCreateEvent> {


    @Override
    public void onApplicationEvent(OrderCreateEvent event) {
        System.out.printf(this.getClass().getName()+ " -- ApplicationListener 接口實現,訂單號[%s]:,鎖定商品[%s]\n",
                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]:,鎖定商品[手機]
================

五、註解驅動@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表達式),只有滿足條件纔會觸發,後面泛型支持那裏有示例。

六、異步事件

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

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

七、泛型支持

事件類一定要繼承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時
我們把發佈事件的代碼改爲如下內容再測試,則不會收到事件通知。
applicationEventPublisher.publishEvent(new OrderGenericEvent(order,false));

八、事件傳播機制

當我們監聽一個事件處理完成時,還需要發佈另一個事件,一般我們想到的是調用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也被觸發了。

在這裏插入圖片描述
當然還可以返回多個事件,不再舉例。

九、事物事件@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)

十、總結

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

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