跳至主要內容

第10章:容器事件和事件监听器

AruNi_Lu框架Spring约 2892 字大约 10 分钟

本文内容

1. 设计

在 Spring 中有一个 Event 事件功能,它可以通过 事件的定义、发布、监听,来完成一些自定义的动作

例如在项目中定义一个用户注册事件,当用户完成注册时,通过事件监听给用户发送优惠卷和短信通知等。使用 事件 功能就能很好的 将基本功能(注册)和对应的策略服务(发送优惠卷、短信通知等)分离开来,降低系统的耦合。当后续再扩展注册服务时(比如添加实名认证、判断用户属性等),就不会影响到依赖于注册成功后执行的动作。

在本章节中需要使用观察者模式来实现上述功能,在对象的状态改变时进行通知其他对象,做出对应的自定义动作。

因此,我们需要定义出 事件类、事件监听类、事件发布类,这些类的功能都需要 结合到 Spring 的 AbstractApplicationContext#refresh() 方法中,用于 处理事件的初始化和注册事件监听器

整体设计如下:

img

  • 在面向用户的应用上下文 AbstractApplicationContext 中添加相关事件内容,包括:初始化事件发布者、注册事件监听器、发布容器刷新完成事件;
  • 使用观察者模式定义事件类、监听类、发布类,还要完成一个广播器的功能,将广播器接收到事件推送时,进行分析处理符合监听事件接收者感兴趣的事件,这个功能由 Class#isAssignableFrom() 进行分析判断(后续代码实现中会讲到)。

2. 实现

2.1 定义和实现事件 Event

首先需要定义出 具备事件功能的抽象类 ApplicationEvent,后续所有事件的类都需要继承这个类。想要具备事件功能,可以直接继承 java.util.EventObject

java.util.EventObject事件状态对象的基类,它封装了事件源对象 source 以及和事件相关的信息。所有 Java 的事件类都需要继承该类。

接着需要一个 定义事件的类 ApplicationContextEvent,继承自 ApplicationEvent,所有的事件(包括关闭、刷新或自己实现的事件)都需要继承这个类,该类提供一个获取引发事件的 source(即 ApplicationContext)的方法,具体实现如下:

public class ApplicationContextEvent extends ApplicationEvent {
    /**
     * Constructs a prototypical Event.
     *
     * @param source the object on which the Event initially occurred
     * @throws IllegalArgumentException if source is null
     */
    public ApplicationContextEvent(Object source) {
        super(source);
    }

    /**
     * 获取 source,即获取引发事件的 ApplicationContext
     */
    public final ApplicationContext getApplicationContext() {
        return (ApplicationContext) getSource();
    }
}

现在,我们在实例化事件类 ApplicationContextEvent 时,就会通过构造函数将 EventObject 类实例化,其中的源对象 source 就是上下文对象 ApplicationContext(包含 beanFactory 资源),当事件发生时,就可以通过 ApplicationContextEvent#getApplicationContext()(实际上是调用 EventObject#getSource())获取到引发事件的 ApplicationContext,然后进行具体的处理

再添加 Spring 框架自己实现的两个事件类 ContextClosedEvent、ContextRefreshedEvent,继承自 ApplicationContextEvent:

  • ContextClosedEvent 用于监听关闭动作;
  • ContextRefreshedEvent 用于监听容器刷新动作。

2.2 事件监听器 Listener

定义一个事件监听器接口 ApplicationListener,继承自 java.util.EventListener,里面提供一个处理 ApplicationEvent 事件的方法 onApplicationEvent(E event)由 ApplicationEvent 事件监听器实现该接口

public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {

    /**
     * 处理 ApplicationEvent 事件,具体处理逻辑要用户自定义
     * @param event 要响应的事件
     */
    void onApplicationEvent(E event);

}

对应上面的事件类,我们需要为其提供对应的监听器,比如 ContextClosedEventListener 和 ContextRefreshedEventListener:

  • ContextClosedEventListener 是容器关闭事件监听器,监听动作在容器关闭时执行;
  • ContextRefreshedEventListener 时容器刷新事件监听器,监听动作在容器刷新时执行。

