Spring事件監聽機制使用和原理解析

2023-06-12 12:01:16

你好,我是劉牌!

前言

好久沒有更新Spring了,今天來分享一下Spring的事件監聽機制,之前分享過一篇Spring監聽機制的使用,今天從原理上進行解析,Spring的監聽機制基於觀察者模式,就是就是我們所說的釋出訂閱模式,這種模式可以在一定程度上實現程式碼的解耦,如果想要實現系統層面的解耦,那麼訊息佇列就是我們的不二選擇,訊息佇列本身也是釋出訂閱模式,只是不同的訊息佇列的實現方式不一樣。

使用

之前的文章我們使用了註解的方式,今天我們使用介面的方式來實現。

定義事件

如下定義了一個事件AppEvent,它繼承了ApplicationEvent類,如果我們要使用Spring的事件監聽機制,那麼我們定義的事件必須繼承ApplicationEvent ,否則就無法使用。

/**
 * 功能說明: 事件
 * <p>
 * Original @Author: steakliu-劉牌, 2023-03-30  11:02
 * <p>
 * Copyright (C)2020-2022  steakliu All rights reserved.
 */
public class AppEvent extends ApplicationEvent {
    private final String event;

    public AppEvent(Object source, String event) {
        super(source);
        this.event = event;
    }

    public String getEvent() {
        return event;
    }
}

定義事件監聽器

事件監聽器實現了ApplicationLister介面,其泛型為ApplicationEvent,因為要監聽事件,所以必須按照Spring的規則來,onApplicationEvent方法就是監聽到的事件,在這裡我們可以進行我們的業務處理,我們可以看出AppLister我們加上了@Component註解,因為事件監聽器需要加入Spring IOC容器中才能生效。

/**
 * 功能說明:事件監聽器
 * <p>
 * Original @Author: steakliu-劉牌, 2023-03-30  11:03
 * <p>
 * Copyright (C)2020-2022  steakliu All rights reserved.
 */
@Component
public class AppListener implements ApplicationListener<AppEvent> {
    @Override
    public void onApplicationEvent(AppEvent event) {
        System.out.println("event:  "+event.getEvent());
    }
}

事件釋出器

有了事件監聽器,就需要釋出事件,所以就需要一個事件釋出器,事件釋出器使用的是ApplicationEventPublisher,使用它的publishEvent方法進行事件釋出。

/**
 * 功能說明:事件釋出器
 * <p>
 * Original @Author: steakliu-劉牌, 2023-06-11  13:55
 * <p>
 * Copyright (C)2020-2022  steakliu All rights reserved.
 */
@Component
public class AppPublisher {

    @Resource
    private ApplicationEventPublisher applicationEventPublisher;

    public void publish(){
        applicationEventPublisher.publishEvent(new AppEvent(new AppListener(),"publish event"));
    }
}

測試

為了方便,這裡直接使用SpringBoot來進行測試,先獲取AppPublisher,然後呼叫publish釋出事件。

@SpringBootApplication
public class Application {

    public static void main(String[] args) {
        ConfigurableApplicationContext context = SpringApplication.run(Application.class, args);
        AppPublisher publisher = context.getBean(AppPublisher.class);
        publisher.publish();
    }
}

上面整個事件釋出的程式碼就寫完了,我們可以看出其實還是比較簡單的,裡面最核心的三個元件分別為,事件(Event)監聽器(Listener)釋出器(Publisher),實際使用中我們可以根據自己的需求去實現。

原理

上面我們知道了Spring的事件監聽機制的基本使用,那麼整個事件在Spring中是怎麼流轉的呢,我們很有必要去弄清楚。

我們使用的是SpringBoot專案來進行測試,我們先找到SpringBoot對事件監聽機制進行處理的入口,然後再進行分析,SpringBoot對上下文進行處理的入口類是AbstractApplicationContext,它是Spring的入口,其中我們主要關注的refresh()方法,因為refresh中的方法比較多,我們下面只保留了三個方法。

@Override
	public void refresh() throws BeansException, IllegalStateException {
		synchronized (this.startupShutdownMonitor) {
    // Initialize event multicaster for this context.
				initApplicationEventMulticaster();
				// Check for listener beans and register them.
				registerListeners();
				// Last step: publish corresponding event.
				finishRefresh();
			}
		}
	}

initApplicationEventMulticaster()

ApplicationEventMulticaster是一個介面,它定義瞭如何將ApplicationEvent傳遞給事件監聽者(event listener)。該介面有多個實現類,可以使用不同的策略將事件分派給不同的監聽者。

ApplicationEventMulticaster為Spring事件機制的核心之一,它支援在應用中傳遞事件,並且可以將事件廣播給多個監聽者。在Spring中,事件是由ApplicationEvent及其子類表示的,例如ContextStartedEvent和ContextStoppedEvent等。當某些事件發生時,Spring容器將使用事件廣播機制來通知感興趣的監聽者。

這個方法的作用是對ApplicationEventMulticaster進行賦值,Spring在初始化的時候會將ApplicationEventMulticaster註冊進IOC容器,這裡就只是單純從IOC容器中獲取ApplicationEventMulticaster來進行賦值,以方便後續的使用。

