試試使用Spring Event組合@Async註解,輕鬆實現程式碼的解耦和非同步

一 前言

在我們寫程式碼的時候,通常需要考慮到程式碼的耦合性,因為低耦合的程式碼有利於我們後續的維護和迭代,而Spring Event可以說是一個降低程式碼耦合度的神器,配合@Async註解更是能夠輕鬆實現非同步。今天我們就一起來了解一下Spring Event。

二:如何使用Spring Event

我們以一個簡單的業務場景為例:

使用者註冊賬號之後,我們需要贈送使用者500積分

1.定義Event事件類和DTO傳輸資料物件

首先我們需要定義一個增加積分的事件,而這個類需要繼承ApplicationEvent類。

public class AddCreditEvent extends ApplicationEvent { private static final long serialVersionUID = 303142463705896963L; public AddCreditEvent(Object source) { super(source); }}複製程式碼

@Datapublic class AddCreditDTO { private Integer userId; private Integer creditAmount; }複製程式碼

2.釋出一個增加積分的事件

我們在使用者進行註冊之後,需要贈送使用者積分,因此,我們在使用者註冊之後需要釋出一個增加積分的事件。我們在UserServiceImpl中實現ApplicationEventPublisherAware介面,實現setApplicationEventPublisher()方法,獲取到applicationEventPublisher物件,透過applicationEventPublisher物件的publishEvent()方法就可以釋出對應的事件了。

