【重写SpringFramework】事件机制下(chapter 3-12)

165 阅读13分钟

1. 前言

上一节我们介绍了生命周期管理的三个特点,针对其不足之处,引入了事件的概念。接着介绍了观察者模式,以及 Java 对订阅发布模式和监听器模式的支持。Spring 框架实现了基于监听器模式的事件机制,事件多播器持有一组监听器列表,可以发送多种类型的事件。本节的重点有两个,一是将 Spring 事件机制整合到 ApplicationContext 的现有流程之中,实现容器到组件、组件与组件之间自由、灵活的通信。二是实现声明式的监听器,即监听器方法。

2. Spring 整合

2.1 ApplicationEventPublisher

ApplicationEventPublisher 的接口的作用只有一个,就是发布事件。ApplicationContext 接口继承了 ApplicationEventPublisher 接口,从而拥有了发布事件的能力。

public interface ApplicationEventPublisher {
    void publishEvent(ApplicationEvent event);
}

public interface ApplicationContext extends ApplicationEventPublisher {}

2.2 ConfigurableApplicationContext

ConfigurableApplicationContext 接口虽然定义了 addApplicationListener 方法,但这与观察者模式的主题角色没有任何关系。作为对外的配置接口,提供一个添加组件的方法无可厚非,事实上该方法主要是框架内部在使用。此外,没有对应的移除监听器的方法也能说明问题。

public interface ConfigurableApplicationContext {
    void addApplicationListener(ApplicationListener<?> listener);
}

2.3 监听器的适配

ApplicationContext 持有一个 ApplicationEventMulticaster 实例,事件多播器存储了所有的监听器。Spring 实现了两种类型的监听器,一种是监听器方法,即声明了 @EventListener 注解的方法。另一种是监听器类,直接实现 ApplicationListener 接口即可。为了统一调用逻辑,这两种监听器都需要先包装成适配器对象,然后再注册到事件多播器中。

12.1 监听器适配.png

3. 监听器类

监听器类除了直接实现 ApplicationListener 接口之外,同时还是 Spring 容器管理的单例。关于监听器类的初始化操作,我们在容器的刷新流程中一并介绍。至于监听器类的事件回调,已经在事件多播器的 multicastEvent 方法中介绍过了。

3.1 容器初始化

AbstractApplicationContext 类新增了两个字段,其中 applicationEventMulticaster 表示事件多播器,applicationListeners 表示通过 addApplicationListener 方法手动添加的监听器类。在 refresh 方法中,有多个步骤与事件有关。大多数的操作是事件机制的通用逻辑,只有少部分是属于监听器类的,至于监听器方法的处理另有一套解决方案。

public abstract class AbstractApplicationContext {
    //事件多播器
    private ApplicationEventMulticaster applicationEventMulticaster;
    //临时存储的监听器类列表
    private final Set<ApplicationListener<?>> applicationListeners = new LinkedHashSet<>();

    @Override
    public void refresh() {
        //3. ApplicationContext使用BeanFactory前的准备工作,比如添加必要的组件
        prepareBeanFactory(beanFactory);

        //7. 注册事件多播器,默认为SimpleApplicationEventMulticaster
        initApplicationEventMulticaster(beanFactory);

        //9. 注册监听器,发送早期事件(earlyApplicationEvents)
        registerListeners();

        //11. 发送refresh完成事件
        finishRefresh();
    }
}

第三步属于准备阶段,prepareBeanFactory 方法负责注册基础组件。这里将容器本身注册为 ApplicationEventPublisher 类型的依赖项,届时在业务代码中注入 ApplicationEventPublisher 实例来发布事件。

//step-3 准备工作
private void prepareBeanFactory(ConfigurableBeanFactory beanFactory) {
    //将ApplicationEventPublisher注册为依赖项
    beanFactory.registerResolvableDependency(ApplicationEventPublisher.class, this);
}

第七步,创建事件多播器实例,并注册成一个单例。这意味着我们有三种方式来发布事件,一是注入 ApplicationEventMulticaster 类型的依赖,二是注入 ApplicationEventPublisher 类型的依赖,三是注入 ApplicationContext 类型的依赖。其中,后两种类型都是 Spring 容器本身。

注:第七步和第九步可以看做一个整体。两个步骤之所以分开,是因为第八步 onRefresh 是一个扩展方法,子类有可能会向 Spring 容器中添加新的监听器类。

//step-7 注册事件多播器
protected void initApplicationEventMulticaster(ConfigurableBeanFactory beanFactory) {
    this.applicationEventMulticaster = new SimpleApplicationEventMulticaster();
    beanFactory.registerSingleton(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, this.applicationEventMulticaster);
}

第九步负责将所有的监听器对象注册到 ApplicationEventMulticaster 组件中,需要注意的是,监听器对象有两个来源:

  • 临时存放在 applicationEventMulticaster 字段,这些是手动注册的监听器类
  • 注册在 Spring 容器中的监听器单例,通过扫描或工厂方法的方式加载的监听器类
//step-9 注册监听器类
protected void registerListeners() {
    //注册手动添加的监听器
    for (ApplicationListener<?> listener : this.applicationListeners) {
        this.applicationEventMulticaster.addApplicationListener(listener);
    }

    //注册BeanFactory中的事件监听器
    List<String> names = getBeanNamesForType(ApplicationListener.class);
    for (String name : names) {
        this.applicationEventMulticaster.addApplicationListener((ApplicationListener<?>) getBean(name));
    }
}

finishRefresh 方法是容器刷新操作的最后一步,之前我们处理了 Lifecycle 组件的初始化操作,现在还需要发送 ContextRefreshedEvent 事件。此时,Spring 容器的启动流程执行完毕,应用程序是可用的。

protected void finishRefresh() {
    //处理Lifecycle(略)

    //发送ContextRefreshedEvent事件
    publishEvent(new ContextRefreshedEvent(this));
}

3.2 容器关闭

不管是调用 close 方法手动关闭,还是通过向 JVM 注册钩子自动关闭,最终都会执行 doClose 方法,发送容器关闭事件。

protected void doClose() {
    //发送容器关闭事件
    publishEvent(new ContextClosedEvent(this));

    //处理Lifecycle(略)
    //销毁所有的单例(略)
}

4. 监听器方法

4.1 概述

监听器方法是指声明了 @EventListener 注解的方法,只要是由 Spring 容器管理的单例都可以定义监听器方法。@EventListener 注解的 value 属性的类型是 Class[],表示监听的一组事件。

@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface EventListener {
    //指定监听的事件(组)
    Class<?>[] value() default {};
}

监听器方法由 EventListenerMethodProcessor 类进行处理,并包装成 ApplicationListenerMethodAdapter 适配器类。与 @Autowired@Configuration 等注解类似,负责解析 @EventListener 注解的组件也是通过 AnnotationConfigUtils 工具类的 registerAnnotationConfigProcessors 方法注册到 Spring 容器中。

public class AnnotationConfigUtils {
    public static final String EVENT_LISTENER_PROCESSOR_BEAN_NAME = "internalEventListenerProcessor";

    public static void registerAnnotationConfigProcessors(BeanDefinitionRegistry registry) {
        //注册支持@Autowired和@Value注解的组件(略)
        //注册支持@Configuration注解的组件(略)
        //注册支持@PostConstruct和@PreDestroy注解的组件(略)

        //注册支持@EventListener注解的组件
        if (!registry.containsBeanDefinition(EVENT_LISTENER_PROCESSOR_BEAN_NAME)) {
            RootBeanDefinition def = new RootBeanDefinition(EventListenerMethodProcessor.class);
            registerPostProcessor(registry, def, EVENT_LISTENER_PROCESSOR_BEAN_NAME);
        }
    }
}

4.2 监听器方法的初始化

EventListenerMethodProcessor 实现了 SmartInitializingSingleton 接口,说明该组件的是在所有的单例 Bean 实例化之后执行。在 afterSingletonsInstantiated 方法中,此时容器中所有的单例已实例化,遍历所有单例,依次执行 processBean 方法。

public class EventListenerMethodProcessor implements SmartInitializingSingleton, ApplicationContextAware {

    @Override
    public void afterSingletonsInstantiated() {
        //遍历所有的单例Bean
        List<String> beanNames = this.applicationContext.getBeanNamesForType(Object.class);
        for (String beanName : beanNames) {
            Class<?> type = this.applicationContext.getType(beanName);
            processBean(beanName, type);
        }
    }
}

processBean 方法中,首先找出指定类型中所有声明了 @EventListener 注解的方法,如果监听器方法存在,则为每一个监听器方法创建一个适配器对象。然后将适配器对象注册到 ApplicationEventMulticaster 组件的监听器列表中。

//解析单例,寻找监听器方法,并包装成适配器对象
protected void processBean(final String beanName, final Class<?> targetType) {

    if (!this.nonAnnotatedClasses.contains(targetType)) {
        //1) 寻找所有声明了@EventListener注解的方法
        Map<Method, EventListener> annotatedMethods = MethodIntrospector.selectMethods(targetType,
        	   new MethodIntrospector.MetadataLookup<EventListener>() {
                @Override
                public EventListener inspect(Method method) {
                    return AnnotatedElementUtils.findMergedAnnotation(method, EventListener.class);
                }
            });

        if (CollectionUtils.isEmpty(annotatedMethods)) {
            this.nonAnnotatedClasses.add(targetType);
        }else{
            //2) 为每个监听方法创建一个适配器对象,并添加到监听器集合中
            for (Method method : annotatedMethods.keySet()) {
                ApplicationListener<?> listener = new ApplicationListenerMethodAdapter(beanName, targetType, method, this.applicationContext);
                this.applicationContext.addApplicationListener(listener);
            }
        }
    }
}

4.3 适配器的监听回调

当应用程序发布事件时,事件多播器从监听器列表中寻找感兴趣的监听器集合,遍历执行每个监听器的 onApplicationEvent 回调方法。对于监听器方法,这一工作是由适配器类 ApplicationListenerMethodAdapter 完成的。onApplicationEvent 方法可以分为两步:

  1. 对参数进行解析,由于监听器方法是通过反射的方式调用方法,参数的类型必须是 Object[],因此需要对原始的事件类型进行转换。
  2. 通过反射的方式调用监听器方法,执行监听回调
public class ApplicationListenerMethodAdapter implements GenericApplicationListener {
    private final String beanName;
    private final Method bridgedMethod;
    private final List<ResolvableType> declaredEventTypes;
    private ApplicationContext applicationContext;

    @Override
    public void onApplicationEvent(ApplicationEvent event) {
        Object[] args = resolveArguments(event);
        doInvoke(args);
    }
}

第一步,解析参数。在 resolveArguments 方法中,首先检查监听器方法是否有资格执行,具体来说就是判断 @EventListener 注解声明的事件列表(存储在 declaredEventTypes 字段中)与传入的事件类型是否匹配。其次是对方法参数的处理,如果没有参数返回一个空数组,否则将传入的事件作为参数。从这里可以看到,监听器方法的一大特点,要么无参,要么只有一个参数,且必须是 ApplicationEvent 或子类型。

//解析监听器方法的参数
protected Object[] resolveArguments(ApplicationEvent event) {
    //如果@EventListener注解声明的事件列表与监听器方法的参数不匹配,则返回空
    ResolvableType declaredEventType = getResolvableType(event);
    if (declaredEventType == null) {
        return null;
    }

    //方法没有参数,返回空数组
    if (this.method.getParameterTypes().length == 0) {
        return new Object[0];
    }
    //将事件对象作为方法的参数
    return new Object[] {event};
}

第二步,通过反射的方式调用监听方法。反射调用需要满足三个条件,声明类的实例、目标方法、传入的参数。此时我们已经通过解析得到了参数,目标方法本来就是适配器类的 bridgedMethod 字段,声明类的实例可以从 Spring 容器中获得(监听器方法往往定义在一个配置类或组件类中)。

//调用监听器方法
protected Object doInvoke(Object... args) {
    //获取声明类实例
    Object bean = this.applicationContext.getBean(this.beanName);
    //bridgedMethod字段就是监听器方法
    ReflectionUtils.makeAccessible(this.bridgedMethod);
	return this.bridgedMethod.invoke(bean, args);
}