注意,上面这两个监听器是用户在需要时才进行实现。

2.3 事件广播器 Multicaster

定义一个事件广播器接口 ApplicationEventMulticaster,提供添加监听器和删除监听器的方法,以及一个广播事件的方法 multicastEvent(ApplicationEvent event)(将给定的 ApplicationEvent 事件多播给适当的监听器)。

public interface ApplicationEventMulticaster {

    /**
     * 添加一个监听器
     */
    void addApplicationListener(ApplicationListener<?> listener);

    /**
     * 删除一个监听器
     */
    void removeApplicationListener(ApplicationListener<?> listener);

    /**
     * 将给定的 ApplicationEvent 事件多播给合适的监听器
     */
    void multicastEvent(ApplicationEvent event);

}

再定义一个 AbstractApplicationEventMulticaster 抽象类,实现 ApplicationEventMulticaster 接口,在这个类中可以实现一些基本功能,避免所有直接实现接口的类还需要处理细节。具体的功能实现如下:

public abstract class AbstractApplicationEventMulticaster implements ApplicationEventMulticaster, BeanFactoryAware {

    // 提供一个存储应用监听器的容器
    public final Set<ApplicationListener<ApplicationEvent>> applicationListeners = new LinkedHashSet<>();

    private BeanFactory beanFactory;

    @Override
    public void addApplicationListener(ApplicationListener<?> listener) {
        applicationListeners.add((ApplicationListener<ApplicationEvent>) listener);
    }

    @Override
    public void removeApplicationListener(ApplicationListener<?> listener) {
        applicationListeners.remove(listener);
    }

    @Override
    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
        this.beanFactory = beanFactory;
    }

    /**
     * 摘取符合广播事件的所有监听器,具体过滤动作在 supportsEvent 方法中
     * @param event 事件
     * @return 符合广播事件的所有监听器
     */
    protected Collection<ApplicationListener> getApplicationListeners(ApplicationEvent event) {
        LinkedList<ApplicationListener> allListeners = new LinkedList<>();
        for (ApplicationListener<ApplicationEvent> listener : applicationListeners) {
            if (supportsEvent(listener, event)) allListeners.add(listener);
        }
        return allListeners;
    }

    /**
     * 判断监听器是否对该事件感兴趣
     * @param applicationListener 监听器
     * @param event 事件
     * @return 监听器是否对该事件感兴趣,是返回 true
     */
    protected boolean supportsEvent(ApplicationListener<ApplicationEvent> applicationListener, ApplicationEvent event) {
        Class<? extends ApplicationListener> listenerClass = applicationListener.getClass();

        // // 按照 CglibSubclassingInstantiationStrategy、SimpleInstantiationStrategy 不同的实例化策略,需要判断后获取目标 class
        Class<?> targetClass = ClassUtil.isCglibProxyClass(listenerClass) ? listenerClass.getSuperclass() : listenerClass;
        Type genericInterface = targetClass.getGenericInterfaces()[0];

        Type actualTypeArgument = ((ParameterizedType) genericInterface).getActualTypeArguments()[0];
        String className = actualTypeArgument.getTypeName();
        Class<?> eventClassName;
        try {
            eventClassName = Class.forName(className);
        } catch (ClassNotFoundException e) {
            throw new BeansException("wrong event class name: " + className);
        }
        // event.getClass() 类是否继承/实现自 eventClassName
        return eventClassName.isAssignableFrom(event.getClass());
    }

}

最后定义一个 AbstractApplicationEventMulticaster 的简单实现类 SimpleApplicationEventMulticaster,主要是为父类提供 beanFactory,以及实现 multicastEvent(ApplicationEvent event) 方法:

public class SimpleApplicationEventMulticaster extends AbstractApplicationEventMulticaster {

    // 通过依赖注入,将 beanFactory 注入进来,然后通过 setBeanFactory() 提供给父类
    public SimpleApplicationEventMulticaster(BeanFactory beanFactory) {
        setBeanFactory(beanFactory);
    }

    @SuppressWarnings("uncheckd")
    @Override
    public void multicastEvent(final ApplicationEvent event) {
        // 获取该事件的监听者,执行 onApplicationEvent() 处理事件
        for (final ApplicationListener listener : getApplicationListeners(event)) {
            listener.onApplicationEvent(event);
        }
    }
}

2.4 定义和实现事件发布者 Publisher

定义一个整个事件的发布接口 ApplicationEventPublisher,提供一个发布事件的方法,所有的事件都需要从这个接口发布出去。

接着在 AbstractApplicationContext#refresh() 中,添加用于处理事件的操作 —— 初始化事件发布者注册事件监听器发布容器刷新完成事件,相关实现如下:

public abstract class AbstractApplicationContext
        extends DefaultResourceLoader implements ConfigurableApplicationContext {

    public static final String APPLICATION_EVENT_MULTICASTER_BEAN_NAME = "applicationEventMulticaster";

    private ApplicationEventMulticaster applicationEventMulticaster;

    @Override
    public void refresh() throws BeansException {
        // 1. 创建 BeanFactory,并加载 BeanDefinition
        refreshBeanFactory();

        // 2. 获取 BeanFactory
        ConfigurableListableBeanFactory beanFactory = getBeanFactory();

        // 3. 添加 ApplicationContextAwareProcessor,让继承自 ApplicationContextAware 的 Bean 对象都能感知所属的 ApplicationContext
        beanFactory.addBeanPostProcessor(new ApplicationContextAwareProcessor(this));

        // 4. 在 Bean 实例化之前,执行 BeanFactoryPostProcessor (Invoke factory processors registered as beans in the context.)
        invokeBeanFactoryPostProcessors(beanFactory);

        // 5. BeanPostProcessor 需要提前于其他 Bean 对象实例化之前执行注册操作
        registerBeanPostProcessors(beanFactory);

        // 6. 初始化事件发布者
        initApplicationEventMulticaster();

        // 7. 注册事件监听器
        registerListeners();

        // 8. 提前实例化单例 Bean 对象
        beanFactory.preInstantiateSingletons();

        // 9. 完成刷新后,发布刷新完成事件
        finishRefresh();

    }

    /**
     * 初始化事件发布者,即实例化 ApplicationEventMulticaster,使 ApplicationContext 具备
     * SimpleApplicationEventMulticaster 的功能。然后将实例化的 Bean 对象 applicationEventMulticaster
     * 注册进单例容器,供其他地方使用。
     */
    private void initApplicationEventMulticaster() {
        ConfigurableListableBeanFactory beanFactory = getBeanFactory();
        applicationEventMulticaster = new SimpleApplicationEventMulticaster(beanFactory);
        beanFactory.registerSingleton(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, applicationEventMulticaster);
    }

    /**
     * 注册事件监听器:
     * 1. 先获取继承/实现了 ApplicationListener 接口的所有监听器
     * 2. 将这些监听器加入到监听器容器中
     */
    private void registerListeners() {
        Collection<ApplicationListener> applicationListeners = getBeansOfType(ApplicationListener.class).values();
        for (ApplicationListener listener : applicationListeners) {
            applicationEventMulticaster.addApplicationListener(listener);
        }
    }

    /**
     * 完成刷新后,发布刷新完成事件
     */
    private void finishRefresh() {
        publishEvent(new ContextRefreshedEvent(this));
    }

    @Override
    public void publishEvent(ApplicationEvent event) {
        applicationEventMulticaster.multicastEvent(event);
    }

    @Override
    public void registerShutdownHook() {
        Runtime.getRuntime().addShutdownHook(new Thread(this::close));
    }

    @Override
    public void close() {
        // 发布容器关闭事件
        publishEvent(new ContextClosedEvent(this));

        // 执行销毁单例 bean 的销毁方法
        getBeanFactory().destroySingletons();
    }
    
}
  • 这里我们需要让 ApplicationContext 继承 ApplicationEventPublisher,这样上下文才拥有发布事件的能力。

2.5 目录结构

到此,事件相关的功能就实现完毕了,下面来看看结构目录的更变(绿色—新增、蓝色—修改):

  • 有些小改动就不展示目录结构更变了。

2.6 类结构图

3. 测试

我们创建一个用户自定义的事件和监听器:

  • 创建一个自定义事件,在事件类的构造函数中可以添加自己的想要的入参信息。这个事件类最终会被完整的放到监听里,所以你添加的属性都会被获得到:

    public class CustomEvent extends ApplicationEvent {
    
        private Long id;
        private String message;
    
        /**
         * Constructs a prototypical Event.
         *
         * @param source the object on which the Event initially occurred
         * @throws IllegalArgumentException if source is null
         */
        public CustomEvent(Object source, Long id, String message) {
            super(source);
            this.id = id;
            this.message = message;
        }
    }
    
  • 用于监听 CustomEvent 事件的监听器,这里你可以处理自己想要的操作,比如一些用户注册后发送优惠券和短信通知等:

    public class CustomEventListener implements ApplicationListener<CustomEvent> {
        
        @Override
        public void onApplicationEvent(CustomEvent event) {
            System.out.println("收到:" + event.getSource() + "消息,当前时间:" + new Date());
            System.out.println("消息:" + event.getId() + ":" + event.getMessage());
        }
        
    }
    

另外,还有关于 ContextRefreshedEvent、ContextClosedEvent 对应的监听器:

public class ContextRefreshedEventListener implements ApplicationListener<ContextRefreshedEvent> {
    @Override
    public void onApplicationEvent(ContextRefreshedEvent event) {
        System.out.println("上下文关闭事件:" + this.getClass().getName());
    }
}

public class ContextClosedEventListener implements ApplicationListener<ContextClosedEvent> {
    @Override
    public void onApplicationEvent(ContextClosedEvent event) {
        System.out.println("上下文关闭事件:" + this.getClass().getName());
    }
}

接下来,需要在 spring.xml 中配置三个事件监听器:监听刷新事件、监控自定义事件、监听关闭事件。把它们当成 Bean 注册进 Spring 容器中:

<?xml version="1.0" encoding="UTF-8"?>
<beans>

    <bean class="com.run.test.event.ContextRefreshedEventListener"/>
    <bean class="com.run.test.event.CustomEventListener"/>
    <bean class="com.run.test.event.ContextClosedEventListener"/>

</beans>

测试类如下,我们通过使用 applicationContext 新增加的发布事件接口方法,发布一个自定义事件 CustomEvent,让对应监听器监听,然后执行处理事件

@Test
public void test_event() {
    ClassPathXmlApplicationContext application = new ClassPathXmlApplicationContext("classpath:spring.xml");
    // 通过使用 applicationContext 新增加的发布事件接口方法,发布一个自定义事件 CustomEvent,让对应监听器监听,然后执行处理事件
    application.publishEvent(new CustomEvent(application, 20000001L, "我是自定义的事件"));
    application.registerShutdownHook();
}

输出结果:

上下文刷新事件:com.run.test.event.ContextRefreshedEventListener
收到:com.run.context.support.ClassPathXmlApplicationContext@369f73a2消息,当前时间:Sun Apr 02 10:59:07 CST 2023
消息:20000001:我是自定义的事件
上下文关闭事件:com.run.test.event.ContextClosedEventListener

Process finished with exit code 0

可以看到,我们自己定义的事件和监听,以及监听系统的事件信息,都完美的触发了。

主要流程

  1. 用户自定义一个事件,继承自 ApplicationEvent,通过构造函数将该事件源对象 source 注入进去,以及填充一些自定义的参数;

  2. 自定义事件后,需要定义一个针对该事件的监听器,需要实现 ApplicationListener 接口,用于处理一些自定义的操作。这些操作通过实现 ApplicationListener#onApplicationEvent() 方法,在该方法中自定义的操作;

  3. 定义好事件和对应的监听器后,就可以在指定的地方通过上下文 ApplicationContext 的 publishEvent() 方法发布事件了,发布后的执行流程如下:

4. 流程

上次编辑于: