Spring Event事件驅動


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消費者
  2. 1生產者 - 多消費者
  3. 多生產者 - 多消費者

上面舉的例子是第一種情況,我們來試試其他兩個情況

繼續創建一個事件監聽器作為消費者:

@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;
    }
}

注意GenericSpringEventCustomSpringEvent之間的區別。我們現在可以靈活地發布任何任意事件,並且不再需要從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:事務提交之前觸發

總結

  1. Spring中處理事件的基礎知識:創建一個簡單的自定義事件,發布它,然后在監聽器中處理它。
  2. 在配置中啟用事件的異步處理。
  3. Spring 4.2中引入的改進,例如注釋驅動的偵聽器,更好的泛型支持以及綁定到事務階段的事件。

👉 本文代碼地址


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM