天天看點

Spring | 事件監聽器應用與最佳實踐

作者:愛摸魚的程式員

引言

在複雜的軟體開發環境中,元件之間的通信和資訊交流顯得尤為重要。Spring架構,作為Java世界中最受歡迎的開發架構之一,提供了一種強大的事件監聽器模型,使得元件間的通信變得更加靈活和解耦。本文主要探讨Spring事件監聽器的原理、使用方法及其在實際開發中的應用,希望為廣大開發者提供實用的參考。

1.1 Spring事件監聽器簡介

Spring事件監聽器是Spring應用中用于處理事件的一種機制。事件通常代表應用狀态的變化,而監聽器則負責響應這些變化。通過Spring的事件監聽器,開發者可以在解耦的前提下,實作不同元件間的資訊交流,提高代碼的可維護性和可擴充性。

1.2 文章目的

本文旨在深入探讨Spring事件監聽器的基本原理,引導讀者如何在實際開發中使用監聽器,并通過一些具體的例子來展示監聽器的使用場景和實作方法。我們還将深入分析Spring監聽器的源碼,以期讀者能更加深刻地了解其工作原理。希望通過本文,讀者可以更加熟練地利用Spring事件監聽器來建構靈活、可維護的應用。

以下所有示例均已上傳至Github上,大家可以将項目拉取到本地進行運作

Github示例(如果對Gradle還不熟練,建議翻看我之前的文章):gradle-spring-boot-demo

Spring事件監聽器原理

了解Spring事件監聽器的原理,是有效使用此機制的前提。這一章将深入探讨Spring事件監聽器的核心元件以及它們如何協同工作。

2.1 元件介紹

在Spring的事件監聽器模型中,主要涉及三個核心元件:事件(Event)、監聽器(Listener)和事件釋出器(Event Publisher)。

2.1.1 事件(Event)

事件通常是由某個特定的動作或者狀态變化觸發的。在Spring中,自定義事件通常需要繼承ApplicationEvent類。事件類包含了事件的基本資訊,例如事件源、發生時間等。

import org.springframework.context.ApplicationEvent;

public class CustomEvent extends ApplicationEvent {
    public CustomEvent(Object source) {
        super(source);
    }
}
           

2.1.2 監聽器(Listener)

監聽器負責接收并處理事件。在Spring中,監聽器通常是實作ApplicationListener接口的類,需要定義一個onApplicationEvent方法來具體處理事件。

import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Component;

@Component
public class CustomEventListener implements ApplicationListener<CustomEvent> {
    @Override
    public void onApplicationEvent(CustomEvent event) {
        // 處理事件
        System.out.println("Received custom event - " + event);
    }
}
           

2.1.3 事件釋出器(Event Publisher)

事件釋出器的角色是将事件通知到所有注冊的監聽器。在Spring應用中,ApplicationEventPublisher接口負責事件的釋出。

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Component;

@Component
public class CustomEventPublisher {
    @Autowired
    private ApplicationEventPublisher applicationEventPublisher;

    public void doSomethingAndPublishAnEvent(final String message) {
        System.out.println("Publishing custom event.");
        CustomEvent customEvent = new CustomEvent(message);
        applicationEventPublisher.publishEvent(customEvent);
    }
}
           

2.2 工作流程

事件監聽器模型的基本工作流程如下:

  1. 事件源産生事件。
  2. 事件釋出器釋出事件。
  3. 注冊的監聽器接收到事件後進行處理。 這種模型支援了元件之間的低耦合互動,使得開發者可以更靈活、更友善地進行開發。

如何使用Spring監聽器

掌握了Spring事件監聽器的基本原理群組成部分後,我們将進一步探讨如何在實際開發中使用它。通過定義事件、建立監聽器和釋出事件,我們可以實作不同元件間的資訊交流。

3.1 定義事件

在Spring中,我們可以通過繼承ApplicationEvent類來定義自己的事件。這個類需要包含所有與事件相關的資訊。

public class TestEvent extends ApplicationEvent {
    private String message;

    public TestEvent(Object source, String message) {
        super(source);
        this.message = message;
    }

    public String getMessage() {
        return message;
    }
}
           

在這個例子中,我們建立了一個名為TestEvent的事件類,該類中含有一個字元串類型的message字段,用于傳遞事件相關的資訊。

3.2 建立監聽器

事件定義好後,我們需要建立監聽器來處理這個事件。監聽器是實作了ApplicationListener接口的類,需要覆寫onApplicationEvent方法來定義事件的處理邏輯。

@Component
public class TestEventListener implements ApplicationListener<TestEvent> {
    @Override
    public void onApplicationEvent(TestEvent testEvent) {
        // [3]在這裡可以執行監聽到事件後的邏輯, 監聽到事件源,觸發動作!
        System.out.println("監聽到TestEvent:" + testEvent.getMessage());
    }
}
           

在這個例子中,我們定義了一個監聽器TestEventListener,該監聽器會列印出接收到的TestEvent事件中的message資訊。

3.3 釋出事件

最後,我們需要釋出事件。事件的釋出通常由事件釋出器ApplicationEventPublisher來完成。

@Component
public class TestEventPublisher {
    @Autowired
    private ApplicationEventPublisher applicationEventPublisher;

    public void publish(String message) {
        // [2]使用publishEvent方法釋出事件,事件源為TestEvent
        applicationEventPublisher.publishEvent(new TestEvent(this, message));
    }
}
           

在這個例子中,TestEventPublisher類中的publishEvent方法會建立并釋出一個新的TestEvent事件。 通過這三個步驟,我們就可以在Spring應用中實作事件的定義、監聽和釋出。這不僅有助于元件間的解耦,還能夠增強代碼的可維護性和可擴充性。

3.4 代碼測試

@SpringBootTest
class GradleSpringBootDemoApplicationTests {


	@Autowired
	private TestEventPublisher testEventPublisher;

	@Test
	void contextLoads() {
		// [1] 釋出事件
		testEventPublisher.publish("Hello, Spring!");
	}
}
           

執行完成,結果如下:

Spring | 事件監聽器應用與最佳實踐

基于監聽器設計模式的手寫案例

為了更深入地了解Spring的監聽器模式,我們來手寫一個基于監聽器設計模式的簡單案例,逐漸展示如何設計事件、監聽器以及如何釋出事件。

4.1 設計目标

我們将建立一個簡單的使用者注冊系統。在使用者成功注冊之後,系統會釋出一個注冊事件,相關的監聽器将監聽這個事件,然後執行相應的操作,如發送歡迎郵件和記錄日志。

4.2 實作步驟

4.2.1 定義事件

首先,我們定義一個使用者注冊成功的事件。該事件包含了使用者的基本資訊。

public class UserRegisterEvent {
    private final String username;
    private final String email;

    public UserRegisterEvent(String username, String email) {
        this.username = username;
        this.email = email;
    }

    // Getters
}
           

4.2.2 建立監聽器

接下來,我們建立兩個監聽器:一個負責發送歡迎郵件,另一個負責記錄使用者注冊日志。

public class WelcomeEmailListener {
    public void sendWelcomeEmail(UserRegisterEvent event) {
        System.out.println("Sending welcome email to " + event.getEmail());
    }
}

public class UserRegisterLogListener {
    public void logUserRegister(UserRegisterEvent event) {
        System.out.println("Logging user register: " + event.getUsername());
    }
}
           

4.2.3 釋出事件

最後,我們建立一個使用者注冊服務,該服務在使用者注冊成功後釋出事件。

import java.util.ArrayList;
import java.util.List;

public class UserRegisterService {
    private final List<Object> listeners = new ArrayList<>();

    public void registerUser(String username, String email) {
        // 使用者注冊邏輯(略)
        System.out.println("User registered: " + username);
        
        // 釋出事件
        UserRegisterEvent event = new UserRegisterEvent(username, email);
        for (Object listener : listeners) {
            if (listener instanceof WelcomeEmailListener) {
                ((WelcomeEmailListener) listener).sendWelcomeEmail(event);
            } else if (listener instanceof UserRegisterLogListener) {
                ((UserRegisterLogListener) listener).logUserRegister(event);
            }
        }
    }

    public void addListener(Object listener) {
        listeners.add(listener);
    }
}
           

我們可以添加一個main方法來模拟使用者的注冊過程并觸發事件的釋出和監聽。

public class Runner {
    public static void main(String[] args) {
        // 建立UserRegisterService執行個體
        UserRegisterService userRegisterService = new UserRegisterService();

        // 向UserRegisterService中添加監聽器
        userRegisterService.addListener(new WelcomeEmailListener());
        userRegisterService.addListener(new UserRegisterLogListener());

        // 模拟使用者注冊
        userRegisterService.registerUser("JohnDoe", "[email protected]");
    }
}
           

當你運作這個main方法時,UserRegisterService将執行注冊邏輯,之後釋出UserRegisterEvent事件,而WelcomeEmailListener和UserRegisterLogListener監聽器将會捕獲到這個事件并執行相應的操作。

Spring | 事件監聽器應用與最佳實踐

運作結果如下:

User registered: kfaino
Sending welcome email to [email protected]
Logging user register: kfaino
           

Spring監聽器源碼解讀

在本章中,我們将探讨Spring監聽器的實作細節,以更深入地了解Spring是如何設計和實作事件監聽器的。

5.1 ApplicationEvent和ApplicationListener

ApplicationEvent和ApplicationListener是Spring事件監聽機制的基石。

5.1.1 ApplicationEvent

ApplicationEvent是所有Spring事件的基類,它繼承自java.util.EventObject。它包含了事件源和事件發生的時間戳。

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

5.1.2 ApplicationListener

ApplicationListener是一個泛型接口,用于處理特定類型的事件。它包含一個方法onApplicationEvent,使用者需要實作該方法來定義事件處理邏輯。

@FunctionalInterface
public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {
    void onApplicationEvent(E event);
}
           

5.2 事件的釋出

ApplicationEventPublisher是事件釋出的核心接口。它定義了publishEvent方法,用于釋出事件到所有比對的監聽器。

public interface ApplicationEventPublisher {
    void publishEvent(ApplicationEvent event);
    void publishEvent(Object event);
}
           

ApplicationContext繼承了ApplicationEventPublisher接口,是以在Spring容器中,可以直接使用ApplicationContext來釋出事件。

5.3 事件的傳播

在Spring中,事件的傳播是通過SimpleApplicationEventMulticaster類來實作的。這個類有一個multicastEvent方法,它會将事件傳遞給所有比對的監聽器。

public class SimpleApplicationEventMulticaster extends AbstractApplicationEventMulticaster {

    @Override
    public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
        for (final ApplicationListener<?> listener : getApplicationListeners(event, eventType)) {
            Executor executor = getTaskExecutor();
            if (executor != null) {
                executor.execute(() -> invokeListener(listener, event));
            } else {
                invokeListener(listener, event);
            }
        }
    }
}
           

此方法中,getApplicationListeners用于擷取所有比對的監聽器,然後invokeListener方法被用來觸發這些監聽器。

5.4 總結

通過深入分析Spring事件監聽器的源碼,我們可以更清晰地了解Spring是如何實作事件的定義、釋出和處理的,這有助于我們更有效地在實際開發中使用這一機制。

Spring内置事件

Spring架構本身提供了一些内置的事件,這些事件代表了容器的一些生命周期階段或特定操作,可以幫助我們更好地監控和管理應用。

6.1 ContextRefreshedEvent

ContextRefreshedEvent事件在Spring容器初始化或重新整理時觸發,即當所有的Bean都已經被成功加載、後處理器已經被調用,和所有單例Bean都已經被預執行個體化之後。

@EventListener
public void handleContextRefresh(ContextRefreshedEvent event) {
    System.out.println("Context Refreshed: " + event.getTimestamp());
}
           

在SpringBoot中,我們可以編寫如下代碼:

@Component
public class MyServletContextListener implements ApplicationListener<ContextRefreshedEvent> {
    @Override
    public void onApplicationEvent(ContextRefreshedEvent contextRefreshedEvent) {
        // TODO 在這裡可以執行一些初始化操作,比如查詢資料庫,緩存資料,加載配置等
        System.out.println("Spring容器加載完成觸發");
    }
}
           

在Spring完成初始化後進行回調:

Spring | 事件監聽器應用與最佳實踐

6.2 ContextClosedEvent

當Spring容器被關閉時,ContextClosedEvent事件會被觸發。在這個階段,所有的單例Bean都已經被銷毀。

@EventListener
public void handleContextClose(ContextClosedEvent event) {
    System.out.println("Context Closed: " + event.getTimestamp());
}
           

6.3 ContextStartedEvent

當使用ConfigurableApplicationContext的start()方法啟動Spring上下文時,會觸發ContextStartedEvent事件。

@EventListener
public void handleContextStart(ContextStartedEvent event) {
    System.out.println("Context Started: " + event.getTimestamp());
}
           

6.4 ContextStoppedEvent

相對應地,當使用ConfigurableApplicationContext的stop()方法停止Spring上下文時,會觸發ContextStoppedEvent事件。

@EventListener
public void handleContextStop(ContextStoppedEvent event) {
    System.out.println("Context Stopped: " + event.getTimestamp());
}
           

6.5 ApplicationReadyEvent

ApplicationReadyEvent事件在Spring應用運作完畢并準備接受請求時觸發。

@EventListener
public void handleApplicationReady(ApplicationReadyEvent event) {
    System.out.println("Application Ready: " + event.getTimestamp());
}
           

6.6 其他事件

除了上述事件外,Spring還提供了一系列其他的内置事件,如RequestHandledEvent、ServletRequestHandledEvent等,可以幫助我們更全面地了解和管理應用的運作狀态。

6.7 總結

了解和利用Spring的内置事件,可以幫助我們更加友善快捷地監控應用的生命周期和運作狀态,優化應用性能和穩定性。同時,這也為我們提供了一種友善的手段,通過監聽這些事件,執行自定義的邏輯,滿足不同的業務需求。

優缺點分析

接下來,我們将詳細探讨Spring監聽器的優點和缺點。了解這些優缺點将幫助我們更為明智地決定何時以及如何使用Spring監聽器。

7.1 優點

  • 低耦合性: Spring監聽器允許不同元件之間互動,而無需它們直接互相引用,有助于實作代碼的低耦合和高内聚。
  • 易于擴充: 通過監聽器,可以友善地對系統進行擴充,為系統添加新的功能或行為,而無需修改現有代碼。
  • 強大的事件處理能力: Spring提供的事件處理機制強大而靈活,可應對各種複雜的業務場景,滿足多樣化的業務需求。
  • 提高子產品性: 監聽器可以清晰地分隔關注點,有助于将不同功能的代碼組織在不同的子產品中,提高了代碼的可維護性和可讀性。

7.2 缺點

  • 性能開銷: 監聽器的使用會帶來一定的性能開銷,特别是在大量事件被觸發和處理時,這可能會成為系統性能的瓶頸。
  • 複雜性: 當系統中存在大量的監聽器和事件時,管理和維護這些監聽器和事件的複雜性将增加,可能導緻錯誤和難以調試的問題。
  • 不适合所有場景: 監聽器并不适合所有場景。在一些簡單的、需要快速響應的場合,引入監聽器可能會顯得過于重和繁瑣。
建議: 在考慮使用Spring監聽器時,應該權衡其帶來的便利性和可能的缺點。在确實需要利用事件來實作子產品間解耦的複雜業務場景下,Spring監聽器是一個非常合适的選擇。但是,在不需要解耦的簡單場景下,應該考慮避免使用監聽器,以減少不必要的複雜性和性能開銷。

最佳實踐

在實際開發中,如何更為合理和高效地使用Spring監聽器是至關重要的。以下是一些關于使用Spring監聽器的最佳實踐,可以幫助您更加明智和靈活地應用Spring監聽器。

  • 明确事件類型:在定義事件時,要清晰、明确地标明事件的類型和它所攜帶的資訊,確定事件可以準确地反映出系統的狀态變化。這也有助于代碼的可讀性和可維護性。
  • 合理劃分監聽器職責:每個監聽器都應該有一個明确且單一的職責。避免在一個監聽器中處理過多不相關的邏輯,這将使得監聽器變得複雜并難以維護。
  • 優化事件釋出:避免過度釋出事件。例如,在循環中釋出事件,或釋出含有大量不必要資訊的事件,都可能導緻性能問題。在釋出事件時要精确控制事件的範圍和内容,避免不必要的性能開銷。
  • 使用異步監聽器:在适合的場合,利用異步監聽器可以提高系統的響應性和吞吐量。異步監聽器可以在單獨的線程中處理事件,防止阻塞主線程,提高系統的可用性。
  • java
  • 複制代碼
  • @Async @EventListener public void handleAsyncEvent(MyEvent event) { // 處理事件 }
  • 設計好事件傳播機制:根據業務需求,合理設計事件的傳播機制。有時,事件需要按照一定的順序傳播,或者在某個監聽器處理後停止傳播,這時就需要精心設計事件的傳播政策。
  • 有效管理監聽器:對于系統中的所有監聽器,需要進行有效的管理和維護。定期審查監聽器的代碼,確定其符合設計原則,同時要及時更新和優化監聽器,保持其高效運作。
  • 注重監聽器的測試:監聽器中的業務邏輯也需要進行充分的測試。針對監聽器的不同邏輯,編寫單元測試和內建測試,確定監聽器在各種情況下都能正确工作。
  • 文檔和注釋:為監聽器和事件提供清晰、完整的文檔和注釋,有助于團隊成員了解代碼的功能和用法,提高團隊的開發效率。

總結

在本文中,我們深入探讨了Spring監聽器的原理、使用方法、基于監聽器設計模式的實際案例、Spring的内置事件、源碼分析、優缺點以及最佳實踐。下面我們将進行一個簡短的回顧和總結。

9.1 回顧

通過學習,我們了解到:

  • Spring監聽器原理:Spring監聽器是基于觀察者設計模式實作的,允許我們在不修改已有代碼的基礎上,增加對特定事件的響應。
  • 使用方法:我們學習了如何定義、注冊和使用監聽器以及如何釋出事件。
  • 手寫案例:我們通過一個實際案例了解了如何基于監聽器設計模式來實作事件監聽和處理。
  • Spring内置事件:Spring提供了一系列内置事件,幫助我們更好地管理和監控應用的生命周期和運作狀态。
  • 源碼分析:我們深入源碼,探究了Spring監聽器的工作機制和實作細節。
  • 優缺點:我們分析了Spring監聽器的優缺點,明白在什麼場景下使用監聽器是合适的,以及需要注意的問題。
  • 最佳實踐:我們學習了一系列最佳實踐,以指導如何更加合理和高效地使用Spring監聽器。

9.2 結語

希望本文能幫助您更深入地了解Spring監聽器,掌握其使用方法和最佳實踐,進而更為高效地開發出優質的軟體産品。同時,也期望您能夠不斷學習、實踐和探索,發現更多的使用Spring監聽器的可能性和創新方法。 如果您對本文有任何建議或問題,請随時提出。感謝您的閱讀!

作者:Kfaino

連結:https://juejin.cn/post/7298636610609807411