protected void initApplicationEventMulticaster() {
        ConfigurableListableBeanFactory beanFactory = getBeanFactory();
        if (beanFactory.containsLocalBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME)) {
            this.applicationEventMulticaster =
                    beanFactory.getBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, ApplicationEventMulticaster.class);
            if (logger.isTraceEnabled()) {
                logger.trace("Using ApplicationEventMulticaster [" + this.applicationEventMulticaster + "]");
            }
        } else {
            this.applicationEventMulticaster = new SimpleApplicationEventMulticaster(beanFactory);
            beanFactory.registerSingleton(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, this.applicationEventMulticaster);
            if (logger.isTraceEnabled()) {
                logger.trace("No '" + APPLICATION_EVENT_MULTICASTER_BEAN_NAME + "' bean, using " +
                        "[" + this.applicationEventMulticaster.getClass().getSimpleName() + "]");
            }
        }
    }

registerListeners()

這個方法的作用主要就是註冊監聽器,它會從IOC容器獲取到我們註冊的監聽器,然後將其加入到Multicaster中,在AbstractApplicationEventMulticaster中,使用一個Set集合來裝監聽器。

public final Set<String> applicationListenerBeans = new LinkedHashSet<>();

finishRefresh()

finishRefresh()的作用是釋出事件,裡面是一些釋出事件的邏輯,但是由於我們還沒有正式釋出事件,所以這裡並不會釋出事件,當我們使用applicationEventPublisher的publishEvent方法釋出事件時,才會真正的釋出事件。

ApplicationEventPublisher釋出事件

上面範例中使用ApplicationEventPublisher的publishEvent釋出事件,最終會進入AbstractApplicationContext類中進行事件釋出,我們只關注最重要的方法multicastEvent(),它是廣播器ApplicationEventMulticaster的一個方法事件都是由廣播器進行釋出。

protected void publishEvent(Object event, @Nullable ResolvableType eventType) {
  getApplicationEventMulticaster().multicastEvent(applicationEvent, eventType);
}

ApplicationEventMulticaster真正釋出事件

ApplicationEventPublisher並沒有真正釋出事件,它相當於只是抽象了事件的釋出,為了讓我們更加簡單和方便使用,但是真正釋出事件的是ApplicationEventMulticaster,在multicastEvent()方法中,如果我們設定了執行緒池,那麼事件就會被加入執行緒池,從而非同步執行,如果沒有設定執行緒池,那麼就同步執行,最終執行都是呼叫invokeListener()方法。

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

預設是不會使用執行緒池的,如果我們需要事件非同步執行,那麼可以設定執行緒池,其核心就是給廣播器SimpleApplicationEventMulticaster的成員變數taskExecutor設定

/**
 * 功能說明: 事件任務執行緒池
 * <p>
 * Original @Author: steakliu-劉牌, 2023-06-11  13:17
 * <p>
 * Copyright (C)2020-2022  steakliu All rights reserved.
 */
@Configuration
public class TaskExecutor {

    @Bean("eventTaskExecutor")
    public Executor taskExecutor() {
        ThreadPoolTaskExecutor threadPoolTaskExecutor = new ThreadPoolTaskExecutor();
        threadPoolTaskExecutor.setCorePoolSize(10);
        threadPoolTaskExecutor.setMaxPoolSize(20);
        threadPoolTaskExecutor.setKeepAliveSeconds(10);
        threadPoolTaskExecutor.setThreadNamePrefix("application-event-thread");
        threadPoolTaskExecutor.setQueueCapacity(100);
        threadPoolTaskExecutor.setAllowCoreThreadTimeOut(true);
        threadPoolTaskExecutor.setAllowCoreThreadTimeOut(true);
        threadPoolTaskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.DiscardOldestPolicy());
        threadPoolTaskExecutor.initialize();
        return threadPoolTaskExecutor;
    }

    @Bean
    public ApplicationEventMulticaster applicationEventMulticaster() {
        SimpleApplicationEventMulticaster simpleApplicationEventMulticaster = new SimpleApplicationEventMulticaster();
        simpleApplicationEventMulticaster.setTaskExecutor(taskExecutor());
        return simpleApplicationEventMulticaster;
    }

}

invokeListener

invokeListener最終會通過傳入的監聽器去呼叫目標監聽器,也就是我們自定義的監聽器,主要程式碼如下,我們可以看到最終呼叫onApplicationEvent方法,就是我們上面範例AppListener監聽器的onApplicationEvent方法。

private void doInvokeListener(ApplicationListener listener, ApplicationEvent event) {
		 listener.onApplicationEvent(event);
}    

到這裡,整個流程就完了,我們梳理一下重要的元件。

  • ApplicationEvent
  • ApplicationListener
  • ApplicationEventPublisher
  • ApplicationEventMulticaster

上面的四個元件基本上就是Spring事件監聽機制的全部,ApplicationEvent是事件的規範,ApplicationListener是監聽器,ApplicationEventPublisher是釋出器,ApplicationEventMulticaster是廣播器,其實ApplicationEventMulticaster和ApplicationEventPublisher本質是一樣的,都能完成事件的釋出,ApplicationEventPublisher最終也是去呼叫ApplicationEventMulticaster,只不過它只專注於事件釋出,單獨提出一個介面來,職責更加單一,這也是一種設計思想。

總結

上面對Spring事件監聽機制的使用和原理進行了詳細的介紹,並對其中涉及的元件進行解析,Spring事件監聽機制是一個很不錯的功能,我們在進行業務開發的時候可以引入,在相關的開源框架中也是用它的身影,比如高效能閘道器ShenYu中就使用了Spring事件監聽機制來發布閘道器的更新資料,它可以降低系統的耦合性,使系統的擴充套件性更好。

今天的分享就到這裡,感謝你的觀看,我們下期見,如果文中有說得不合理或者不對的地方,希望你能指出,我們進行交流!