public class UserServiceImpl implements UserService, ApplicationEventPublisherAware { private ApplicationEventPublisher applicationEventPublisher; @Override public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) { this。applicationEventPublisher = applicationEventPublisher; } @Override public void register(RegisterReqDTO reqDTO) { //註冊使用者 registerUser(reqDTO); //釋出增加積分事件 新增積分 AddCreditDTO addCreditDTO = new AddCreditDTO(); addCreditDTO。setUserId(user。getId()); addCreditDTO。setCreditAmount(500); applicationEventPublisher。publishEvent(new AddCreditEvent(addCreditDTO)); }}複製程式碼

3.建立監聽類監聽釋出的事件

釋出完事件之後,我們需要有對應的監聽類去監聽我們釋出的事件,在監聽類中執行我們對應的業務邏輯。監聽類需要實現ApplicationListener類,並且設定泛型為我們釋出的事件型別,同時我們需要將監聽類交給Spring管理(所以我們加上@Component註解)。這樣在onApplicationEvent()方法中就可以執行我們的業務邏輯了。

@Componentpublic class AddCreditEventListener implements ApplicationListener { @Override public void onApplicationEvent(AddCreditEvent event) { Object source = event。getSource(); AddCreditDTO addCreditDTO = (AddCreditDTO)source; //新增積分業務程式碼 。。。。 }}複製程式碼

三:配合@Async註解實現非同步

1.啟動類上新增@EnableAsync註解

在啟動類上新增@EnableAsync註解

@SpringBootApplication@EnableAsyncpublic class UserApplication { public static void main(String[] args) { SpringApplication。run(UserApplication。class,args); }}複製程式碼

2.在onApplicationEvent()方法上新增@Async註解

在onApplicationEvent方法上新增@Async註解就可以輕鬆實現非同步了,但是並不推薦直接使用@Async註解,可以配置一個自定義執行緒池,根據業務以及系統資源配置好最大執行緒數,核心執行緒數,阻塞佇列等執行緒池引數。

注:為什麼不推薦直接使用@Async?因為在Springboot環境中,@Async預設使用的執行緒池最大執行緒數是Integer。MAX,並且阻塞佇列的大小也是Integer。MAX,這顯然是不合理的,所以我們最好自己定義執行緒池,然後指定@Async的value屬性。

@Componentpublic class AddCreditEventListener implements ApplicationListener { @Override @Async public void onApplicationEvent(AddCreditEvent event) { Object source = event。getSource(); AddCreditDTO addCreditDTO = (AddCreditDTO)source; //新增積分業務程式碼 。。。。 }}複製程式碼

四:利用事件機制完成業務邏輯有什麼好處?

1.降低程式碼的耦合度

如果我需要新增積分,那麼我就釋出一個新增積分的事件,需要成為會員,那麼我就釋出一個成為會員的事件,透過不同的事件,將業務邏輯解耦,只需要釋出事件,不需要關注具體的實現邏輯,程式碼的條理更清晰。方便以後的擴充套件與維護工作。

2.增強程式碼的複用性

例如註冊使用者可能需要新增積分,購買商品之後也需要新增積分,那麼我們就都可以透過釋出一個新增積分的事件來完成了。程式碼的複用性得到了很大的提高。

五:Spring Event的實現原理

1。首先在Spring容器啟動時,在Application的refresh()方法中會呼叫prepareBeanFactory()方法,在prepareBeanFactory()方法中會註冊一個ApplicationListenerDetector的類,ApplicationListenerDetector實現了BeanPostProcessor,在postProcessAfterInitialization()方法中儲存所有的實現了ApplicationListener的類。

protected void prepareBeanFactory(ConfigurableListableBeanFactory beanFactory) { 。。。 // Register early post-processor for detecting inner beans as ApplicationListeners。 beanFactory。addBeanPostProcessor(new ApplicationListenerDetector(this)); 。。。。}複製程式碼

public Object postProcessAfterInitialization(Object bean, String beanName) { if (bean instanceof ApplicationListener) { // potentially not detected as a listener by getBeanNamesForType retrieval Boolean flag = this。singletonNames。get(beanName); if (Boolean。TRUE。equals(flag)) { // singleton bean (top-level or inner): register on the fly //儲存所有實現了ApplicationListener的類 this。applicationContext。addApplicationListener((ApplicationListener<?>) bean); } else if (Boolean。FALSE。equals(flag)) { if (logger。isWarnEnabled() && !this。applicationContext。containsBean(beanName)) { // inner bean with other scope - can‘t reliably process events logger。warn(“Inner bean ’” + beanName + “‘ implements ApplicationListener interface ” + “but is not reachable for event multicasting by its containing ApplicationContext ” + “because it does not have singleton scope。 Only top-level listener beans are allowed ” + “to be of non-singleton scope。”); } this。singletonNames。remove(beanName); } } return bean; }複製程式碼

2。refresh()方法會呼叫initApplicationEventMulticaster()方法,initApplicationEventMulticaster()方法的作用是看Spring容器中是否存在ApplicationEventMulticaster廣播器,如果不存在,那麼就建立一個SimpleApplicationEventMulticaster的廣播器,並且加入到Spring容器中。

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 { //如果不存在廣播器 那麼久建立一個SimpleApplicationEventMulticaster的廣播器 並註冊到Spring容器中 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() + “]”); } } }複製程式碼

3。refresh()方法中呼叫registerListeners()方法,registerListeners()方法作用就是將ApplicationEvent繫結到ApplicationEventMulticaster廣播器中。

protected void registerListeners() { // Register statically specified listeners first。 for (ApplicationListener<?> listener : getApplicationListeners()) { getApplicationEventMulticaster()。addApplicationListener(listener); } // Do not initialize FactoryBeans here: We need to leave all regular beans // uninitialized to let post-processors apply to them! String[] listenerBeanNames = getBeanNamesForType(ApplicationListener。class, true, false); for (String listenerBeanName : listenerBeanNames) { getApplicationEventMulticaster()。addApplicationListenerBean(listenerBeanName); } // Publish early application events now that we finally have a multicaster。。。 Set earlyEventsToProcess = this。earlyApplicationEvents; this。earlyApplicationEvents = null; if (!CollectionUtils。isEmpty(earlyEventsToProcess)) { for (ApplicationEvent earlyEvent : earlyEventsToProcess) { getApplicationEventMulticaster()。multicastEvent(earlyEvent); } } }複製程式碼

4。有了前面的準備,接下來我們可以看applicationEventPublisher。publishEvent()方法了。publishEvent()方法的核心作用就是呼叫ApplicationEventMulticaster廣播器的multicastEvent()方法。

protected void publishEvent(Object event, @Nullable ResolvableType eventType) { Assert。notNull(event, “Event must not be null”); // Decorate event as an ApplicationEvent if necessary ApplicationEvent applicationEvent; if (event instanceof ApplicationEvent) { applicationEvent = (ApplicationEvent) event; } else { applicationEvent = new PayloadApplicationEvent<>(this, event); if (eventType == null) { eventType = ((PayloadApplicationEvent<?>) applicationEvent)。getResolvableType(); } } // Multicast right now if possible - or lazily once the multicaster is initialized if (this。earlyApplicationEvents != null) { this。earlyApplicationEvents。add(applicationEvent); } else { getApplicationEventMulticaster()。multicastEvent(applicationEvent, eventType); } // Publish event via parent context as well。。。 if (this。parent != null) { if (this。parent instanceof AbstractApplicationContext) { ((AbstractApplicationContext) this。parent)。publishEvent(event, eventType); } else { this。parent。publishEvent(event); } } }複製程式碼

multicastEvent()

multicastEvent方法會找到所有監聽對應事件的監聽類,呼叫invokeListener(listener, event)方法(這裡預設執行緒池為空,所以預設是同步執行的)。

public void multicastEvent(final 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); } } }複製程式碼

invokeListener()

invokeListener()方法的作用就是呼叫doInvokeListener()方法,所以我們看doInvokeListener()方法。

protected void invokeListener(ApplicationListener<?> listener, ApplicationEvent event) { ErrorHandler errorHandler = getErrorHandler(); if (errorHandler != null) { try { doInvokeListener(listener, event); } catch (Throwable err) { errorHandler。handleError(err); } } else { doInvokeListener(listener, event); } }複製程式碼

doInvokeListener()

doInvokeListener()方法就是呼叫對應監聽器的onApplicationEvent方法。

private void doInvokeListener(ApplicationListener listener, ApplicationEvent event) { try { listener。onApplicationEvent(event); } catch (ClassCastException ex) { String msg = ex。getMessage(); if (msg == null || matchesClassCastMessage(msg, event。getClass()) || (event instanceof PayloadApplicationEvent && matchesClassCastMessage(msg, ((PayloadApplicationEvent) event)。getPayload()。getClass()))) { // Possibly a lambda-defined listener which we could not resolve the generic event type for // -> let’s suppress the exception。 Log loggerToUse = this。lazyLogger; if (loggerToUse == null) { loggerToUse = LogFactory。getLog(getClass()); this。lazyLogger = loggerToUse; } if (loggerToUse。isTraceEnabled()) { loggerToUse。trace(“Non-matching event type for listener: ” + listener, ex); } } else { throw ex; } } }複製程式碼

至此,Spring Event事件的實現也就完成了。

六:最後

本文主要介紹了Spring Event的使用以及它的實現原理,看完這篇文章相信你對Spring Event已經有了一定的瞭解,不妨在我們的業務開發中嘗試使用Spring Event來降低程式碼的耦合度吧

原文連結:https://juejin。cn/post/7138224964506746917