Spring Event(第一篇)

2,162 阅读7分钟

一、为什么需要面向事件编程

面向事件编程:所有模块或者对象都是通过事件来进行交互,区别于传统编程不同模块直接相互调用,模块间不需要进行强依赖。

传统编程模式如下:
image.png

面向事件编程模式如下:
image.png

面向事件编程的优势:

  1. 系统模块之间耦合度变低,各模块的变更不会产生相互影响。
  2. 事件比方法更贴近业务,更加形而上。

劣势:

  1. 系统的复杂性会有所增强。
  2. 新手理解成本会有所增加。

二、Spring Event来源于Java事件

Java事件源于监听器编程模型。标准化接口如下:

  • 事件对象 - java.util.EventObject
  • 事件监听器 - java.util.EventListener

事件的设计模式:观察者模式扩展:

  • 消息发送者 - java.util.Observable
  • 观察者 - java.util.Observer
import java.util.EventListener;
import java.util.EventObject;
import java.util.Observable;
import java.util.Observer;

public class ObserverDemo {

    public static void main(String[] args) {
        EventObservable observable = new EventObservable();
        // 添加观察者(监听者)
        observable.addObserver(new EventObserver());
        // 发布消息(事件)
        observable.notifyObservers("Hello,World");
    }

    static class EventObservable extends Observable {

        public void setChanged() {
            super.setChanged();
        }

        public void notifyObservers(Object arg) {
            setChanged();
            super.notifyObservers(new EventObject(arg));
            clearChanged();
        }
    }

    static class EventObserver implements Observer, EventListener {

        @Override
        public void update(Observable o, Object event) {
            EventObject eventObject = (EventObject) event;
            System.out.println("收到事件 :" + eventObject);
        }
    }
}

输出结果如下:

收到事件 :java.util.EventObject[source=Hello,World]

面向接口的事件-监听器设计模式

Java技术规范事件接口监听器接口
JavaBeansjava.beans.PropertyChangeEventjava.beans.PropertyChangeListener
Java AWTjava.awt.event.MouseEventjava.awt.event.MouseListener
Java Swingjava.swing.event.MenuEventjava.swing.event.MenuListener
Java Preferencejava.util.prefs.PreferenceChangeEventjava.util.prefs.PreferenceChangeListener

面向注解的事件-监听器设计模式

  • Servlet 3.0+ : @javax.servlet.annotation.WebListener (监听器)
  • JPA 1.0+ : @javax.persistence.PostPersist (事件)
  • Java Common : @PostConstruct (事件)

三、Spring标准事件-ApplicationEvent

ApplicationEvent基于Java标准事件(java.util.EventObject)进行扩展,增加了事件发生时间戳。

package org.springframework.context;

import java.util.EventObject;

public abstract class ApplicationEvent extends EventObject {
    private static final long serialVersionUID = 7099057708183571937L;
    private final long timestamp = System.currentTimeMillis();

    public ApplicationEvent(Object source) {
        super(source);
    }

    public final long getTimestamp() {
        return this.timestamp;
    }
}

Spring应用上下文基于Spring标准事件(ApplicationEvent)扩展 - ApplicationContextEvent

import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationEvent;

public abstract class ApplicationContextEvent extends ApplicationEvent {
    public ApplicationContextEvent(ApplicationContext source) {
        super(source);
    }

    public final ApplicationContext getApplicationContext() {
        return (ApplicationContext)this.getSource();
    }
}
  • Spring应用上下文ApplicationContext作为事件源
  • 实现类:
    1. org.springframework.context.event.ContextClosedEvent
    2. org.springframework.context.event.ContextRefreshedEvent
    3. org.springframework.context.event.ContextStartedEvent
    4. org.springframework.context.event.ContextStoppedEvent
import org.springframework.context.ApplicationEvent;

public class MySpringEvent extends ApplicationEvent {
    
    public MySpringEvent(String message) {
        super(message);
    }

    @Override
    public String getSource() {
        return (String) super.getSource();
    }

    public String getMessage() {
        return getSource();
    }
}
public class MySpringEvent2 extends MySpringEvent {
    
    public MySpringEvent2(String message) {
        super(message);
    }

    @Override
    public String getSource() {
        return super.getSource();
    }

    @Override
    public String getMessage() {
        return getSource();
    }
}
import org.springframework.context.ApplicationListener;

public class MySpringEventListener implements ApplicationListener<MySpringEvent> {

    @Override
    public void onApplicationEvent(MySpringEvent event) {
        System.out.printf("[线程 : %s] 监听到事件 : %s\n", Thread.currentThread().getName(), event);
    }
}
import org.springframework.context.support.GenericApplicationContext;

