自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

使用上 Spring 的事件機制,真香!

開發(fā)
此文章主要是講解什么是Spring的事件機制,怎么使用Spring事件機制,工作中的場景有哪些。

本文主要是簡單的講述了Spring的事件機制,基本概念,講述了事件機制的三要素事件、事件發(fā)布、事件監(jiān)聽器。如何實現(xiàn)一個事件機制,應(yīng)用的場景,搭配@Async注解實現(xiàn)異步的操作等等。希望對大家有所幫助。

Spring的事件機制的基本概念

Spring的事件機制是Spring框架中的一個重要特性,基于觀察者模式實現(xiàn),它可以實現(xiàn)應(yīng)用程序中的解耦,提高代碼的可維護性和可擴展性。Spring的事件機制包括事件、事件發(fā)布、事件監(jiān)聽器等幾個基本概念。其中,事件是一個抽象的概念,它代表著應(yīng)用程序中的某個動作或狀態(tài)的發(fā)生。事件發(fā)布是事件發(fā)生的地方,它負責產(chǎn)生事件并通知事件監(jiān)聽器。事件監(jiān)聽器是事件的接收者,它負責處理事件并執(zhí)行相應(yīng)的操作。在Spring的事件機制中,事件源和事件監(jiān)聽器之間通過事件進行通信,從而實現(xiàn)了模塊之間的解耦。

舉個例子:用戶修改密碼,修改完密碼后需要短信通知用戶,記錄關(guān)鍵性日志,等等其他業(yè)務(wù)操作。

如下圖,就是我們需要調(diào)用多個服務(wù)來進行實現(xiàn)一個修改密碼的功能。

 使用了事件機制后,我們只需要發(fā)布一個事件,無需關(guān)心其擴展的邏輯,讓我們的事件監(jiān)聽器去處理,從而實現(xiàn)了模塊之間的解耦。

 事件

通過繼承ApplicationEvent,實現(xiàn)自定義事件。是對 Java EventObject 的擴展,表示 Spring 的事件,Spring 中的所有事件都要基于其進行擴展。其源碼如下。

我們可以獲取到timestamp屬性指的是發(fā)生時間。

 事件發(fā)布

事件發(fā)布是事件發(fā)生的地方,它負責產(chǎn)生事件并通知事件監(jiān)聽器。ApplicationEventPublisher用于用于發(fā)布 ApplicationEvent 事件,發(fā)布后 ApplicationListener 才能監(jiān)聽到事件進行處理。源碼如下。

需要一個ApplicationEvent,就是我們的事件,來進行發(fā)布事件。

 事件監(jiān)聽器

ApplicationListener 是 Spring 事件的監(jiān)聽器,用來接受事件,所有的監(jiān)聽器都必須實現(xiàn)該接口。該接口源碼如下。

 Spring的事件機制的使用方法

下面會給大家演示如何去使用Spring的事件機制。就拿修改密碼作為演示。

如何定義一個事件

新增一個類,繼承我們的ApplicationEvent。

如下面代碼,繼承后定義了一個userId,有一個UserChangePasswordEvent方法。這里就定義我們監(jiān)聽器需要的業(yè)務(wù)參數(shù),監(jiān)聽器需要那些參數(shù),我們這里就定義那些參數(shù)。

/**
 * @Author JiaQIng
 * @Description 修改密碼事件
 * @ClassName UserChangePasswordEvent
 * @Date 2023/3/26 13:55
 **/
@Getter
@Setter
public class UserChangePasswordEvent extends ApplicationEvent {
    private String userId;

    public UserChangePasswordEvent(String userId) {
        super(new Object());
        this.userId = userId;
    }
}

如何監(jiān)聽事件

實現(xiàn)監(jiān)聽器有兩種方法:

(1) 新建一個類實現(xiàn)ApplicationListener接口,并且重寫onApplicationEvent方法注入到Spring容器中,交給Spring管理如下代碼新建了一個發(fā)送短信監(jiān)聽器,收到事件后執(zhí)行業(yè)務(wù)操作****;

/**
 * @Author JiaQIng
 * @Description 發(fā)送短信監(jiān)聽器
 * @ClassName MessageListener
 * @Date 2023/3/26 14:16
 **/
@Component
public class MessageListener implements ApplicationListener<UserChangePasswordEvent> {

    @Override
    public void onApplicationEvent(UserChangePasswordEvent event) {
        System.out.println("收到事件:" + event);
        System.out.println("開始執(zhí)行業(yè)務(wù)操作給用戶發(fā)送短信。用戶userId為:" + event.getUserId());
    }
}

(2) 使用@EventListener注解標注處理事件的方法,此時Spring將創(chuàng)建一個ApplicationListenerbean對象,使用給定的方法處理事件源碼如下參數(shù)可以給指定的事件這里巧妙的用到了@AliasFor的能力,放到了@EventListener身上注意:一般建議都需要指定此值,否則默認可以處理所有類型的事件,范圍太廣了;

 代碼如下。新建一個事件監(jiān)聽器,注入到Spring容器中,交給Spring管理。在指定方法上添加@EventListener參數(shù)為監(jiān)聽的事件。方法為業(yè)務(wù)代碼。使用 @EventListener 注解的好處是一個類可以寫很多監(jiān)聽器,定向監(jiān)聽不同的事件,或者同一個事件。

/**
 * @Author JiaQIng
 * @Description 事件監(jiān)聽器
 * @ClassName LogListener
 * @Date 2023/3/26 14:22
 **/
@Component
public class ListenerEvent {

    @EventListener({ UserChangePasswordEvent.class })
    public void LogListener(UserChangePasswordEvent event) {
        System.out.println("收到事件:" + event);
        System.out.println("開始執(zhí)行業(yè)務(wù)操作生成關(guān)鍵日志。用戶userId為:" + event.getUserId());
    }

    @EventListener({ UserChangePasswordEvent.class })
    public void messageListener(UserChangePasswordEvent event) {
        System.out.println("收到事件:" + event);
        System.out.println("開始執(zhí)行業(yè)務(wù)操作給用戶發(fā)送短信。用戶userId為:" + event.getUserId());
    }
}

(3)  @TransactionalEventListener來定義一個監(jiān)聽器,他與@EventListener不同的就是@EventListener標記一個方法作為監(jiān)聽器,他默認是同步執(zhí)行,如果發(fā)布事件的方法處于事務(wù)中,那么事務(wù)會在監(jiān)聽器方法執(zhí)行完畢之后才提交事件發(fā)布之后就由監(jiān)聽器去處理,而不要影響原有的事務(wù),也就是說希望事務(wù)及時提交我們就可以使用該注解來標識注意此注解需要spring-tx的依賴;

注解源碼如下:主要是看一下注釋內(nèi)容。

// 在這個注解上面有一個注解:@EventListener,所以表明其實這個注解也是個事件監(jiān)聽器。 
@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@EventListener
public @interface TransactionalEventListener {

 /**
  * 這個注解取值有:BEFORE_COMMIT(指定目標方法在事務(wù)commit之前執(zhí)行)、AFTER_COMMIT(指定目標方法在事務(wù)commit之后執(zhí)行)、
  * AFTER_ROLLBACK(指定目標方法在事務(wù)rollback之后執(zhí)行)、AFTER_COMPLETION(指定目標方法在事務(wù)完成時執(zhí)行,這里的完成是指無論事務(wù)是成功提交還是事務(wù)回滾了)
  * 各個值都代表什么意思表達什么功能,非常清晰,
  * 需要注意的是:AFTER_COMMIT + AFTER_COMPLETION是可以同時生效的
  * AFTER_ROLLBACK + AFTER_COMPLETION是可以同時生效的
  */
 TransactionPhase phase() default TransactionPhase.AFTER_COMMIT;

 /**
  * 表明若沒有事務(wù)的時候,對應(yīng)的event是否需要執(zhí)行,默認值為false表示,沒事務(wù)就不執(zhí)行了。
  */
 boolean fallbackExecution() default false;

 /**
  *  這里巧妙的用到了@AliasFor的能力,放到了@EventListener身上
  *  注意:一般建議都需要指定此值,否則默認可以處理所有類型的事件,范圍太廣了。
  */
 @AliasFor(annotation = EventListener.class, attribute = "classes")
 Class<?>[] value() default {};

 /**
  * The event classes that this listener handles.
  * <p>If this attribute is specified with a single value, the annotated
  * method may optionally accept a single parameter. However, if this
  * attribute is specified with multiple values, the annotated method
  * must <em>not</em> declare any parameters.
  */
 @AliasFor(annotation = EventListener.class, attribute = "classes")
 Class<?>[] classes() default {};

 /**
  * Spring Expression Language (SpEL) attribute used for making the event
  * handling conditional.
  * <p>The default is {@code ""}, meaning the event is always handled.
  * @see EventListener#condition
  */
 @AliasFor(annotation = EventListener.class, attribute = "condition")
 String condition() default "";

 /**
  * An optional identifier for the listener, defaulting to the fully-qualified
  * signature of the declaring method (e.g. "mypackage.MyClass.myMethod()").
  * @since 5.3
  * @see EventListener#id
  * @see TransactionalApplicationListener#getListenerId()
  */
 @AliasFor(annotation = EventListener.class, attribute = "id")
 String id() default "";

}

使用方式如下。phase事務(wù)類型,value指定事件。

/**
 * @Author JiaQIng
 * @Description 事件監(jiān)聽器
 * @ClassName LogListener
 * @Date 2023/3/26 14:22
 **/
@Component
public class ListenerEvent {

    @EventListener({ UserChangePasswordEvent.class })
    public void logListener(UserChangePasswordEvent event) {
        System.out.println("收到事件:" + event);
        System.out.println("開始執(zhí)行業(yè)務(wù)操作生成關(guān)鍵日志。用戶userId為:" + event.getUserId());
    }

    @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT,value = { UserChangePasswordEvent.class })
    public void messageListener(UserChangePasswordEvent event) {
        System.out.println("收到事件:" + event);
        System.out.println("開始執(zhí)行業(yè)務(wù)操作給用戶發(fā)送短信。用戶userId為:" + event.getUserId());
    }
}

如何發(fā)布一個事件

(1) 使用ApplicationContext進行發(fā)布,由于ApplicationContext已經(jīng)繼承了ApplicationEventPublisher,因此可以直接使用發(fā)布事件源碼如下;

 (2) 直接注入我們的ApplicationEventPublisher,使用@Autowired注入一下;

三種發(fā)布事件的方法,我給大家演示一下@Autowired注入的方式發(fā)布我們的事件。

@SpringBootTest
class SpirngEventApplicationTests {
    @Autowired
    ApplicationEventPublisher appEventPublisher;
    @Test
    void contextLoads() {
        appEventPublisher.publishEvent(new UserChangePasswordEvent("1111111"));
    }

}

我們執(zhí)行一下看一下接口:

 測試成功。

搭配@Async注解實現(xiàn)異步操作

監(jiān)聽器默認是同步執(zhí)行的,如果我們想實現(xiàn)異步執(zhí)行,可以搭配@Async注解使用,但是前提條件是你真的懂@Async注解,使用不當會出現(xiàn)問題的。 后續(xù)我會出一篇有關(guān)@Async注解使用的文章。這里就不給大家詳細的解釋了。有想了解的同學(xué)可以去網(wǎng)上學(xué)習(xí)一下有關(guān)@Async注解使用。

使用@Async時,需要配置線程池,否則用的還是默認的線程池也就是主線程池,線程池使用不當會浪費資源,嚴重的會出現(xiàn)OOM事故。

下圖是阿里巴巴開發(fā)手冊的強制要求。

 簡單的演示一下:這里聲明一下俺沒有使用線程池,只是簡單的演示一下。

(1) 在我們的啟動類上添加@EnableAsync開啟異步執(zhí)行配置;

@EnableAsync
@SpringBootApplication
public class SpirngEventApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpirngEventApplication.class, args);
    }

}

(2) 在我們想要異步執(zhí)行的監(jiān)聽器上添加@Async注解:

/**
 * @Author JiaQIng
 * @Description 事件監(jiān)聽器
 * @ClassName LogListener
 * @Date 2023/3/26 14:22
 **/
@Component
public class ListenerEvent {
    
    @Async
    @EventListener({ UserChangePasswordEvent.class })
    public void logListener(UserChangePasswordEvent event) {
        System.out.println("收到事件:" + event);
        System.out.println("開始執(zhí)行業(yè)務(wù)操作生成關(guān)鍵日志。用戶userId為:" + event.getUserId());
    }
}

這樣我們的異步執(zhí)行監(jiān)聽器的業(yè)務(wù)操作就完成了。

Spring的事件機制的應(yīng)用場景

  • 告警操作,比喻釘釘告警,異常告警,可以通過事件機制進行解耦;
  • 關(guān)鍵性日志記錄和業(yè)務(wù)埋點,比喻說我們的關(guān)鍵日志需要入庫,記錄一下操作時間,操作人,變更內(nèi)容等等,可以通過事件機制進行解耦;
  • 性能監(jiān)控,比喻說一些接口的時長,性能方便的埋點等可以通過事件機制進行解耦;
  • 一切與主業(yè)務(wù)無關(guān)的操作都可以通過這種方式進行解耦,常用的場景大概就上述提到的,而且很多架構(gòu)的源碼都有使用這種機制,如GateWay,Spring等等;

Spring的事件機制的注意事項

  • 對于同一個事件,有多個監(jiān)聽器的時候,注意可以通過@Order注解指定順序,Order的value值越小,執(zhí)行的優(yōu)先級就越高;
  • 如果發(fā)布事件的方法處于事務(wù)中,那么事務(wù)會在監(jiān)聽器方法執(zhí)行完畢之后才提交事件發(fā)布之后就由監(jiān)聽器去處理,而不要影響原有的事務(wù),也就是說希望事務(wù)及時提交我們就可以@TransactionalEventListener來定義一個監(jiān)聽器;
  • 監(jiān)聽器默認是同步執(zhí)行的,如果我們想實現(xiàn)異步執(zhí)行,可以搭配@Async注解使用,但是前提條件是你真的懂@Async注解,使用不當會出現(xiàn)問題的;
  • 對于同一個事件,有多個監(jiān)聽器的時候,如果出現(xiàn)了異常,后續(xù)的監(jiān)聽器就失效了,因為他是把同一個事件的監(jiān)聽器add在一個集合里面循環(huán)執(zhí)行,如果出現(xiàn)異常,需要注意捕獲異常處理異常;
責任編輯:趙寧寧 來源: 技術(shù)老男孩
相關(guān)推薦

2023-09-07 10:31:27

2022-12-19 08:32:57

項目Feign框架

2022-12-13 08:29:13

項目插入式注解

2024-09-14 09:59:04

2024-12-23 06:40:00

2024-06-28 08:21:20

前端自動化部署

2023-11-01 08:22:07

Spring發(fā)布器源對象

2025-03-11 00:35:00

Spring事件機制

2020-05-27 20:25:47

SpringSpringBoot數(shù)據(jù)

2022-03-04 15:19:59

Spring BooJavaVert.x

2010-07-29 10:33:59

Flex鍵盤事件

2020-08-19 09:45:29

Spring數(shù)據(jù)庫代碼

2023-02-08 08:11:58

Spring容器核心事件

2023-10-08 08:23:44

Android事件邏輯

2023-10-30 07:36:19

Spring事務(wù)傳播機制

2010-06-22 10:05:36

Linux監(jiān)控

2023-02-10 08:56:30

2010-08-12 15:35:44

Flex事件機制

2010-08-06 09:45:50

Flex事件機制

2022-11-15 07:35:50

Spring事件觀察者模式
點贊
收藏

51CTO技術(shù)棧公眾號