Spring Event(第一篇)
一、为什么需要面向事件编程
面向事件编程:所有模块或者对象都是通过事件来进行交互,区别于传统编程不同模块直接相互调用,模块间不需要进行强依赖。
传统编程模式如下:

面向事件编程模式如下:

面向事件编程的优势:
- 系统模块之间耦合度变低,各模块的变更不会产生相互影响。
 - 事件比方法更贴近业务,更加形而上。
 
劣势:
- 系统的复杂性会有所增强。
 - 新手理解成本会有所增加。
 
二、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技术规范 | 事件接口 | 监听器接口 | 
|---|---|---|
| JavaBeans | java.beans.PropertyChangeEvent | java.beans.PropertyChangeListener | 
| Java AWT | java.awt.event.MouseEvent | java.awt.event.MouseListener | 
| Java Swing | java.swing.event.MenuEvent | java.swing.event.MenuListener | 
| Java Preference | java.util.prefs.PreferenceChangeEvent | java.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作为事件源
 - 实现类:
- org.springframework.context.event.ContextClosedEvent
 - org.springframework.context.event.ContextRefreshedEvent
 - org.springframework.context.event.ContextStartedEvent
 - 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 事件广播
 - 事件异常处理
 - 事件和监听器底层实现原理
 
转载自:https://juejin.cn/post/7188425898561896506