一、为什么需求面向事情编程

面向事情编程:一切模块或者目标都是经过事情来进行交互,差异于传统编程不同模块直接彼此调用,模块间不需求进行强依靠。

传统编程形式如下:

Spring Event(第一篇)

面向事情编程形式如下:

Spring Event(第一篇)

面向事情编程的优势:

  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技术规范 事情接口 监听器接口
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作为事情源
  • 完成类:
    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 事情广播
  • 事情反常处理
  • 事情和监听器底层完成原理