通过反射的方式调用方法,我们需要特别注意参数的类型。在示例代码中,监听器方法支持两种类型的事件,但是方法参数指定为 AEvent。当发送 BEvent 事件时,由于传入的事件类型与方法参数类型不一致(argument type mismatch),会导致抛出 IllegalArgumentException 异常。因此,如果监听器方法支持多种事件,方法参数必须是所有事件的共同父类,比如 ApplicationEventObject 类型。

//示例代码,监听器方法的参数问题
@EventListener({AEvent.class, BEvent.class})
public void XxxEvent(AEvent event) {
    //当发送BEvent事件时,反射调用方法会报错
}

5. 整体流程分析

Spring 容器在对两种类型的监听器进行处理时,既有各自的逻辑,也有共同的逻辑。接下来,我们回顾有关事件机制的所有操作,从整体层面加深认识。整个流程分为七个步骤,如下所示:

  1. AnnotationConfigUtils 工具类的 registerAnnotationConfigProcessors 方法,注册 EventListenerMethodProcessor 组件。
  2. AbstractApplicationContextrefresh 方法中执行 2~6 步。首先调用 prepareBeanFactory 方法添加 ApplicationEventPublisher 类型的依赖,实际上是容器自身。
  3. 调用 initApplicationEventMulticaster 方法注册事件多播器组件。
  4. 调用 registerListeners 方法注册监听器类,具体是通过事件多播器将监听器类对象转换成 GenericApplicationListenerAdapter 适配器对象。
  5. 调用 finishBeanFactoryInitialization 方法,回调 SmartInitializingSingleton 接口,通过 EventListenerMethodProcessor 组件将监听器方法转换为 ApplicationListenerMethodAdapter 适配器对象。
  6. 调用 finishRefresh 方法,发布容器刷新事件。
  7. 当容器关闭时,回调 onClose 方法,发布容器关闭事件。

12.2 事件处理流程.png

6. 测试

6.1 监听器类

在测试方法中,我们向 Spring 容器注册了三个监听器类(当然也可以通过组件扫描或 BeanMethod 的方式),在容器的刷新过程中,会侦测到这些监听器类,并注册到事件多播器的监听器列表中。最后分别发送了两个事件,由监听器负责接收。

//测试方法
@Test
public void testEventListener(){
    GenericApplicationContext context = new GenericApplicationContext();
    ConfigurableBeanFactory factory = context.getBeanFactory();
    factory.registerSingleton("l1", new NewsListener("张三"));
    factory.registerSingleton("l2", new NewsListener("李四"));
    factory.registerSingleton("l3", new MilkListener("李四"));
    context.refresh();

    context.publishEvent(new NewsEvent("《人民日报》"));
    context.publishEvent(new MilkEvent("光明牛奶"));
}

从测试结果来看,张三只监听了报纸的事件,而李四监听了报纸和牛奶的事件,符合预期。

张三读 《人民日报》
李四读 《人民日报》
李四喝 光明牛奶

6.2 监听器方法

首先在配置类 EventConfig 中定义两个监听器方法,其中一个只监听 NewsEvent,另一个同时监听 NewsEventMilkEvent 两个事件。

//测试类:配置两个监听方法
@Configuration
public class EventConfig {

    //监听单一事件
    @EventListener(NewsEvent.class)
    public void newsListener(NewsEvent event){
        System.out.println("张三读 " + event.getSource());
    }


    //监听多个事件,方法参数必须是事件列表的共同父类,比如ApplicationEvent或Object
    @EventListener({NewsEvent.class, MilkEvent.class})
    public void newsAndMilkListener(ApplicationEvent event){
        if (event instanceof NewsEvent) {
            System.out.println("李四读 " + event.getSource());
        }

        if (event instanceof MilkEvent) {
            System.out.println("李四喝 " + event.getSource());
        }
    }
}

在测试方法中,Spring 容器的类型是 AnnotationConfigApplicationContext,自动注册 EventListenerMethodProcessor 组件,然后对配置类 EventConfig 进行解析,将两个监听器方法包装成适配器对象,并添加到 Spring 容器的监听器列表中。最后分别发送了两个事件,由监听器负责接收。

