Spring事件驅動模型,簡單來說類似於Message-Queue消息隊列中的Pub/Sub發布/訂閱模式,也類似於Java設計模式中的觀察者模式。
自定義事件
Spring的事件接口位於org.springframework.context.ApplicationEvent,源碼如下:
public abstract class ApplicationEvent extends EventObject {
private static final long serialVersionUID = 7099057708183571937L;
private final long timestamp;
public ApplicationEvent(Object source) {
super(source);
this.timestamp = System.currentTimeMillis();
}
public final long getTimestamp() {
return this.timestamp;
}
}
繼承了Java的事件對象EventObject,所以可以使用getSource()方法來獲取到事件傳播對象。
自定義Spring事件
public class CustomSpringEvent extends ApplicationEvent {
private String message;
public CustomSpringEvent(Object source, String message) {
super(source);
this.message = message;
}
public String getMessage() {
return message;
}
}
然后定義事件監聽器,該監聽器實際上等同於消費者,需要交給Spring容器管理。
@Component
public class CustomSpringEventListener implements ApplicationListener<CustomSpringEvent> {
@Override
public void onApplicationEvent(CustomSpringEvent event) {
System.out.println("Received spring custom event - " + event.getMessage());
}
}
最后定義事件發布者
@Component
public class CustomSpringEventPublisher {
@Autowired
private ApplicationEventPublisher applicationEventPublisher;
public void doStuffAndPublishAnEvent(final String message) {
System.out.println("Publishing custom event. ");
CustomSpringEvent customSpringEvent = new CustomSpringEvent(this, message);
applicationEventPublisher.publishEvent(customSpringEvent);
}
}
創建測試類
@RunWith(SpringRunner.class)
@SpringBootTest
public class CustomSpringEventPublisherTest {
@Autowired
private CustomSpringEventPublisher publisher;
@Test
public void publishStringEventTest() {
publisher.doStuffAndPublishAnEvent("111");
}
}
運行測試類,可以看到控制台打印了兩條重要信息
//發布事件
Publishing custom event.
//監聽器得到了事件,並相應處理
Received spring custom event - 111
由於Spring事件是發布/訂閱的模式,而發布訂閱模式有以下三種情況
- 1生產者 - 1消費者
- 1生產者 - 多消費者
- 多生產者 - 多消費者
上面舉的例子是第一種情況,我們來試試其他兩個情況
繼續創建一個事件監聽器作為消費者:
@Component
public class CustomSpringEventListener2 implements ApplicationListener<CustomSpringEvent> {
@Override
public void onApplicationEvent(CustomSpringEvent event) {
System.out.println("CustomSpringEventListener2 Received spring custom event - " + event.getMessage());
}
}
運行測試類后,可以觀察到,控制台順序打印了兩條消費信息:
Publishing custom event.
CustomSpringEventListener1 Received spring custom event - 111
CustomSpringEventListener2 Received spring custom event - 111
說明,Spring的發布訂閱模式是廣播模式,所有消費者都能接受到消息,並正常消費
再試試第三種多生產者 - 多消費者的情況
繼續創建一個發布者,
@Component
public class CustomSpringEventPublisher2 {
@Autowired
private ApplicationEventPublisher applicationEventPublisher;
public void doStuffAndPublishAnEvent(final String message) {
System.out.println("CustomSpringEventPublisher2 Publishing custom event. ");
CustomSpringEvent customSpringEvent = new CustomSpringEvent(this, message);
applicationEventPublisher.publishEvent(customSpringEvent);
}
}
控制台輸出:
CustomSpringEventPublisher Publishing custom event.
CustomSpringEventListener1 Received spring custom event - 111
CustomSpringEventListener2 Received spring custom event - 111
CustomSpringEventPublisher2 Publishing custom event.
CustomSpringEventListener1 Received spring custom event - 222
CustomSpringEventListener2 Received spring custom event - 222
從以上輸出內容,我們可以猜測到,Spring的事件發布訂閱機制是同步進行的,也就是說,事件必須被所有消費者消費完成之后,發布者的代碼才能繼續往下走,這顯然不是我們想要的效果,那有沒有異步執行的事件呢?
Spring中的異步事件
要使用Spring 的異步事件,我們需要自定義異步事件配置類
@Configuration
public class AsynchronousSpringEventsConfig {
@Bean(name = "applicationEventMulticaster")
public ApplicationEventMulticaster simpleApplicationEventMulticaster() {
SimpleApplicationEventMulticaster eventMulticaster
= new SimpleApplicationEventMulticaster();
eventMulticaster.setTaskExecutor(new SimpleAsyncTaskExecutor());
return eventMulticaster;
}
}
發布和訂閱的代碼不用變動,直接運行測試類,控制台將打印出:
CustomSpringEventPublisher Publishing custom event.
CustomSpringEventPublisher2 Publishing custom event.
CustomSpringEventListener1 Received spring custom event - 111
CustomSpringEventListener2 Received spring custom event - 111
CustomSpringEventListener2 Received spring custom event - 222
CustomSpringEventListener1 Received spring custom event - 222
可以看到,兩個發布者幾乎同時運行,證明監聽器是異步執行的,沒有阻塞住發布者的代碼。准確的說,監聽器將在一個單獨的線程中異步處理事件。
Spring自帶的事件類型
事件驅動在Spring中是被廣泛采用的,我們查看ApplicationEvent的子類可以發現許多Event事件,在此不贅述。