public class CustomizedSpringEventDemo {

    public static void main(String[] args) {
        GenericApplicationContext context = new GenericApplicationContext();

        // 1.添加自定义 Spring 事件监听器
        context.addApplicationListener(new MySpringEventListener());

        context.addApplicationListener(event -> System.out.println("Event : " + event));

        // 2.启动 Spring 应用上下文
        context.refresh();

        // 3. 发布自定义 Spring 事件
        context.publishEvent(new MySpringEvent("Hello,World"));
        context.publishEvent(new MySpringEvent2("2020"));

        // 4. 关闭 Spring 应用上下文
        context.close();
    }
}

输出结果如下:

Event : org.springframework.context.event.ContextRefreshedEvent[source=org.springframework.context.support.GenericApplicationContext@254989ff, started on Sat Jan 14 15:00:24 CST 2023]
[线程 : main] 监听到事件 : org.geekbang.thinking.in.spring.event.MySpringEvent[source=Hello,World]
Event : org.geekbang.thinking.in.spring.event.MySpringEvent[source=Hello,World]
[线程 : main] 监听到事件 : org.geekbang.thinking.in.spring.event.MySpringEvent2[source=2020]
Event : org.geekbang.thinking.in.spring.event.MySpringEvent2[source=2020]
Event : org.springframework.context.event.ContextClosedEvent[source=org.springframework.context.support.GenericApplicationContext@254989ff, started on Sat Jan 14 15:00:24 CST 2023]

四、Spring ApplicationListener

4.1、基于接口的Spring事件监听器

扩展Java标准事件监听器 java.util.EventListener

  • 扩展接口:org.springframework.context.ApplicationListener
  • 设计特点:单一类型事件处理
  • 处理方法:onApplicationEvent(ApplicationEvent)
  • 事件类型:org.springframework.context.ApplicationEvent
package org.springframework.context;

import java.util.EventListener;

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

4.2、基于注解的Spring事件监听器

@org.springframework.context.event.EventListener 特点如下:

  • 支持多ApplicationEvent类型,无需接口约束
  • 在方法上支持注解
  • 支持异步执行
  • 支持泛型类型事件
  • 支持顺序控制 @Order进行控制
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.event.EventListener;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.CustomizableThreadFactory;

import java.util.concurrent.Executor;

import static java.util.concurrent.Executors.newSingleThreadExecutor;

@EnableAsync // 激活 Spring 异步特性
public class AnnotatedAsyncEventHandlerDemo {

    public static void main(String[] args) {

        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();

        // 1. 注册当前类作为 Configuration Class
        context.register(AnnotatedAsyncEventHandlerDemo.class);

        // 2.启动 Spring 应用上下文
        context.refresh(); 

        // 3. 发布自定义 Spring 事件
        context.publishEvent(new MySpringEvent("Hello,World"));

        // 4. 关闭 Spring 应用上下文(ContextClosedEvent)
        context.close();

    }

    @Async // 同步 -> 异步
    @EventListener
    public void onEvent(MySpringEvent event) {
        System.out.printf("[线程 : %s] onEvent方法监听到事件 : %s\n", Thread.currentThread().getName(), event);
    }

    @Bean
    public Executor taskExecutor() {
        return newSingleThreadExecutor(new CustomizableThreadFactory("my-spring-event-thread-pool-a"));
    }
}

输出结果如下:

[线程 : my-spring-event-thread-pool-a1] onEvent方法监听到事件 : org.geekbang.thinking.in.spring.event.MySpringEvent[source=Hello,World]

4.3、注册Spring ApplicationListener

主要分为如下两种注册方式:

  • ApplicationListener 作为Spring Bean 注册
  • 通过ConfigurableApplicationContext API注册
import org.springframework.context.ApplicationListener;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.event.ContextRefreshedEvent;

public class ApplicationListenerDemo {

    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
        // 将引导类 ApplicationListenerDemo 作为 Configuration Class
        context.register(ApplicationListenerDemo.class);
        // 方法一:基于 Spring 接口:向 Spring 应用上下文注册事件
        // a 方法:基于 ConfigurableApplicationContext API 实现
        context.addApplicationListener(event -> println("ApplicationListener - 接收到 Spring 事件:" + event));
        // b 方法:基于 ApplicationListener 注册为 Spring Bean
        // 通过 Configuration Class 来注册
        context.register(MyApplicationListener.class);
        // 启动 Spring 应用上下文
        context.refresh(); // ContextRefreshedEvent
        // 关闭 Spring 应用上下文
        context.close(); // ContextClosedEvent
    }
    
    static class MyApplicationListener implements ApplicationListener<ContextRefreshedEvent> {
        @Override
        public void onApplicationEvent(ContextRefreshedEvent event) {
            println("MyApplicationListener - 接收到 Spring 事件:" + event);
        }
    }

    private static void println(Object printable) {
        System.out.printf("[线程:%s] : %s\n", Thread.currentThread().getName(), printable);
    }
}

