深入浅出Spring/SpringBoot 事件监听机制

安全 应用安全
在Java中,事件对象都是继承java.util.EventObject对象,事件监听器都是java.util.EventListener实例,EventObject对象不提供默认构造器,需要外部传递source参数,即用于记录并跟踪事件的来源。

[[327899]]

说明

事件监听机制可以理解为是一种观察者模式,有数据发布者(事件源)和数据接受者(监听器);

在Java中,事件对象都是继承java.util.EventObject对象,事件监听器都是java.util.EventListener实例;

EventObject对象不提供默认构造器,需要外部传递source参数,即用于记录并跟踪事件的来源;

Spring事件

Spring事件对象为ApplicationEvent,继承EventObject,源码如下:

public abstract class ApplicationEvent extends EventObject { 
 
    /** 
     * Create a new ApplicationEvent. 
     * @param source the object on which the event initially occurred (never {@code null}) 
     */ 
    public ApplicationEvent(Object source) { 
        super(source); 
        this.timestamp = System.currentTimeMillis(); 
    } 
 

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.

Spring事件监听器为ApplicationListener,继承EventListener, 源码如下:

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

  • 1.
  • 2.
  • 3.

实现Spring事件监听有两种方式:

  1. 面向接口编程,实现ApplicationListener接口;
  2. 基于注解驱动,@EventListener(Spring自定义的注解);

实例:

面向接口编程,实现ApplicationListener接口:

自定义事件对象:

public class MyApplicationEvent extends ApplicationEvent { 
    public MyApplicationEvent(Object source) { 
        super(source); 
    } 

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.

自定义事件监听器:

public class MyApplicationListener implements ApplicationListener<MyApplicationEvent> { 
    @Override 
    public void onApplicationEvent(MyApplicationEvent event) { 
        System.out.println("收到事件:" + event); 
    } 

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.

启动服务并发布事件:

public class ApplicationEventBootstrap { 
 
    public static void main(String[] args) { 
        AnnotationConfigApplicationContext context = 
                new AnnotationConfigApplicationContext(); 
        // 注册自定义事件监听器 
        context.addApplicationListener(new MyApplicationListener()); 
        // 启动上下文 
        context.refresh(); 
        // 发布事件,事件源为Context 
        context.publishEvent(new MyApplicationEvent(context)); 
        // 结束 
        context.close(); 
    } 

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.

运行结果:

收到事件:com.xx.MyApplicationEvent[source=org.springframework.context.annotation.AnnotationConfigApplicationContext@cb0ed20, started on Sat May 16 16:32:04 CST 2020] 
  • 1.

使用注解 @EventListener实现Spring事件监听:

@Component 
public class MyApplicationListener2 { 
 
    @EventListener(MyApplicationEvent.class) 
    public void onEvent(MyApplicationEvent event) { 
        System.out.println("收到事件:" + event); 
    } 

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.

启动并发布事件:

public class ApplicationEventBootstrap { 
 
