Small Spring IOC篇:容器事件及其监听器

一、前言

本小节是 IOC 篇最后一个扩展点,本次将在 ApplicationContext 维度去扩展,主要作用是去注册和使用事件和监听器功能,在容器内实现一定程度的解耦。

这一节的东西也是日常中使用较少的,但是我这里说的是直接使用,Spring 内部实际上用的很多,但是对于框架使用者而言是无感的,更何况在 SpringBoot 中全面被注解代替了,即使开发者刻意使用,也是使用注解,因此对于这一节的原理,我们确实平时很少接触。

正因如此,我这里要先给测试,先看看站在框架使用者的角度下,容器事件和监听是如何使用的。然后再看这个功能怎么去嵌入到 ApplicationContext 中去。

二、从测试入手

其实这套流程是有点像我们平时更熟悉的 MQ 的,平时我们也是通过使用 MQ 来进行异步消费和解耦流程的,因此这里我用一个表格来展示一下他们的结构共同点。

但是注意,和 MQ 不同,这里的容器事件和监听是完全同步的。

角色 Spring 事件 MQ
消息体 ApplicationEvent 消息 payload
发布者 publishEvent() producer.send()
订阅者 ApplicationListener consumer
中介 applicationEventMulticaster broker(Kafka/RocketMQ/RabbitMQ)
注册 registerListeners() 订阅 topic / 消费组

下面的是发送事件的一方(发布者),可以看到直接就是调用了 applicationContext 对象中的 API 去发送的,因此可想而知,我们后面肯定是要去写这个 API 方法的。

java 复制代码
    @Test
    public void test_event() {
        ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:spring.xml");
        applicationContext.publishEvent(new CustomEvent(applicationContext, 1019129009086763L, "成功了!"));

        applicationContext.registerShutdownHook();
    }

其中我们需要一个继承了 ApplicationContextEvent 的对象作为信息载体,就像MQ的消息对象一样。

java 复制代码
public class CustomEvent extends ApplicationContextEvent {

    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;
    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }
}

监听器(消费者),注意,**我们是通过在这里填写接口的泛型来规定这个监听器监听什么消息的。**举一反三,假使我们这里填写 ApplicationEvent ,那么这个监听器将监听所有消息,因为ApplicationEvent 是所有事件载体对象的父类。

注意:监听不是根据对象类型去进行精确匹配的,而是监听一种继承等级,这种等级是根据继承链来设置的,比如这里如果我们填一个父类,那么我们就可以接收到这个父类及其所有子类的消息,而如果填一个继承链顶端的子类,那么我们就只会接收到这一个类的事件消息了。

java 复制代码
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());
    }

}

最终结果如下:

你可能会有疑问,刷新事件和关闭事件是怎么来的,这个暂时先不讲,后面分析源码的时候会了解得更加清楚。

三、容器事件及其监听器

先看继承链,其实这里看着很多,干的事情还是比较清晰的,搞这么多接口和类主要还是为了解耦,所以不要害怕。

1.事件对象载体设计

首先是 EventObject,这个是 JDK 提供的 API,我们直接继承使用即可。

java 复制代码
public abstract class ApplicationEvent extends EventObject {

    public ApplicationEvent(Object source) {
        super(source);
    }

}

继续向上继承:

java 复制代码
public class ApplicationContextEvent extends ApplicationEvent {

    public ApplicationContextEvent(Object source) {
        super(source);
    }

    public final ApplicationContext getApplicationContext() {
        return (ApplicationContext) getSource();
    }

}

从这里开始就到顶了,这个是用于关闭监听的类:

java 复制代码
public class ContextClosedEvent extends ApplicationContextEvent{

    public ContextClosedEvent(Object source) {
        super(source);
    }

}

和上面的类平级,用于刷新监听:

java 复制代码
public class ContextRefreshedEvent extends ApplicationContextEvent{

    public ContextRefreshedEvent(Object source) {
        super(source);
    }

}

2.事件广播器

接口中前面两个方法由抽象类实现,最后一个由抽象类的子类实现:

java 复制代码
public interface ApplicationEventMulticaster {

    /**
     * Add a listener to be notified of all events.
     * @param listener the listener to add
     */
    void addApplicationListener(ApplicationListener<?> listener);

    /**
     * Remove a listener from the notification list.
     * @param listener the listener to remove
     */
    void removeApplicationListener(ApplicationListener<?> listener);

    /**
     * Multicast the given application event to appropriate listeners.
     * @param event the event to multicast
     */
    void multicastEvent(ApplicationEvent event);

}

这里的 supportsEvent() 方法看着有点复杂,其实里面在做两个事情:

第一是在剥壳,由于框架使用者自己写的监听器会被注册为一个 bean ,而我们在创建 bean 的时候是默认通过 cglib 来进行实例化的,因此如果不把 cglib 这层壳剥掉,后面判断类型时就会无法强转,会报错。

第二就是在判断类型,为啥要判断类型呢?就是前面我提及的匹配继承等级,因此这里面源码不是简单用 equal() ,而是使用的 isAssignableFrom() 。当然,这都是 JDK 提供的 API 了。

java 复制代码
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 final void setBeanFactory(BeanFactory beanFactory) {
        this.beanFactory = beanFactory;
    }

    protected Collection<ApplicationListener> getApplicationListeners(ApplicationEvent event) {
        LinkedList<ApplicationListener> allListeners = new LinkedList<ApplicationListener>();
        for (ApplicationListener<ApplicationEvent> listener : applicationListeners) {
            if (supportsEvent(listener, event)) allListeners.add(listener);
        }
        return allListeners;
    }

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

        // 按照 CglibSubclassingInstantiationStrategy、SimpleInstantiationStrategy 不同的实例化类型,需要判断后获取目标 class
        Class<?> targetClass = ClassUtils.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);
        }
        // 判定此 eventClassName 对象所表示的类或接口与指定的 event.getClass() 参数所表示的类或接口是否相同,或是否是其超类或超接口。
        // isAssignableFrom是用来判断子类和父类的关系的,或者接口的实现类和接口的关系的,默认所有的类的终极父类都是Object。如果A.isAssignableFrom(B)结果是true,证明B可以转换成为A,也就是A可以由B转换而来。
        return eventClassName.isAssignableFrom(event.getClass());
    }

}

3.嵌入流程

这里就是为了给外部提供发送消息的 API 了,因此需要一个接口提供方法,然后我们在抽象类中实现。

java 复制代码
public interface ApplicationEventPublisher {

    void publishEvent(ApplicationEvent event);

}

首先还是 refresh() 方法,新增三个流程如下,其实就是嵌入监听器和发布者。其次可以看到我们这里是手动将 SimpleApplicationEventMulticaster 注册为一个 bean 对象的,直接用的常量作为 beanName。

初始化事件发布者主要就是在手动实例化 SimpleApplicationEventMulticaster(这个实现类是用于后续调用监听者方法的) 。

注册事件监听器就是在筛选出监听器,然后通过 getBean() 方法去走一遍 bean 的生命周期,得到监听器的 bean 实例,最后放到 applicationListeners 的 Set 中去 。

发布容器刷新完成事件的 finishRefresh() 其实是用于告诉监听者应该消费消息了,这个交由SimpleApplicationEventMulticaster 实现。

java 复制代码
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 {
        //...
        // 6. 初始化事件发布者
        initApplicationEventMulticaster();

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

        // 9. 发布容器刷新完成事件
        finishRefresh();
    }

    private void initApplicationEventMulticaster() {
        ConfigurableListableBeanFactory beanFactory = getBeanFactory();
        applicationEventMulticaster = new SimpleApplicationEventMulticaster(beanFactory);
        beanFactory.registerSingleton(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, applicationEventMulticaster);
    }

    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 close() {
        // 发布容器关闭事件
        publishEvent(new ContextClosedEvent(this));

        // 执行销毁单例bean的销毁方法
        getBeanFactory().destroySingletons();
    }

}
 

SimpleApplicationEventMulticaster 中最重要的就是 getApplicationListeners(event) 方法了,本质是从 applicationListeners 的 Set 中获取监听器的所有成品 bean 示例,然后进行遍历调用。

java 复制代码
public class SimpleApplicationEventMulticaster extends AbstractApplicationEventMulticaster {

    public SimpleApplicationEventMulticaster(BeanFactory beanFactory) {
        setBeanFactory(beanFactory);
    }

    @SuppressWarnings("unchecked")
    @Override
    public void multicastEvent(final ApplicationEvent event) {
        for (final ApplicationListener listener : getApplicationListeners(event)) {
            listener.onApplicationEvent(event);
        }
    }

}
java 复制代码
    protected Collection<ApplicationListener> getApplicationListeners(ApplicationEvent event) {
        LinkedList<ApplicationListener> allListeners = new LinkedList<ApplicationListener>();
        for (ApplicationListener<ApplicationEvent> listener : applicationListeners) {
            if (supportsEvent(listener, event)) allListeners.add(listener);
        }
        return allListeners;
    }

四、总结

至此,IOC篇完结。

相关推荐
金銀銅鐵18 分钟前
带您看懂字节码指令(三):一行加法里有什么
java·jvm
文丑颜不良啊23 分钟前
LeetCode2921~2940题目解析
java·leetcode
北漂人Java1 小时前
基于 Spring AI Alibaba WorkFlow 构建 Java 生产级 AI Agent
java·人工智能·spring
用户112936112192 小时前
编译全绿,上线就崩:MyBatis 五层方法名大小写埋下的坑
java
文丑颜不良啊3 小时前
LeetCode2901~2920题目解析
java·leetcode
码云社区3 小时前
能碳管理系统开发要关注什么?能源数据、统计口径与AI分析的衔接
java·人工智能·ai·能源·能源管理系统·能碳管理系统·能碳管理系统源码
Flynn Liang3 小时前
Agentic 工作流:子代理、Supervisor 与人工介入
java·langchain·多智能体·supervisor·agentic·langchain4j
xys_6783 小时前
JavaSE通关手册:运算符
java·java-ee
jyOverQ4 小时前
从零学 JVM(七):垃圾收集器详解——Serial、ParNew、Parallel、CMS 与 G1
java·jvm