结果输出为:

[线程:main] : ApplicationListener - 接收到 Spring 事件:org.springframework.context.event.ContextRefreshedEvent[source=org.springframework.context.annotation.AnnotationConfigApplicationContext@37f8bb67, started on Sat Jan 14 15:21:54 CST 2023]
[线程:main] : MyApplicationListener - 接收到 Spring 事件:org.springframework.context.event.ContextRefreshedEvent[source=org.springframework.context.annotation.AnnotationConfigApplicationContext@37f8bb67, started on Sat Jan 14 15:21:54 CST 2023]
[线程:main] : ApplicationListener - 接收到 Spring 事件:org.springframework.context.event.ContextClosedEvent[source=org.springframework.context.annotation.AnnotationConfigApplicationContext@37f8bb67, started on Sat Jan 14 15:21:54 CST 2023]

五、Spring Event控制

5.1、Spring Event发布器

  • 通过ApplicationEventPublisher 发布Spring事件,通过依赖注入获取ApplicationEventPublisher。
  • 通过ApplicationEventMulticaster 发布Spring事件,通过依赖注入和依赖查找获取ApplicationEventMulticaster。
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class ApplicationListenerDemo implements ApplicationEventPublisherAware {

    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
        // 将引导类 ApplicationListenerDemo 作为 Configuration Class
        context.register(ApplicationListenerDemo.class);
        // 方法一:基于 Spring 接口:向 Spring 应用上下文注册事件
        context.addApplicationListener(event -> println("ApplicationListener - 接收到 Spring 事件:" + event));
        // 启动 Spring 应用上下文
        context.refresh(); // ContextRefreshedEvent
        // 关闭 Spring 应用上下文
        context.close(); // ContextClosedEvent
    }
    /**
     * 通过依赖注入获取ApplicationEventPublisher, 发布Spring事件
     * @param applicationEventPublisher applicationEventPublisher
     */
    @Override
    public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
        applicationEventPublisher.publishEvent(new ApplicationEvent("Hello,World") {
            private static final long serialVersionUID = -5821621139228213756L;
        });
        // 发送 PayloadApplicationEvent
        applicationEventPublisher.publishEvent("Hello,World");
    }

    private static void println(Object printable) {
        System.out.printf("[线程:%s] : %s\n", Thread.currentThread().getName(), printable);
    }
}

输出结果如下:

[线程:main] : ApplicationListener - 接收到 Spring 事件:org.geekbang.thinking.in.spring.event.ApplicationListenerDemo$1[source=Hello,World]
[线程:main] : ApplicationListener - 接收到 Spring 事件:org.springframework.context.PayloadApplicationEvent[source=org.springframework.context.annotation.AnnotationConfigApplicationContext@439f5b3d, started on Sat Jan 14 15:39:41 CST 2023]
[线程:main] : ApplicationListener - 接收到 Spring 事件:org.springframework.context.event.ContextRefreshedEvent[source=org.springframework.context.annotation.AnnotationConfigApplicationContext@439f5b3d, started on Sat Jan 14 15:39:41 CST 2023]
[线程:main] : ApplicationListener - 接收到 Spring 事件:org.springframework.context.event.ContextClosedEvent[source=org.springframework.context.annotation.AnnotationConfigApplicationContext@439f5b3d, started on Sat Jan 14 15:39:41 CST 2023]

5.2、Spring Event传播

当Spring 应用出现多层次Spring 应用上下文(ApplicationContext)时,如Spring WebMVC、Spring Boot、Spring Cloud 场景下,从子ApplicationContext 发起Spring 事件可能会传递到其parent ApplicationContext 直到root的过程。

那如何避免Spring 层次性上下文事件的传播呢?

  • 定位Spring 事件源(ApplicationContext), 需要对其进行过滤处理。
import org.springframework.context.ApplicationListener;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.event.ApplicationContextEvent;
import java.util.LinkedHashSet;
import java.util.Set;

public class HierarchicalSpringEventPropagateDemo {