//测试方法
@Test
public void testEventMethod(){
    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(EventConfig.class);
    context.publishEvent(new NewsEvent("《人民日报》"));
    context.publishEvent(new MilkEvent("光明牛奶"));
}

测试结果与上一个测试相同,这说明监听器方法和监听器类的功能是相同的。

张三读 《人民日报》
李四读 《人民日报》
李四喝 光明牛奶

6.3 容器事件

除了监听自定义事件,我们也可以监听容器的生命周期。定义两个监听器类,分别监听容器的刷新和关闭事件。ContextRefreshedEventContextClosedEvent 是 Spring 预定义的容器事件。

//测试类
public class SimpleContextRefreshedListener implements ApplicationListener<ContextRefreshedEvent> {
    @Override
    public void onApplicationEvent(ContextRefreshedEvent event) {
        System.out.println("监听容器刷新事件");
    }
}

public class SimpleContextClosedListener implements ApplicationListener<ContextClosedEvent> {
    @Override
    public void onApplicationEvent(ContextClosedEvent event) {
        System.out.println("监听容器关闭事件");
    }
}

在测试方法中,我们先将两个监听器类注册到 Spring 容器中,并向虚拟机注册销毁的回调。然后执行 refresh 方法,在容器刷新流程的最后阶段,发送 ContextRefreshedEvent 事件。代码执行完毕,由虚拟机触发 close 回调,发送 ContextClosedEvent 事件。

//测试方法
@Test
public void testContextEvent() {
    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
    context.register(SimpleContextRefreshedListener.class);
    context.register(SimpleContextClosedListener.class);
    //注册虚拟机钩子,当程序调用完毕后,由JVM来回调close方法,发送ContextClosedEvent
    context.registerShutdownHook();
    context.refresh();
}

从测试结果可以看到,两个监听器分别对指定的事件进行了拦截。

监听容器刷新事件
监听容器关闭事件

7. 总结

本节我们讨论了 Spring 容器与事件机制的整合,ApplicationContext 通过继承 ApplicationEventPublisher 接口,从而拥有了发布事件的能力。此外,ApplicationContext 持有一个事件多播器实例,负责处理事件的具体逻辑。

Spring 实现了两种类型的监听器,即监听器类和监听器方法。为了统一调用逻辑,这两种监听器都需要先包装成适配器对象,然后再注册到事件多播器中。我们得到了两个非常重要的结论:其一,事件多播器存储了所有的监听器。其二,这些监听器对象是以适配器的形式存在。

此外,在 Spring 容器的生命周期中,在特定的时间点发布事件,比如容器刷新操作的结尾发布 ContextRefreshedEvent 事件,当容器关闭时发布 ContextClosedEvent 方法。我们可以通过监听器类或监听器方法的形式,来监听感兴趣的事件。

8. 项目信息

新增修改一览,新增(10),修改(5)。

context
└─ src
   ├─ main
   │  └─ java
   │     └─ cn.stimd.spring.context
   │        ├─ annotation
   │        │  └─ AnnotationConfigUtils.java (*)
   │        ├─ event
   │        │  ├─ ApplicationContextEvent.java (+)
   │        │  ├─ ApplicationEventPublisher.java (+)
   │        │  ├─ ApplicationListenerMethodAdapter.java (+)
   │        │  ├─ ContextClosedEvent.java (+)
   │        │  ├─ ContextRefreshedEvent.java (+)
   │        │  ├─ EventListener.java (+)
   │        │  └─ EventListenerMethodProcessor.java (+)
   │        ├─ support
   │        │  └─ AbstractApplicationContext.java (*)
   │		├─ ApplicationContext.java(*)
   │		└─ ConfigurableApplicationContext.java(*)
   └─ test
      └─ java
         └─ context
            └─ event
               ├─ EventConfig.java (+)
               ├─ EventTest.java (*)
               ├─ SimpleContextClosedListener.java (+)
               └─ SimpleContextRefreshedListener.java (+)

注:+号表示新增、*号表示修改

注:项目的 master 分支会跟随教程的进度不断更新,如果想查看某一节的代码,请选择对应小节的分支代码。


欢迎关注公众号【Java编程探微】,加群一起讨论。

原创不易,觉得内容不错请关注、点赞、收藏。