注解驅動的監聽器
從Spring 4.2開始,事件監聽器不需要是實現ApplicationListener接口的bean,它可以通過@EventListener注解在任何被Spring容器管理的bean的公共方法上。
@Component
public class AnnotationDrivenContextStartedListener {
@EventListener
public void handleContextStart(CustomSpringEvent cse) {
System.out.println("Handling Custom Spring Event.");
}
}
控制台輸出結果:
CustomSpringEventPublisher Publishing custom event.
Handling Custom Spring Event.
CustomSpringEventPublisher2 Publishing custom event.
Handling Custom Spring Event.
同樣的,我們可以看出,這個事件監聽器是同步執行的,如果要改為異步監聽器,在事件方法上加上@Async,並且在Spring應用中開啟異步支持(在SpringBootApplication上添加@EnableAsync)。
@Component
public class AnnotationDrivenContextStartedListener {
@Async
@EventListener
public void handleContextStart(CustomSpringEvent cse) {
System.out.println("Handling Custom Spring Event.");
}
}
再次運行測試類:
CustomSpringEventPublisher Publishing custom event.
CustomSpringEventPublisher2 Publishing custom event.
Handling Custom Spring Event.
Handling Custom Spring Event.
泛型支持
創建一個通用泛型事件模型
@Data
public class GenericSpringEvent<T> {
private T message;
protected boolean success;
public GenericSpringEvent(T what, boolean success) {
this.message = what;
this.success = success;
}
}
注意GenericSpringEvent和CustomSpringEvent之間的區別。我們現在可以靈活地發布任何任意事件,並且不再需要從ApplicationEvent擴展。
這樣的話,我們無法像之前一樣,通過繼承ApplicationListener的方式來定義一個監聽器,因為ApplicationListener定義了事件必須是ApplicationEvent的子類。所以,我們只能使用注解驅動的監聽器。
通過在@EventListener注釋上定義布爾SpEL表達式,也可以使事件監聽器成為條件。在這種情況下,只會為成功的String的GenericSpringEvent調用事件處理程序:
@Component
public class AnnotationDrivenEventListener {
@EventListener(condition = "#event.success")
public void handleSuccessful(GenericSpringEvent<String> event) {
System.out.println("Handling generic event (conditional).");
}
}
定義具體類型的事件:
public class StringGenericSpringEvent extends GenericSpringEvent<String> {
public StringGenericSpringEvent(String message, boolean success) {
super(message, success);
}
}
定義發布者:
@Component
public class StringGenericSpringEventPublisher {
@Autowired
private ApplicationEventPublisher applicationEventPublisher;
public void doStuffAndPublishAnEvent(final String message, final boolean success) {
System.out.println("CustomSpringEventPublisher Publishing custom event. ");
StringGenericSpringEvent springEvent = new StringGenericSpringEvent(message, success);
applicationEventPublisher.publishEvent(springEvent);
}
}
測試類:
@RunWith(SpringRunner.class)
@SpringBootTest
public class CustomSpringEventPublisherTest {
@Autowired
private StringGenericSpringEventPublisher publisher;
@Test
public void publishStringEventTest() {
publisher.doStuffAndPublishAnEvent("success", true);
publisher.doStuffAndPublishAnEvent("failed", false);
}
}
運行結果:
CustomSpringEventPublisher Publishing custom event.
Handling generic event (conditional) success
CustomSpringEventPublisher Publishing custom event.
監聽器只處理了成功的事件,成功忽略掉了失敗的事件。這樣的好處是,可以為同一個事件定義成功和失敗不同的操作。
Spring事件的事務綁定
從Spring 4.2開始,框架提供了一個新的@TransactionalEventListener注解,它是@EventListener的擴展,允許將事件的偵聽器綁定到事務的一個階段。綁定可以進行以下事務階段:
- AFTER_COMMIT(默認的):在事務成功后觸發
- AFTER_ROLLBACK:事務回滾時觸發
- AFTER_COMPLETION:事務完成后觸發,不論是否成功
- BEFORE_COMMIT:事務提交之前觸發
總結
- Spring中處理事件的基礎知識:創建一個簡單的自定義事件,發布它,然后在監聽器中處理它。
- 在配置中啟用事件的異步處理。
- Spring 4.2中引入的改進,例如注釋驅動的偵聽器,更好的泛型支持以及綁定到事務階段的事件。
👉 本文代碼地址