    public static void main(String[] args) { 
        AnnotationConfigApplicationContext context = 
                new AnnotationConfigApplicationContext(); 
        // 注册自定义事件监听器 
        context.register(MyApplicationListener2.class); 
        // 启动上下文 
        context.refresh(); 
        // 发布事件,事件源为Context 
        context.publishEvent(new MyApplicationEvent(context)); 
        // 结束 
        context.close(); 
    } 

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.

运行结果:

收到事件:com.xx.MyApplicationEvent[source=org.springframework.context.annotation.AnnotationConfigApplicationContext@cb0ed20, started on Sat May 16 16:32:04 CST 2020] 
  • 1.

通过实例可以看出,上面两种方式都可正常发布和接收事件。

实现原理

通过上面实例可以看出,context 可以发布事件,那底层是怎么发布的,让我们继续看源码:

public abstract class AbstractApplicationContext extends DefaultResourceLoader 
        implements ConfigurableApplicationContext { 
      protected void publishEvent(Object event, @Nullable ResolvableType eventType) { 
        ... 
        getApplicationEventMulticaster().multicastEvent(applicationEvent, eventType); 
        ... 
      } 

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.

通过源码我们可以看出,事件应该是通过

ApplicationEventMulticaster发布的,我们继续看:

public class SimpleApplicationEventMulticaster extends AbstractApplicationEventMulticaster 
  • 1.

Spring 中事件发布都是通过

SimpleApplicationEventMulticaster来实现的

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) { 
        // 异步 
                executor.execute(() -> invokeListener(listener, event)); 
            } 
            else { 
                invokeListener(listener, event); 
            } 
        } 
    } 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.

可以看出,如果设置了Executor则异步发送,否则同步;而且可以看出通过 resolveDefaultEventType(event) 对发布的事件类型进行了校验,这就是为什么我们可以直接使用泛型来指定我们想接收的事件对象, 比如上面的 ApplicationListener。

private void doInvokeListener(ApplicationListener listener, ApplicationEvent event) { 
        try { 
            listener.onApplicationEvent(event); 
         
  • 1.
  • 2.
  • 3.
  • 4.

最后就使用对应的ApplicationListener进行接收和处理就行了,那么ApplicationListener是什么时候注册的呢?

如何添加ApplicationListener?

  1. 直接添加,使用content.addApplicationListener(上面实例中有使用);
  2. 将自定义的ApplicationListener注册为一个Bean,Spring再初始化Bean之后会添加,具体代码在ApplicationListenerDetector#postProcessAfterInitialization,判断一个Bean如果是ApplicationListener,则也是使用context.addApplicationListener添加;
  3. 使用注解@EventListener,在初始化Bean之后,会在EventListenerMethodProcessor中进行处理和添加;

第三种实现的源码如下(

EventListenerMethodProcessor中):

private void processBean(final String beanName, final Class<?> targetType) { 
  .... 
  // 获取public 且有@EventListener的方法  
  AnnotatedElementUtils.findMergedAnnotation(method, EventListener.class)); 
  ...  
  ApplicationListener<?> applicationListener = factory.createApplicationListener(beanName, targetType, methodToUse);                  
  // 添加监听器 
  context.addApplicationListener(applicationListener);  

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.

Spring内建事件

  • ContextRefreshedEvent: Spring应用上下文就绪事件;
  • ContextStartedEvent: Spring应用上下文启动事件;
  • ContextStopedEvent: Spring应用上下文停止事件;
  • ContextClosedEvent: Spring应用上下文关闭事件;

Spring Boot事件

Spring Boot事件是在Spring事件基础上进行的封装

public abstract class SpringApplicationEvent extends ApplicationEvent 
  • 1.

事件对象改为SpringApplicationEvent,事件源为SpringApplication(Spring事件源为Context);

底层发布事件还是使用

SimpleApplicationEventMulticaster 对象,不过有点需要说明的是,Spring Boot 1.4开始,SpringApplication和ApplicationContext使用的都是

SimpleApplicationEventMulticaster实例,但是两者属于不同的对象(1.0 ~ 1.3版本是同一个对象);

事件回顾:

public class EventBootstrap { 
 
    public static void main(String[] args) { 
        new SpringApplicationBuilder(Object.class) 
                .listeners(event -> { 
                    System.out.println("事件对象:" 
                    + event.getClass().getSimpleName() 
                    + " ,事件源:" + event.getSource().getClass().getSimpleName()); 
                }) 
                .web(WebApplicationType.NONE) 
                .run(args) 
                .close(); 
    } 

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.

运行结果:

事件对象:ApplicationContextInitializedEvent ,事件源:SpringApplication 
事件对象:ApplicationPreparedEvent ,事件源:SpringApplication 
事件对象:ContextRefreshedEvent ,事件源:AnnotationConfigApplicationContext 
事件对象:ApplicationStartedEvent ,事件源:SpringApplication 
事件对象:ApplicationReadyEvent ,事件源:SpringApplication 
事件对象:ContextClosedEvent ,事件源:AnnotationConfigApplicationContext 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.

从结果可以看出,事件对象类型和事件源,以及事件发布顺序。

责任编辑:武晓燕 来源: 今日头条
相关推荐

2016-10-14 14:32:58

JavascriptDOMWeb

2016-10-14 13:53:05

JavascriptDOMWeb

2025-03-27 09:38:35

2021-03-16 08:54:35

AQSAbstractQueJava

2011-07-04 10:39:57

Web

2022-01-12 08:54:52

Spring编程架构设计

2023-11-12 00:10:07

Redis高可用

2021-07-20 15:20:02

FlatBuffers阿里云Java

2012-05-21 10:06:26

FrameworkCocoa

2019-01-07 15:29:07

HadoopYarn架构调度器

2017-07-02 18:04:53

块加密算法AES算法

2022-09-26 09:01:15

语言数据JavaScript

2022-09-29 09:19:04

线程池并发线程

2011-01-27 10:11:46

J2EEjavaspring

2018-01-25 18:30:09

Zookeeper分布式数据

2023-12-04 13:22:00

JavaScript异步编程

2021-07-19 11:54:15

MySQL优先队列

2009-11-17 17:31:58

Oracle COMM

2010-07-16 09:11:40

JavaScript内存泄漏

2010-07-26 12:57:12

OPhone游戏开发
点赞
收藏

51CTO技术栈公众号