Spring 框架事件收發功能的使用 (一) 基本使用

Spring 框架事件收發功能的使用 (一) 基本使用

1. 概述

Spring 框架時間收發功能本系列的文章總共分為三部分。

  • 第一部分:為大家介紹如何用 Spring 來進行事件收發,如何讓 Spring 的事件收發變為異步進行的,Spring 又是如何實現異步收發的。
  • 第二部分:為大家介紹如何在事務的各個節點去觸發監聽器的執行,以及基于事務的事件監聽器在異步配置下失效的原因。
  • 第三部分:為大家找到如何讓基于事務的事件監聽器能在異步配置下正確使用和運行。

下列內容翻譯自 Spring framework document 1.15.2
Spring 在 ApplicationContext 中的事件處理,通過 ApplicationEvent class 和 ApplicationListener interface 所提供。如果一個 bean 實現了 ApplicationListener 并且被放發布到了 context 中,每當一個 ApplicationEvent 被發布到 ApplicationContext 中,那這個 bean 便會被通知。實際上,這個流程的實現基于觀察者模式
Spring 中還有內建的 7 種 Event,這里不做贅述,有興趣的同學可以通過上方連接進入 Spring 官方文檔進行查看。

2. 事件收發的實現

2.1 概述

第一小節的內容主要是介紹 Spring 框架中事件收發功能的兩種實現方式,一種是基于繼承類和實現接口的方式,另一種是基于注解的方式。推薦還是使用注解的方式,因為這種方式能將同一業務特征的監聽和處理放入一個服務類中,便于統一的管理和查閱。

2.2 自定義使用

2.2.1 基于繼承的方式

我們首先需要創建一個繼承于 ApplicationEvent 的事件數據類:

@Data
public class BlackListEvent extends ApplicationEvent {

    private final String address;
    private final String content;

    // accessor and other methods...
}

為了能將我們新創建的事件類實例發布出去,我們需要有一個實現了 ApplicationEventPublisherAware 的服務類。同時,我們需要將 ApplicationEventPublisher 注入到服務類。然后,我們可以通過publisher實例的 publish 方法將我們的 event 實例發布出去。

@Service
public class EmailService implements ApplicationEventPublisherAware {

    private List<String> blackList;
    private ApplicationEventPublisher publisher;

    public void setBlackList(List<String> blackList) {
        this.blackList = blackList;
    }

    public void setApplicationEventPublisher(ApplicationEventPublisher publisher) {
        this.publisher = publisher;
    }

    public void sendEmail(String address, String content) {
        if (blackList.contains(address)) {
            publisher.publishEvent(new BlackListEvent(this, address, content));
            return;
        }
        // send email...
    }
}

此時,我們成功的將事件發送出去了。那如果我們對這個事件感興趣的話,如何去接受他呢?接下來我們就來看看怎么能接收到我們剛剛發出的事件。下列的代碼實現了我們對 BlackListEvent 的監聽器,當我們將這個監聽器實例化以后,我們就可以正確的監聽到發送出來的BlackListEvent 事件并處理他了。

@Componet
public class BlackListNotifier implements ApplicationListener<BlackListEvent> {

    private String notificationAddress;

    public void setNotificationAddress(String notificationAddress) {
        this.notificationAddress = notificationAddress;
    }

    public void onApplicationEvent(BlackListEvent event) {
        // notify appropriate parties via notificationAddress...
    }

這是最基本的實現方式,但是我相信大部分朋友都已經習慣了使用 Spring 4.0 引入的注解模式來使用 Spring 了。所以我們接下來就會講,如果用更加 fashion 的方式來實現我們的事件收發。

2.2.2 基于注解的方式

首先,大家要確認一下自己的 Spring 版本是否是在 4.2+ 。如果低于這個版本的話,這段內容對于你來說是沒有任何用處的喲。
基于注解我們能夠更加簡單明了的來實現監聽器。并且我們有三種不同的實現方式,來針對于我們不同的監聽場景。

  • 1.接收到需要監聽的 event ,并且處理他
public class BlackListNotifier {

    private String notificationAddress;

    public void setNotificationAddress(String notificationAddress) {
        this.notificationAddress = notificationAddress;
    }