    public static void main(String[] args) {
        // 1. 创建 parent Spring 应用上下文
        AnnotationConfigApplicationContext parentContext = new AnnotationConfigApplicationContext();
        parentContext.setId("parent-context");
        // 注册 MyListener 到 parent Spring 应用上下文
        parentContext.register(MyListener.class);
        // 2. 创建 current Spring 应用上下文
        AnnotationConfigApplicationContext currentContext = new AnnotationConfigApplicationContext();
        currentContext.setId("current-context");
        // 3. current -> parent
        currentContext.setParent(parentContext);
        // 注册 MyListener 到 current Spring 应用上下文
        currentContext.register(MyListener.class);
        // 4.启动 parent Spring 应用上下文
        parentContext.refresh();
        // 5.启动 current Spring 应用上下文
        currentContext.refresh();
        // 关闭所有 Spring 应用上下文
        currentContext.close();
        parentContext.close();
    }

    static class MyListener implements ApplicationListener<ApplicationContextEvent> {
        /*
         * 定位Spring事件源,防止其进行层次性传播,对其进行过滤处理
         */
        private static final Set<ApplicationContextEvent> PROCESSED_EVENTS = new LinkedHashSet<>();
        @Override
        public void onApplicationEvent(ApplicationContextEvent event) {
            if (PROCESSED_EVENTS.add(event)) {
                System.out.printf("监听到 Spring 应用上下文[ ID : %s ] 事件 :%s\n", event.getApplicationContext().getId(),
                        event.getClass().getSimpleName());
            }
        }
    }
}

输出结果如下:

监听到 Spring 应用上下文[ ID : parent-context ] 事件 :ContextRefreshedEvent
监听到 Spring 应用上下文[ ID : current-context ] 事件 :ContextRefreshedEvent
监听到 Spring 应用上下文[ ID : current-context ] 事件 :ContextClosedEvent
监听到 Spring 应用上下文[ ID : parent-context ] 事件 :ContextClosedEvent

5.3、Spring 内建 Event

Spring 内建派生事件主要有四种:

  • ContextRefreshedEvent: Spring 应用上下文就绪事件
  • ContextStartedEvent: Spring 应用上下文启动事件
  • ContextStoppedEvent: Spring 应用上下文停止事件
  • ContextClosedEvent: Spring 应用上下文关闭事件
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class ApplicationListenerDemo {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
        // 将引导类 ApplicationListenerDemo 作为 Configuration Class
        context.register(ApplicationListenerDemo.class);
        // 方法一:基于 Spring 接口:向 Spring 应用上下文注册事件
        context.addApplicationListener(event -> println("ApplicationListener - 接收到 Spring 事件:" + event));
        // 启动 Spring 应用上下文
        context.refresh(); // ContextRefreshedEvent
        // 启动 Spring 上下文
        context.start();  // ContextStartedEvent
        // 停止 Spring 上下文
        context.stop();  // ContextStoppedEvent
        // 关闭 Spring 应用上下文
        context.close(); // ContextClosedEvent
    }
    private static void println(Object printable) {
        System.out.printf("[线程:%s] : %s\n", Thread.currentThread().getName(), printable);
    }
}

结果输出如下:

[线程:main] : ApplicationListener - 接收到 Spring 事件:org.springframework.context.event.ContextRefreshedEvent[source=org.springframework.context.annotation.AnnotationConfigApplicationContext@37f8bb67, started on Sat Jan 14 16:16:00 CST 2023]
[线程:main] : ApplicationListener - 接收到 Spring 事件:org.springframework.context.event.ContextStartedEvent[source=org.springframework.context.annotation.AnnotationConfigApplicationContext@37f8bb67, started on Sat Jan 14 16:16:00 CST 2023]
[线程:main] : ApplicationListener - 接收到 Spring 事件:org.springframework.context.event.ContextStoppedEvent[source=org.springframework.context.annotation.AnnotationConfigApplicationContext@37f8bb67, started on Sat Jan 14 16:16:00 CST 2023]
[线程:main] : ApplicationListener - 接收到 Spring 事件:org.springframework.context.event.ContextClosedEvent[source=org.springframework.context.annotation.AnnotationConfigApplicationContext@37f8bb67, started on Sat Jan 14 16:16:00 CST 2023]

六、总结

Spring Event作为Spring一个基础能力,为Spring后续能力的扩展提供了极大的帮助,也为Spring变成业界通用框架奠定了坚固的基石。

本篇主要介绍了

  • 事件编程的缘由
  • Java事件的设计
  • Spring Event扩展Java事件
  • 基于接口和注解的Spring 事件监听器
  • 注册Spring ApplicationListener的方式
  • Spring 两种事件发布器
  • Spring 层次性上下文事件传播以及如何解决
  • Spring 四种内建事件

后续会进一步介绍Spring Event:

  • Payload Event
  • 自定义Spring 事件
  • ApplicationEventPublisher、ApplicationEventMulticaster的相关处理
  • 同步和异步Spring 事件广播
  • 事件异常处理
  • 事件和监听器底层实现原理