Spring容器的事件监听机制(简单明了的介绍)

本文涉及的产品
容器镜像服务 ACR,镜像仓库100个 不限时长
简介: 上一篇我们介绍了SpringFactoriesLoader。这一篇我接着来介绍一下Spring的另一个知识点,就是Spring容器的事件监听机制。

前言

上一篇我们介绍了SpringFactoriesLoader。这一篇我接着来介绍一下Spring的另一个知识点,就是Spring容器的事件监听机制。

事件

说到事件,我们第一反应是什么是事件?其实 事件是发生在应用程序中的动作,比如点击按钮,在文本框中输入内容等操作都被称为事件。而当事件触发时,应用程序做出的一定的响应则表示应用监听了这个事件,而在服务器端,事件的监听机制更多的用于异步通知以及监控和异常处理。Java提供了实现事件监听机制的两个基础类:自定义事件类型扩展自java.util.EventObject,事件的监听器扩展自java.util.EnventListener。下面我们就以一个监控方法的耗时为例。

1. 定义事件

首先自定义事件类型,通常的做法是继承EnventObject类,随着事件的发生,相应的状态通常封装在此类中。在此处我们定义了一个时间戳,用于记录方法的开始执行时间。

/**
 * 定义事件类型,通常的做法是继承 EnventObject,
 * 随着事件的发生,相应的状态通常都封装在此类中。
 * @author xiang.wei
 * @date 2020/5/5 5:13 PM
 */
public class MethodMonitorEvent extends EventObject {
    /**
     * 时间戳,用于记录方法开始执行的时间
     */
    protected long timestamp;
    /**
     * Constructs a prototypical Event.
     *
     * @param source The object on which the Event initially occurred.
     * @throws IllegalArgumentException if source is null.
     */
    public MethodMonitorEvent(Object source) {
        super(source);
    }
}

2. 定义监听器

事件类型定义好之后,接下来,我们还需要定义一个监听器,用于监听事件的发生。我们可以在方法开始执行之前发布一个begin事件,在方法结束之后发布一个end事件。我们定义了事件监听接口,里面定义了处理begin事件的方法onMethodBegin和处理end事件的方法onMethodEnd。我们注意到这两个方法都只有一个参数,就是MethodMonitorEvent 参数。说明这个监听器类只负责监听对应的事件并进行处理。

/**
 * 定义事件监听接口
 * @author xiang.wei
 * @date 2020/5/5 5:24 PM
 */
public interface MethodMonitorEventListener extends EventListener {
    /**
     * 处理方法执行之前发布的事件
     * @param event
     */
    void onMethodBegin(MethodMonitorEvent event);
    /**
     * 处理方法结束时发布的事件
     * @param event
     */
    void onMethodEnd(MethodMonitorEvent event);
}

在这个监听器接口的实现类里我们将会具体实现这两个方法的逻辑。方法也是很简单

public class MethodMonitorEventListenerImpl implements MethodMonitorEventListener {
    @Override
    public void onMethodBegin(MethodMonitorEvent event) {
        //记录方法开始执行时的时间
        event.timestamp = System.currentTimeMillis();
    }
    @Override
    public void onMethodEnd(MethodMonitorEvent event) {
        //计算耗时
        long duration = System.currentTimeMillis() - event.timestamp;
        System.out.println("总耗时:" + duration+" ms");
    }
}

3. 定义发布器

有了事件和监听器。我们还需要一个事件发布者,它本身作为一个事件源,在合适的时机,将相应的时间发布给对应的事件监听器。

public class MethodMonitorEventPublisher {
    private List<MethodMonitorEventListener> eventListeners = new ArrayList<>();
    public void methodMonitor() throws InterruptedException {
        //定义事件
        MethodMonitorEvent eventObject = new MethodMonitorEvent(this);
        publishEvent("begin", eventObject);
        //模拟方法执行:休眠5秒钟
        TimeUnit.SECONDS.sleep(5);
        publishEvent("end", eventObject);
    }
  //发布事件的逻辑
    public void publishEvent(String status, MethodMonitorEvent event) {
        //避免在事件处理期间,监听器被移除,这里为了安全做了一个复制操作
        List<MethodMonitorEventListener> copyListeners = new ArrayList<>(eventListeners);
        for (MethodMonitorEventListener listener : copyListeners) {
            if ("begin".equals(status)) {
                listener.onMethodBegin(event);
            } else {
                listener.onMethodEnd(event);
            }
        }
    }
    public static void main(String[] args) throws InterruptedException {
        MethodMonitorEventPublisher publisher = new MethodMonitorEventPublisher();
        //添加监听器
        publisher.addEventListener(new MethodMonitorEventListenerImpl());
  //发布事件
        publisher.methodMonitor();
    }
    public void addEventListener(MethodMonitorEventListener listener) {
        eventListeners.add(listener);
    }
}

对于事件发布者(事件源)我们需要关注两点:

在合适的时机发布事件,此例中的methodMonitor()方法就是事件发布的源头,其在方法执行之前和结束之后两个时间点发布MethodMonitorEvent事件,每个时间点发布的事件都会传给相应的监听器进⾏处理。

事件监听器的管理:publisher 类中提供了事件监听器的注册和移除方法。这样客户端可以根据实际情况决定是否需要注册新的监听器或者移除某个监听器,如果没有提供remove方法,那么注册的监听器实例将一直被MethodMonitorEventPublisher引⽤,即使已经废弃不⽤了,也依然在发布者的监听器列表中,这会导致隐性的内存泄漏。

Spring容器的事件监听机制

说完了Java提供的事件监听机制的两个基础类,以及如何实现一个自定义事件的demo。下面就请出本篇文章的主角Spring容器的时间监听机制。

Spring的ApplicationContext容器内部中的所有事件类型均继承自org.springframework.context.ApplicationEvent

容器中的所有监听器都实现了org.springframework.context.ApplicationListener接口

并且以bean的形式注册到了容器中,一旦容器内发布ApplicationEvent及其子类型的事件,注册到容器中的ApplicationListener就会对这些事件进行处理。ApplicationEventMulticaster类是事件管理者,管理监听器和发布事件,ApplicationContext通过委托ApplicationEventMulticaster来发布事件

ApplicationEventPublisher 是事件发布者,该接口封装了事件有关的公共方法。

1.事件的继承类图

ApplicationContextEvent 继承自ApplicationEvent,而ApplicationEvent 继承自EventObject,Spring提供了一些默认的实现,比如:

ContextStartedEvent 表示容器在启动时发布的事件类型,即调用start()方法。

ContextRefreshedEvent表示容器在初始化或者刷新的时候发布的事件类型,如调用refresh() 方法,此处的实例化是指所有的bean都已经被加载。后置处理器被激活。所有单例bean都已被实例化。所有的容器对象都已准备好可使用。

ContextStoppedEvent表示容器在即将关闭时发布的事件类型,即调用了stop()方法。

监听器的继承类图

容器内部用ApplicationListener作为事件监听器接口定义,它继承自EventListener。ApplicationContext容器在启动时,会自动识别并加载EventListener类型的bean,一旦容器内有事件发布,将通知这些注册到容器的EventListener。

其中ContextRefreshListener监听器监听的是ContextRefreshedEvent事件,而ContextCloserListener监听器监听的是ContextClosedEvent事件。

private class ContextRefreshListener implements ApplicationListener<ContextRefreshedEvent> {
  @Override
  public void onApplicationEvent(ContextRefreshedEvent event) {
    FrameworkServlet.this.onApplicationEvent(event);
  }
  }

而ApplicationContext接口继承了ApplicationEventPublisher接口,该接口提供了void publishEvent(ApplicationEvent event)方法的定义,不难看出,ApplicationContext容器担当的就是事件发布者的角色。需要说明的是Spring事件默认是同步的,即调用publishEvent方法发布事件后,它会处于阻塞状态,直到onApplicationEvent接受到事件并处理返回之后才继续执行下去,这种单线程同步的好处是可以进行任务管理。

总结

本文首先介绍了Java中事件监听机制的基本概念,并且以一个记录方法耗时的demo说明了如何自定义事件类型。接着就是介绍了Spring容器的事件监听机制。


相关文章
|
2月前
|
XML Java 数据格式
编织Spring魔法:解读核心容器中的Beans机制【beans 一】
编织Spring魔法:解读核心容器中的Beans机制【beans 一】
42 0
|
2月前
|
XML Java 数据格式
Spring IoC容器初始化过程(xml形式)
Spring IoC容器初始化过程(xml形式)
46 0
|
1天前
|
XML Java 数据格式
【spring】01 Spring容器研究
【spring】01 Spring容器研究
6 0
|
1月前
|
Java 容器 Spring
【spring(一)】核心容器总结
【spring(一)】核心容器总结
|
1月前
|
Java 开发者 容器
【Java】深入了解Spring容器的两个关键组件
【Java】深入了解Spring容器的两个关键组件
10 0
|
1月前
|
XML Java 数据格式
Spring 的奇幻起源:从 IoC 容器到 Bean 的魔法世界 (下)
Spring 的奇幻起源:从 IoC 容器到 Bean 的魔法世界
|
1月前
|
XML Java 数据格式
Spring 的奇幻起源:从 IoC 容器到 Bean 的魔法世界 (上)
Spring 的奇幻起源:从 IoC 容器到 Bean 的魔法世界 (上)
|
2月前
|
前端开发 Java 数据格式
10个知识点让你读懂spring MVC容器
随着 Spring Boot 逐步全面覆盖到我们的项目之中,我们已经基本忘却当年经典的 Servlet + Spring MVC 的组合,那让人熟悉的 web.xml 配置。而本文,我们想先抛开 Spring Boot 到一旁,回到从前,一起来看看 Servlet 是怎么和 Spring MVC 集成,怎么来初始化 Spring 容器的。
20 1
|
2月前
|
XML Java 数据格式
spring6IoC容器
spring6IoC容器
60 0
|
3月前
|
XML Java 数据格式
Spring5源码(26)-ApplicationContext容器refresh过程简析
Spring5源码(26)-ApplicationContext容器refresh过程简析
38 0