    @EventListener
    public void processBlackListEvent(BlackListEvent event) {
        // notify appropriate parties via notificationAddress...
    }
}
  • 2.監聽一個或多個事件,但是不需要 event 的數據,只是對應 event 發布以后,需要做一些處理
@EventListener({ContextStartedEvent.class, ContextRefreshedEvent.class})
public void handleContextStart() {
    ...
}
  • 3.使用SpEL表達式來監聽事件(其實我也不太會 SpEL 表達式,后面會抽時間看看文檔,再寫個博客來學習一下)
@EventListener(condition = "#blEvent.content == 'my-event'")
public void processBlackListEvent(BlackListEvent blEvent) {
    // notify appropriate parties via notificationAddress...
}

3. 異步事件收發的實現

3.1 概述

上一節的內容介紹了如何使用 Spring 框架自帶的事件收發功能。通常,我們使用事件收發方式來處理的目的,都是希望能將事件觸發和事件監聽這兩部分的業務進行解耦以及能夠無阻塞的執行監聽后的任務。但是,如果我們不去特別配置的話,那整個事件的收發其實是同步進行的。本節將會分為兩個部分來介紹異步的進行事件的收發,第一部分是介紹我們如何配置能讓 spring 的事件收發功能在異步下進行。第二部分我們將會對 Spring 框架事件處理源碼進行普析,看看 Spring 到底是如何實現事件收發的,以及異步是如何實現的。

3.2 實現異步的事件收發

3.2.1 使用注解實現異步(官方文檔推薦)

我們可以通過在監聽器上添加 @Async 注解的方式,來實現某些監聽器的異步化。但是,使用這種方式的時候一定要記住,在 Spring 的配置類中需要添加開啟異步化的注解 @EnableAsync 。這種方式的好處是,我們可以讓那些需要某些監聽器使用異步的方式進行處理,其他的監聽器仍然用同步的方式進行任務執行。

@EventListener
@Async
public void processBlackListEvent(BlackListEvent event) {
    // BlackListEvent is processed in a separate thread
}

這種方式比較靈活,但是如果本身項目內部的事件監聽器比較多,而且都需要異步的話,想想那滿屏幕的 @Sync,看起來肯定是非常不整潔和優雅的。那么,我們還有什么辦法可以讓項目里面所有的 EventListener 自然而然就是異步的呢?請看下一小節。

3.3 Spring 框架是如何實現異步收發的

為了能讓 EventListener 注釋的監聽器方法都是異步的,我們需要進入 Spring 框架的源碼,看看到底 Spring 是怎么實現這個事件監聽的。
首先,我們進入 @EventListener 這個類,可以看到在類名上面一大段文字說明,其中包含下面這一段解釋:

 * <p>Processing of {@code @EventListener} annotations is performed via
 * the internal {@link EventListenerMethodProcessor} bean which gets
 * registered automatically when using Java config or manually via the
 * {@code <context:annotation-config/>} or {@code <context:component-scan/>}
 * element when using XML config.

大概,含義就是說,我們這個注解的功能是通過內部的 EventListenerMethodProcessor 這個類來實現功能的,這個類會在你使用 Java Config 的模式或者是使用 XML 的方式自動掃描后,會被注冊。那自然而然我們進入這個類,找一找他是如何實現 @EventListener 注解的功能的。
接下來我會在源碼中進行注釋的方式進行分解,這樣更容易將我對源碼的理解使用文本的方式輸出出來。

public class EventListenerMethodProcessor
        implements SmartInitializingSingleton, ApplicationContextAware, BeanFactoryPostProcessor {

    protected final Log logger = LogFactory.getLog(getClass());

    @Nullable
    private ConfigurableApplicationContext applicationContext;

    @Nullable
    private ConfigurableListableBeanFactory beanFactory;

    @Nullable
    private List<EventListenerFactory> eventListenerFactories;

    private final EventExpressionEvaluator evaluator = new EventExpressionEvaluator();

    private final Set<Class<?>> nonAnnotatedClasses = Collections.newSetFromMap(new ConcurrentHashMap<>(64));

    /**
    * 用于注入 ApplicationContext 
    **/
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) {
        Assert.isTrue(applicationContext instanceof ConfigurableApplicationContext,
                "ApplicationContext does not implement ConfigurableApplicationContext");
        this.applicationContext = (ConfigurableApplicationContext) applicationContext;
    }
    /**
    * 實現BeanFactoryPostProcessor接口以后,可以在 spring 初始化 bean 時通過這個方法進行自定義的 bean 初始化。
    * 但是,會導致 bean 過早的初始化,可能會導致一些意想不到的副作用。
    **/
    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
        this.beanFactory = beanFactory;
        // 獲取傳入 class 類型的 bean 列表
        Map<String, EventListenerFactory> beans = beanFactory.getBeansOfType(EventListenerFactory.class, false, false);
        List<EventListenerFactory> factories = new ArrayList<>(beans.values());
        // 將所有的 EventListnerFactory 實例 bean 按照 注解順序進行排序(這是為了讓 @Order 注解生效)
        AnnotationAwareOrderComparator.sort(factories);
        this.eventListenerFactories = factories;
    }

    /**
    * 實現SmartInitializingSingleton接口以后,可以在通常的 singleton bean 初始化完成后被回調
    * 
    **/
    @Override
    public void afterSingletonsInstantiated() {
        ConfigurableListableBeanFactory beanFactory = this.beanFactory;
        // 判斷 beanFactory 在之前是否正確注入
        Assert.state(this.beanFactory != null, "No ConfigurableListableBeanFactory set");
        String[] beanNames = beanFactory.getBeanNamesForType(Object.class);//獲取所有 bean 的名字
        for (String beanName : beanNames) {
            if (!ScopedProxyUtils.isScopedTarget(beanName)//判斷這個 bean 是否被代理) {
                Class<?> type = null;
                try {
                    type = AutoProxyUtils.determineTargetClass(beanFactory, beanName);//獲取對應 beanName 的 bean class 類型
                }
                catch (Throwable ex) {
                    // An unresolvable bean type, probably from a lazy bean - let's ignore it.
                    if (logger.isDebugEnabled()) {
                        logger.debug("Could not resolve target class for bean with name '" + beanName + "'", ex);
                    }
                }
                if (type != null) {
                    if (ScopedObject.class.isAssignableFrom(type)) {//判斷此 bean 的類型是否是通過代理的
                        try {
                            Class<?> targetClass = AutoProxyUtils.determineTargetClass(
                                    beanFactory, ScopedProxyUtils.getTargetBeanName(beanName)//獲取被代理后的 bean 的 beanName
                                    );//獲取被代理的 bean 的類型
                            if (targetClass != null) {
                                type = targetClass;
                            }
                        }
                        catch (Throwable ex) {
                            // An invalid scoped proxy arrangement - let's ignore it.
                            if (logger.isDebugEnabled()) {
                                logger.debug("Could not resolve target bean for scoped proxy '" + beanName + "'", ex);
                            }
                        }
                    }
                    try {
                        processBean(beanName, type);//注冊所有@EventListner @TransactionalEventListener
                    }
                    catch (Throwable ex) {
                        throw new BeanInitializationException("Failed to process @EventListener " +
                                "annotation on bean with name '" + beanName + "'", ex);
                    }
                }
            }
        }
    }

    private void processBean(final String beanName, final Class<?> targetType) {
        if (!this.nonAnnotatedClasses.contains(targetType)//排除掉 bean 類型中沒有@EventListener 的注解
                 && !isSpringContainerClass(targetType)//不是 Spring 框架內部的類
                 ) {
            Map<Method, EventListener> annotatedMethods = null;
            try {
                //獲取對應bean 類型中所有被 @EventListener 注解的方法
                annotatedMethods = MethodIntrospector.selectMethods(targetType,
                        (MethodIntrospector.MetadataLookup<EventListener>) method ->
                                AnnotatedElementUtils.findMergedAnnotation(method, EventListener.class));
            }
            catch (Throwable ex) {
                // An unresolvable type in a method signature, probably from a lazy bean - let's ignore it.
                if (logger.isDebugEnabled()) {
                    logger.debug("Could not resolve methods for bean with name '" + beanName + "'", ex);
                }
            }
            // 如果 bean 類型中沒有 @EventListener 注解,則把他記錄下來,避免下次再去處理,可以優化性能
            if (CollectionUtils.isEmpty(annotatedMethods)) {
                this.nonAnnotatedClasses.add(targetType);
                if (logger.isTraceEnabled()) {
                    logger.trace("No @EventListener annotations found on bean class: " + targetType.getName());
                }
            }
            else { 
                ConfigurableApplicationContext context = this.applicationContext;
                Assert.state(context != null, "No ApplicationContext set");
                List<EventListenerFactory> factories = this.eventListenerFactories;
                Assert.state(factories != null, "EventListenerFactory List not initialized");

                for (Method method : annotatedMethods.keySet()) {
                    for (EventListenerFactory factory : factories) {
                        if (factory.supportsMethod(method)) {
                            Method methodToUse = AopUtils.selectInvocableMethod(method, context.getType(beanName));
                            ApplicationListener<?> applicationListener =
                                    factory.createApplicationListener(beanName, targetType, methodToUse);
                            if (applicationListener instanceof ApplicationListenerMethodAdapter) {
                                ((ApplicationListenerMethodAdapter) applicationListener).init(context, this.evaluator);
                            }
                            context.addApplicationListener(applicationListener);
                            break;
                        }
                    }
                }
                if (logger.isDebugEnabled()) {
                    logger.debug(annotatedMethods.size() + " @EventListener methods processed on bean '" +
                            beanName + "': " + annotatedMethods);
                }
            }
        }
    }

    /**
     * Determine whether the given class is an {@code org.springframework}
     * bean class that is not annotated as a user or test {@link Component}...
     * which indicates that there is no {@link EventListener} to be found there.
     * @since 5.1
     */
    private static boolean isSpringContainerClass(Class<?> clazz) {
        return (clazz.getName().startsWith("org.springframework.") &&
                !AnnotatedElementUtils.isAnnotated(ClassUtils.getUserClass(clazz), Component.class));
    }

}

上面,一大堆的代碼就是 Spring 注冊監聽器的邏輯,那我們接下來看看 publisher 是怎么把 event 發送給他們的。


public class SimpleApplicationEventMulticaster extends AbstractApplicationEventMulticaster {

    @Nullable
    private Executor taskExecutor;

    @Nullable
    private ErrorHandler errorHandler;


    /**
     * Create a new SimpleApplicationEventMulticaster.
     */
    public SimpleApplicationEventMulticaster() {
    }

    /**
     * Create a new SimpleApplicationEventMulticaster for the given BeanFactory.
     */
    public SimpleApplicationEventMulticaster(BeanFactory beanFactory) {
        setBeanFactory(beanFactory);
    }


    /**
     * 這里可以將監聽器異步化的線程池綁定到這個 multicaster 中,通過@Nullable注解可以看出
     * 如果,我們沒有現式的去綁定線程池,那么這個 multicaster 應該是使用同步的方式進行廣播。
    **/
    public void setTaskExecutor(@Nullable Executor taskExecutor) {
        this.taskExecutor = taskExecutor;
    }

    /**
     * Return the current task executor for this multicaster.
     */
    @Nullable
    protected Executor getTaskExecutor() {
        return this.taskExecutor;
    }

    /**
     * 這里可以看到,如果我們在事件發送失敗以后,需要一些處理,那我們也可以自定義一個
     * ErrorHandler 給綁定到這個 廣播器 上
     */
    public void setErrorHandler(@Nullable ErrorHandler errorHandler) {
        this.errorHandler = errorHandler;
    }

    @Nullable
    protected ErrorHandler getErrorHandler() {
        return this.errorHandler;
    }

    
    @Override
    public void multicastEvent(ApplicationEvent event) {
        multicastEvent(event, resolveDefaultEventType(event));
    }

    /**
    * AbstractApplicationEventMulticaster  
    **/
    @Override
    public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
        ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
        for (final ApplicationListener<?> listener : getApplicationListeners(event, type)) {//將所有對應的
            Executor executor = getTaskExecutor();
            if (executor != null) {//這里便是如何實現異步的地方,如果我們給他綁定了線程池,那么他會使用 lambda 的方式調用 listener。
                executor.execute(() -> invokeListener(listener, event));
            }
            else {
                invokeListener(listener, event);
            }
        }
    }

}

所以,從上面的源碼可以得出。如果我們給這個 multicaster 配置上他需要的線程池,那么他便會以異步的方式來調用 listener。

4. 小結

我們在這篇文章中,告訴了大家如何使用類實現的方式和注解的方式來使用 Spring 框架自帶的事件收發功能。如何將 Spring 的事件收發變為異步的。Spring 又是如何實現異步收發事件的。接下來的一篇文章中,介紹如何將事件的監聽器的觸發與事件發布所處事務進行聯動,以及在異步場景下的陷阱。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容