夯实Spring系列|第二十二章:Spring 事件(Events)

夯实Spring系列|第二十二章:Spring 事件(Events)

前言

本章主要是了解 Spring Event 事件底层实现的相关细节以及一些重要的使用场景。

1.项目环境

2.Java 事件/监听编程模型

设计模式 - 观察者模式扩展

  • 可观察对象(消息发送者)- java.util.Observable
  • 观察者 - java.util.Observer

标准化接口

  • 事件对象 - java.util.EventObject
  • 事件监听器 - java.util.EventListener

示例:

public class ObservableDemo {
    public static void main(String[] args) {
        EventObservable observable = new EventObservable();
        // 添加观察者(监听者)
        observable.addObserver(new EventObserver());
        // 手动设置发布消息(事件)
        observable.notifyObservers("Hello,World");
    }

    static class EventObservable extends Observable {
        public void setChanged() {
            super.setChanged();// 必须通过手动设置的方式修改 changed 状态位
        }

        public void notifyObservers(Object obj) {
            setChanged();
            super.notifyObservers(new EventObject(obj));
            clearChanged();
        }
    }

    static class EventObserver implements Observer {
        @Override
        public void update(Observable o, Object event) {
            EventObject eventObject = (EventObject) event;
            System.out.println("收到事件:" + eventObject);
        }
    }
}

执行结果:

收到事件:java.util.EventObject[source=Hello,World]

3.面向接口的事件/监听器设计模式

Java 技术规范事件接口监听器接口
JavaBeansjava.beans.PropertyChangeEventjava.beans.PropertyChangeListener
Java AWTjava.awt.event.MouseEventjava.awt.event.MouseListener
Java Swingjavax.swing.event.MenuEventjavax.swing.event.MenuListener
Java Preferencejava.util.prefs.PreferenceChangeEventjava.util.prefs.PreferenceChangeListener

基本模式(约定俗成):

  • 所有的 Event 都会继承 EventObject

  • 所有的 Listener 都会继承 EventListener

4.面向注解的事件/监听器设计模式

扩展视野,看一看 JavaEE 以及 Java 里面一些相关事件/监听注解

Java 技术规范事件注解监听器注解
Servlet 3.0+@javax.servlet.annotation.WebListener
JPA 1.0+@javax.persistence.PostPersist
Java Common@PostConstruct
EJB 3.0+@javax.ejb.PrePassivate
JSF 2.0+@javax.face.event.ListenerFor

5.Spring 标准事件-ApplicationEvent

Java 标准事件 java.util.EventObject 扩展

  • 扩展特性:事件发生事件戳

Spring 应用上下文 ApplicationEvent 扩展 - ApplicationContextEvent

  • Spring 应用上下文(ApplicationContext)作为事件源
  • 具体实现
    • org.springframework.context.event.ContextClosedEvent
    • org.springframework.context.event.ContextRefreshedEvent
    • org.springframework.context.event.ContextStartedEvent
    • org.springframework.context.event.ContextStoppedEvent

ApplicationEvent 源码

  • 继承了 EventObject 基础上,增加了 timestamp 时间戳
public abstract class ApplicationEvent extends EventObject {

	/** use serialVersionUID from Spring 1.2 for interoperability. */
	private static final long serialVersionUID = 7099057708183571937L;

	/** System time when the event happened. */
	private final long timestamp;

	public ApplicationEvent(Object source) {
		super(source);
		this.timestamp = System.currentTimeMillis();
	}

	public final long getTimestamp() {
		return this.timestamp;
	}

}

ApplicationContextEvent 源码

  • 通过构造方法将 ApplicationContext 应用上下文传入到 ApplicationEvent 中,方便使用
public abstract class ApplicationContextEvent extends ApplicationEvent {

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

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

}

6.基于接口的 Spring 事件监听器

Java 标准事件监听器 java.util.EventListener 扩展

  • 扩展接口 - org.springframework.context.ApplicationListener
  • 设计特点:单一类型事件处理
  • 处理方法:onApplicationEvent(ApplicationEvent)
  • 事件类型:org.springframework.context.ApplicationEvent

示例

public class ApplicationListenerDemo {
    public static void main(String[] args) {
        GenericApplicationContext context = new GenericApplicationContext();

        // 向 Spring 应用上下文中注册时间
        context.addApplicationListener(new ApplicationListener<ApplicationEvent>() {
            @Override
            public void onApplicationEvent(ApplicationEvent event) {
                System.out.println("接收到 Spring 事件:" + event);
            }
        });
        context.refresh(); // 对应事件 ContextRefreshedEvent
        context.start();// 对应事件 ContextStartedEvent
        context.stop();// 对应事件 ContextStoppedEvent
        context.close();// 对应事件 ContextClosedEvent
    }

}

执行结果:

接收到 Spring 事件:org.springframework.context.event.ContextRefreshedEvent[source=org.springframework.context.support.GenericApplicationContext@129a8472, started on Thu Jun 18 21:03:40 CST 2020]
接收到 Spring 事件:org.springframework.context.event.ContextStartedEvent[source=org.springframework.context.support.GenericApplicationContext@129a8472, started on Thu Jun 18 21:03:40 CST 2020]
接收到 Spring 事件:org.springframework.context.event.ContextStoppedEvent[source=org.springframework.context.support.GenericApplicationContext@129a8472, started on Thu Jun 18 21:03:40 CST 2020]
接收到 Spring 事件:org.springframework.context.event.ContextClosedEvent[source=org.springframework.context.support.GenericApplicationContext@129a8472, started on Thu Jun 18 21:03:40 CST 2020]

7.基于注解的 Spring 事件监听器

Spring 注解 - @org.springframework.context.event.EventListener

特性说明
设计特点支持多 ApplicationEvent 类型无需接口约束
注解目标方法
是否支持异步执行支持
是否支持泛型类型事件支持
是否支持顺序控制支持,配合 @Order 注解控制

示例

  • 接收所有类型
    @EventListener
    public void onApplicationEvent(ApplicationEvent event){
        System.out.println("@EventListener - 接收到 Spring 事件:" + event);
    }
  • 具体事件类型
    @EventListener
    public void onApplicationEvent(ContextRefreshedEvent event){
        System.out.println("@EventListener - 接收到 [ContextRefreshedEvent] 事件:" + event);
    }

    @EventListener
    public void onApplicationEvent(ContextStartedEvent event){
        System.out.println("@EventListener - 接收到 [ContextStartedEvent] 事件:" + event);
    }

    @EventListener
    public void onApplicationEvent(ContextStoppedEvent event){
        System.out.println("@EventListener - 接收到 [ContextStoppedEvent] 事件:" + event);
    }

    @EventListener
    public void onApplicationEvent(ContextClosedEvent event){
        System.out.println("@EventListener - 接收到 [ContextClosedEvent] 事件:" + event);
    }
  • 异步执行
    @EventListener
    @Async
    public void onApplicationEvent(ContextStoppedEvent event){
        System.out.println("@EventListener(Async 异步) - 接收到 [ContextStoppedEvent] 事件:" + event);
    }

执行结果:

@EventListener(Async 异步) - 接收到 [ContextStoppedEvent] 事件:org.springframework.context.event.ContextStoppedEvent[source=org.springframework.context.annotation.AnnotationConfigApplicationContext@2752f6e2, started on Thu Jun 18 21:28:25 CST 2020]
  • 顺序控制
    @EventListener
    @Order(2)
    public void onApplicationEvent(ContextRefreshedEvent event){
        System.out.println("@EventListener(onApplicationEvent) - 接收到 [ContextRefreshedEvent] 事件:" + event);
    }

    @EventListener
    @Order(1)
    public void onApplicationEvent1(ContextRefreshedEvent event){
        System.out.println("@EventListener(onApplicationEvent1) - 接收到 [ContextRefreshedEvent] 事件:" + event);
    }

执行结果:

@EventListener(onApplicationEvent1) - 接收到 [ContextRefreshedEvent] 事件:org.springframework.context.event.ContextRefreshedEvent[source=org.springframework.context.annotation.AnnotationConfigApplicationContext@2752f6e2, started on Thu Jun 18 21:32:43 CST 2020]
@EventListener(onApplicationEvent) - 接收到 [ContextRefreshedEvent] 事件:org.springframework.context.event.ContextRefreshedEvent[source=org.springframework.context.annotation.AnnotationConfigApplicationContext@2752f6e2, started on Thu Jun 18 21:32:43 CST 2020]

8.注册 Spring ApplicationListener

方法一:通过 ConfigurableApplicationContext API 注册

        // 方法一:通过 ConfigurableApplicationContext API 注册
        context.addApplicationListener(new ApplicationListener<ApplicationEvent>() {
            @Override
            public void onApplicationEvent(ApplicationEvent event) {
                System.out.println("ApplicationListener - 接收到 Spring 事件:" + event);
            }
        });

方法二:ApplicationListener 作为 Spring Bean 注册

        // 方法二:基于 ApplicationListener 注册为 Spring Bean
        context.register(MyApplicationListener.class);
        ...
            
        /**
     * 这里的泛型设计可以指定具体类型,或者父类
     */
    static class MyApplicationListener implements ApplicationListener<ContextRefreshedEvent>{

        @Override
        public void onApplicationEvent(ContextRefreshedEvent event) {
            System.out.println("MyApplicationListener - 接收到 Spring 事件:" + event);
        }
    }

9.Spring 事件发布器

方法一:通过 ApplicationEventPublisher 发布 Spring 事件

  • 获取 ApplicationEventPublisher
    • 依赖注入

方法二:通过 ApplicationEventMulticaster 发布 Spring 事件

  • 获取 ApplicationEventMulticaster
    • 依赖注入
    • 依赖查找

14 依赖注入 ApplicationEventPublisher - ApplicationEventPublisher 示例

17.同步和异步 Spring 事件广播 - ApplicationEventMulticaster 示例

10.Spring 层次性上下文事件传播

发生场景说明

当 Spring 应用出现多层次 Spring 应用上下文(ApplicationContext)时,如 Spring WebMVC、Spring Boot、SpringCloud 场景下,由子 ApplicationContext 发起 Spring 事件可能会传递到其 Parent ApplicationContext(直到 Root)的过程。

如何避免

  • 定位 Spring 事件源(ApplicationContext)进行过滤处理

10.1 示例

public class HierarchicalSpringEventPropagateDemo {

    public static void main(String[] args) {
        // 1.创建 parent Spring 应用上下文
        AnnotationConfigApplicationContext parentApplicationContext = new AnnotationConfigApplicationContext();
        parentApplicationContext.setId("parent-context");
        // 2.创建 current Spring 应用上下文
        AnnotationConfigApplicationContext currentApplicationContext = new AnnotationConfigApplicationContext();
        currentApplicationContext.setId("current-context");
        // 3.current -> parent
        currentApplicationContext.setParent(parentApplicationContext);

        // 将 MyListener 注册应用上下文
//        currentApplicationContext.addApplicationListener(new MyListener());
        parentApplicationContext.register(MyListener.class);
        currentApplicationContext.register(MyListener.class);

        // 启动应用上下文
        parentApplicationContext.refresh();
        currentApplicationContext.refresh();
    }

    static class MyListener implements ApplicationListener<ContextRefreshedEvent> {

        @Override
        public void onApplicationEvent(ContextRefreshedEvent event) {
            ApplicationContext applicationContext = event.getApplicationContext();
            System.out.printf("上下文[id:%s]接收到事件:[%s]\n", applicationContext.getId(), event);
        }
    }

}

执行结果:

上下文[id:parent-context]接收到事件:[org.springframework.context.event.ContextRefreshedEvent[source=org.springframework.context.annotation.AnnotationConfigApplicationContext@506e6d5e, started on Fri Jun 19 14:45:06 CST 2020]]
上下文[id:current-context]接收到事件:[org.springframework.context.event.ContextRefreshedEvent[source=org.springframework.context.annotation.AnnotationConfigApplicationContext@2a70a3d8, started on Fri Jun 19 14:45:07 CST 2020, parent: org.springframework.context.annotation.AnnotationConfigApplicationContext@506e6d5e]]
上下文[id:current-context]接收到事件:[org.springframework.context.event.ContextRefreshedEvent[source=org.springframework.context.annotation.AnnotationConfigApplicationContext@2a70a3d8, started on Fri Jun 19 14:45:07 CST 2020, parent: org.springframework.context.annotation.AnnotationConfigApplicationContext@506e6d5e]]

可以看到 current-context 接收到了两次 ContextRefreshedEvent 事件。

10.2 源码分析

发送事件的调用链路大致如下:

还是从 AbstractApplicationContext#refresh 启动应用上下文的方法开始

org.springframework.context.support.AbstractApplicationContext#refresh

  • org.springframework.context.support.AbstractApplicationContext#finishRefresh
    • org.springframework.context.support.AbstractApplicationContext#publishEvent(org.springframework.context.ApplicationEvent)
      • org.springframework.context.support.AbstractApplicationContext#publishEvent(java.lang.Object, org.springframework.core.ResolvableType)

10.3 源码调试

第一次 [parent-context] 进来,403 行发送 ContextRefreshedEvent 事件
在这里插入图片描述
这里可以看到当前 ApplicationContext 的 id 为 parent-context
在这里插入图片描述
第二次 [current-context] 进来,可以看到当前 ApplicationContext 的父应用上下文 id

this.parent.getId() = parent-context
在这里插入图片描述
如果判断当前的应用上下文有 parent 应用上下文,那么 parent 应用上下文再 publishEvent 发送一次事件。

10.4 解决方案

如何来解决这个事件被重复处理的问题呢?

可以增加一个缓存来存储已经处理过的事件,如果判断已经处理过了,就不再处理

代码如下:

    static class MyListener implements ApplicationListener<ContextRefreshedEvent> {

        private static Set<ContextRefreshedEvent> processedEvents = new LinkedHashSet<>();

        @Override
        public void onApplicationEvent(ContextRefreshedEvent event) {
            if(processedEvents.add(event)){
                ApplicationContext applicationContext = event.getApplicationContext();
                System.out.printf("上下文[id:%s]接收到事件:[%s]\n", applicationContext.getId(), event);
            }
        }
    }

11.Spring 内建事件

ApplicationContextEvent 派生事件

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

部分源码

  • org.springframework.context.support.AbstractApplicationContext#start

可以看到调用 start() 或者 stop() ,会通过 publishEvent 来发布事件

	@Override
	public void start() {
		getLifecycleProcessor().start();
		publishEvent(new ContextStartedEvent(this));
	}

	@Override
	public void stop() {
		getLifecycleProcessor().stop();
		publishEvent(new ContextStoppedEvent(this));
	}

12.Spring 4.2 PayLoad 事件

Spring PayLoad 事件 - org.springframework.context.PayloadApplicationEvent

  • 使用场景:简化 Spring 事件发送,关注事件源主体
  • 发送方法
    • ApplicationEventPublisher#publishEvent

示例

第一种:

    @Override
    public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
        applicationEventPublisher.publishEvent(new ApplicationEvent("hello,world"){

        });
        // 改造成 PayLoadApplicationEvent
        applicationEventPublisher.publishEvent("hello,world");
        // 发送 PayLoadApplicationEvent
        applicationEventPublisher.publishEvent(new PayloadApplicationEvent(this,"hello,world"));
    }

第二种:

applicationEventPublisher.publishEvent(new MyPayLoadApplicationEvent(this,"hello,world"));

自定义 MyPayLoadApplicationEvent 类继承 PayloadApplicationEvent

    static class MyPayLoadApplicationEvent<String> extends PayloadApplicationEvent<String>{

        /**
         * Create a new PayloadApplicationEvent.
         *
         * @param source  the object on which the event initially occurred (never {@code null})
         * @param payload the payload object (never {@code null})
         */
        public MyPayLoadApplicationEvent(Object source, String payload) {
            super(source, payload);
        }
    }

执行结果:

ApplicationListener - 接收到 Spring 事件:com.huajie.thinking.in.spring.event.ApplicationListenerDemo$2[source=hello,world]
ApplicationListener - 接收到 Spring 事件:org.springframework.context.PayloadApplicationEvent[source=org.springframework.context.annotation.AnnotationConfigApplicationContext@7f690630, started on Fri Jun 19 16:50:00 CST 2020]
ApplicationListener - 接收到 Spring 事件:org.springframework.context.PayloadApplicationEvent[source=com.huajie.thinking.in.spring.event.ApplicationListenerDemo@710c2b53]
ApplicationListener - 接收到 Spring 事件:com.huajie.thinking.in.spring.event.ApplicationListenerDemo$MyPayLoadApplicationEvent[source=com.huajie.thinking.in.spring.event.ApplicationListenerDemo@710c2b53]

13.自定义 Spring 事件

扩展 org.springframework.context.ApplicationEvent

实现 org.springframework.context.ApplicationListener

注册 org.springframework.context.ApplicationListener

示例

自定义 Spring 事件

public class MySpringEvent extends ApplicationEvent {
    /**
     * Create a new {@code ApplicationEvent}.
     *
     * @param message the object on which the event initially occurred or with
     *               which the event is associated (never {@code null})
     */
    public MySpringEvent(String message) {
        super(message);
    }

    @Override
    public Object getSource() {
        return String.valueOf(super.getSource());
    }

    public Object getMessage() {
        return getSource();
    }

}

自定义监听用来监听我们的 MySpringEvent事件

public class MyApplicationListener implements ApplicationListener<MySpringEvent> {

    @Override
    public void onApplicationEvent(MySpringEvent event) {
        System.out.println("MyApplicationListener - 接收到 Spring 事件:" + event);
    }
}

测试

public class CustomizedSpringEventDemo {
    public static void main(String[] args) {
        GenericApplicationContext context = new GenericApplicationContext();

        // 1.添加自定义 Spring 事件监听器
        context.addApplicationListener(new MyApplicationListener());

        // 2.启动 Spring 应用上下文
        context.refresh();

        // 3.发布自定义 Spring 事件
        context.publishEvent(new MySpringEvent("hello,world"));

        // 4.关闭应用上下文
        context.close();
    }
}

执行结果:

MyApplicationListener - 接收到 Spring 事件:com.huajie.thinking.in.spring.event.MySpringEvent[source=hello,world]

14.依赖注入 ApplicationEventPublisher

通过 ApplicationEventPublisherAware 回调接口

通过 @Autowired ApplicationEventPublisher

示例

public class InjectionApplicationEventPublisherDemo implements ApplicationEventPublisherAware, ApplicationContextAware {

    @Autowired
    private ApplicationEventPublisher autowiredApplicationEventPublisher;

    @Autowired
    private ApplicationContext autowiredApplicationContext;

    @PostConstruct
    public void init(){
        // #3
        autowiredApplicationEventPublisher.publishEvent(new MySpringEvent("the event from @Autowired-ApplicationEventPublisher"));
        // #4
        autowiredApplicationContext.publishEvent(new MySpringEvent("the event from @Autowired-ApplicationContext"));
    }

    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();

        context.register(InjectionApplicationEventPublisherDemo.class);

        //添加 Spring 事件监听
        context.addApplicationListener(new MyApplicationListener());

        context.refresh();

        context.close();
    }


    @Override
    public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
        applicationEventPublisher.publishEvent(new MySpringEvent("the event from ApplicationEventPublisherAware"));// #1
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        applicationContext.publishEvent(new MySpringEvent("the event from ApplicationContextAware"));// #2
    }
}

执行结果:

MyApplicationListener - 接收到 Spring 事件:com.huajie.thinking.in.spring.event.MySpringEvent[source=the event from ApplicationEventPublisherAware]
MyApplicationListener - 接收到 Spring 事件:com.huajie.thinking.in.spring.event.MySpringEvent[source=the event from ApplicationContextAware]
MyApplicationListener - 接收到 Spring 事件:com.huajie.thinking.in.spring.event.MySpringEvent[source=the event from @Autowired-ApplicationEventPublisher]
MyApplicationListener - 接收到 Spring 事件:com.huajie.thinking.in.spring.event.MySpringEvent[source=the event from @Autowired-ApplicationContext]

事件发布的顺序

  • 1.ApplicationEventPublisherAware
  • 2.ApplicationContextAware
  • 3.@Autowired-ApplicationEventPublisher
  • 4.@Autowired-ApplicationContext

后面两个的发布顺序和编码顺序相关,而前面两个的顺序和以下源码相关,回调接口的顺序决定了事件发布的顺序

AbstractApplicationContext#refresh

  • AbstractApplicationContext#prepareBeanFactory
    • ApplicationContextAwareProcessor#invokeAwareInterfaces
	private void invokeAwareInterfaces(Object bean) {
		if (bean instanceof EnvironmentAware) {
			((EnvironmentAware) bean).setEnvironment(this.applicationContext.getEnvironment());
		}
		if (bean instanceof EmbeddedValueResolverAware) {
			((EmbeddedValueResolverAware) bean).setEmbeddedValueResolver(this.embeddedValueResolver);
		}
		if (bean instanceof ResourceLoaderAware) {
			((ResourceLoaderAware) bean).setResourceLoader(this.applicationContext);
		}
		if (bean instanceof ApplicationEventPublisherAware) {
			((ApplicationEventPublisherAware) bean).setApplicationEventPublisher(this.applicationContext);
		}
		if (bean instanceof MessageSourceAware) {
			((MessageSourceAware) bean).setMessageSource(this.applicationContext);
		}
		if (bean instanceof ApplicationContextAware) {
			((ApplicationContextAware) bean).setApplicationContext(this.applicationContext);
		}
	}

15.依赖查找 ApplicationEventMulticaster

依赖查找的方式

  • Bean 名称:“applicationEventMulticaster”

  • Bean 类型:org.springframework.context.event.ApplicationEventMulticaster

接口分析

public interface ApplicationEventMulticaster {

	void addApplicationListener(ApplicationListener<?> listener);

	void addApplicationListenerBean(String listenerBeanName);

	void removeApplicationListener(ApplicationListener<?> listener);

	void removeApplicationListenerBean(String listenerBeanName);
    
	void removeAllListeners();

	void multicastEvent(ApplicationEvent event);

	void multicastEvent(ApplicationEvent event, @Nullable ResolvableType eventType);

}

和 ApplicationEventPublisher 接口相比

  • 多了添加 ApplicationListener 和 删除 ApplicationListener 的相关接口

  • 而发布事件接口 multicastEvent(ApplicationEvent),广播事件,相当于 发布-订阅 这种模式,适用一对多的场景

初始化方法同样是在应用上下启动的过程中

AbstractApplicationContext#refresh

  • AbstractApplicationContext#initApplicationEventMulticaster

源码分为两部分

  • 一是判断 ApplicationEventMulticaster Bean 是否存在当前上下文中,如果存在就通过依赖查找的方式找到该对象赋值给当前的 applicationEventMulticaster

  • 二是不存在ApplicationEventMulticaster Bean,那么创建一个默认的 ApplicationEventMulticaster,通过 registerSingleton 注册到当前的应用上下文中

	protected void initApplicationEventMulticaster() {
		ConfigurableListableBeanFactory beanFactory = getBeanFactory();
        // 如果当前上下文存在 “applicationEventMulticaster”
		if (beanFactory.containsLocalBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME)) {
            // 通过名称+类型的方式依赖查找
			this.applicationEventMulticaster =
					beanFactory.getBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, ApplicationEventMulticaster.class);
			if (logger.isTraceEnabled()) {
				logger.trace("Using ApplicationEventMulticaster [" + this.applicationEventMulticaster + "]");
			}
		}
		else {
            // 新建 applicationEventMulticaster 对象
			this.applicationEventMulticaster = new SimpleApplicationEventMulticaster(beanFactory);
            // 注册到当前上下文中
			beanFactory.registerSingleton(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, this.applicationEventMulticaster);
			if (logger.isTraceEnabled()) {
				logger.trace("No '" + APPLICATION_EVENT_MULTICASTER_BEAN_NAME + "' bean, using " +
						"[" + this.applicationEventMulticaster.getClass().getSimpleName() + "]");
			}
		}
	}

16.ApplicationEventPublisher 底层实现

16.1 底层实现

  • 接口:org.springframework.context.event.ApplicationEventMulticaster
  • 抽象类:org.springframework.context.event.AbstractApplicationEventMulticaster
  • 实现类:org.springframework.context.event.SimpleApplicationEventMulticaster

16.2 源码分析

其实 ApplicationEventPublisher 发布事件功能底层调用的是 ApplicationEventMulticaster 的方法。

基于两点可以说明

1.ApplicationContext 继承了 ApplicationEventPublisher,所以 ApplicationContext 的子类其实就是 ApplicationEventPublisher

2.AbstractApplicationContext#publishEvent() 发布事件方法 403 行

		// Multicast right now if possible - or lazily once the multicaster is initialized
		if (this.earlyApplicationEvents != null) {
			this.earlyApplicationEvents.add(applicationEvent);
		}
		else {
			getApplicationEventMulticaster().multicastEvent(applicationEvent, eventType);
		}

从名称就可以看到实际上是获取的 ApplicationEventMulticaster 对象,再调用的 multicastEvent 来发布事件。

getApplicationEventMulticaster() 代码如下:

	ApplicationEventMulticaster getApplicationEventMulticaster() throws IllegalStateException {
		if (this.applicationEventMulticaster == null) {
			throw new IllegalStateException("ApplicationEventMulticaster not initialized - " +
					"call 'refresh' before multicasting events via the context: " + this);
		}
		return this.applicationEventMulticaster;
	}

16.3 earlyApplicationEvents 是用来做什么的?

这里还有一个细节 earlyApplicationEvents 这个是用来做什么的?看名称意思是一个早期的应用事件。

其实这个判断主要是为了解决一个Spring 启动过程中生命周期的一个 bug,防止在 ApplicationEventMulticaster 初始化之前使用这个对象.。

我们可以用 InjectionApplicationEventPublisherDemo 这个示例稍微修改一下实现一个 BeanPostProcessor 接口如下:

public class InjectionApplicationEventPublisherDemo implements ApplicationEventPublisherAware, ApplicationContextAware, BeanPostProcessor {...}

启动代码进行调试

相关的代码在 AbstractApplicationContext#refresh 启动应用上下文的过程中

registerBeanPostProcessors、initApplicationEventMulticaster 、registerListeners 这三个步骤中
在这里插入图片描述
假设某个类在 registerBeanPostProcessors 这个生命周期执行的过程中调用了 ApplicationEventMulticaster 对象,但是这个对象还没有初始化。

这个时候调用发布事件方法会进入到这个逻辑中
在这里插入图片描述
将事件添加到 earlyApplicationEvents 这个集合中,而且会跳过 getApplicationEventMulticaster().multicastEvent(applicationEvent, eventType); 这段逻辑,因为此时 ApplicationEventMulticaster 还没有初始化。

然后等 initApplicationEventMulticaster() 初始化 ApplicationEventMulticaster 对象之后,在 registerListeners() 方法中重新发布这个事件。
在这里插入图片描述

17.同步和异步 Spring 事件广播

17.1 基于实现类 - org.springframework.context.event.SimpleApplicationEventMulticaster

  • 模式切换:SimpleApplicationEventMulticaster#setTaskExecutor
    • 默认模式:同步
    • 异步模式:如 java.util.concurrent.ThreadPoolExecutor
  • 设计缺陷:非基于接口契约编程

代码示例

public class AsyncEventHandlerDemo {
    public static void main(String[] args) {
        GenericApplicationContext context = new GenericApplicationContext();

        // 1.添加自定义 Spring 事件监听器
        context.addApplicationListener(new MyApplicationListener());

        // 2.启动 Spring 应用上下文
        context.refresh();

        // 依赖查找 ApplicationEventMulticaster

        ApplicationEventMulticaster applicationEventMulticaster = context.getBean(AbstractApplicationContext.APPLICATION_EVENT_MULTICASTER_BEAN_NAME,
                ApplicationEventMulticaster.class);
        if (applicationEventMulticaster instanceof SimpleApplicationEventMulticaster) {
            SimpleApplicationEventMulticaster simpleApplicationEventMulticaster = (SimpleApplicationEventMulticaster) applicationEventMulticaster;
            ExecutorService taskExecutor = Executors.newSingleThreadExecutor(
                    new CustomizableThreadFactory("my-spring-event-thread-pool")
            );
            // 同步执行 -> 异步执行
            simpleApplicationEventMulticaster.setTaskExecutor(taskExecutor);
            // 添加应用上下文关闭的 listener 监听事件 关闭线程池
            applicationEventMulticaster.addApplicationListener(new ApplicationListener<ContextClosedEvent>() {
                @Override
                public void onApplicationEvent(ContextClosedEvent event) {
                    if (!taskExecutor.isShutdown()) {
                        taskExecutor.shutdown();
                    }
                }
            });
        }

        // 3.发布自定义 Spring 事件
        context.publishEvent(new MySpringEvent("hello,world"));

        // 4.关闭应用上下文
        context.close();
    }
}

执行结果:

MyApplicationListener - 接收到 Spring 事件:[线程:my-spring-event-thread-pool1]:com.huajie.thinking.in.spring.event.MySpringEvent[source=hello,world]

17.2 基于注解 - @org.springframework.context.event.EventListener

  • 模式切换
    • 默认模式:同步
    • 异步模式:标准 @org.springframework.scheduling.annotation.Async
  • 实现限制:无法直接实现同步/异步动态切换

代码示例:

@EnableAsync
public class AnnotatedAsyncEventHandlerDemo {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();

        // 1.注册当前类作为 Configuration Class
        context.register(AnnotatedAsyncEventHandlerDemo.class);

        // 2.启动 Spring 应用上下文
        context.refresh();

        // 3.发布自定义 Spring 事件
        context.publishEvent(new MySpringEvent("hello,world"));

        // 4.关闭应用上下文
        context.close();
    }

    @EventListener
    @Async
    public void onApplicationEvent(MySpringEvent event) {
        System.out.println(String.format("基于 @EventListener - 接收到 Spring 事件:[线程:%s]:%s\n",
                Thread.currentThread().getName(), event));
    }
}

执行结果:

六月 26, 2020 9:42:17 上午 org.springframework.aop.interceptor.AsyncExecutionAspectSupport getDefaultExecutor
信息: No task executor bean found for async processing: no bean of type TaskExecutor and no bean named 'taskExecutor' either
基于 @EventListener - 接收到 Spring 事件:[线程:SimpleAsyncTaskExecutor-1]:com.huajie.thinking.in.spring.event.MySpringEvent[source=hello,world]

可以看到线程已经不是 main 线程,被切换成 SimpleAsyncTaskExecutor-1。但是和上面基于实现类的例子有些不同,看控制台的信息没有一个 name 为 ‘taskExecutor’ 的 Bean。

在上面示例的基础上,我们定义一个 name = ‘taskExecutor’ 的 Spring Bean

    @Bean
    public Executor taskExecutor(){
        return Executors.newSingleThreadExecutor(
                new CustomizableThreadFactory("my-spring-event-thread-pool")
        );
    }

执行结果:

基于 @EventListener - 接收到 Spring 事件:[线程:my-spring-event-thread-pool1]:com.huajie.thinking.in.spring.event.MySpringEvent[source=hello,world]

18.Spring 4.1 事件异常处理

Spring 3.0 错误处理接口 - org.springframework.util.ErrorHandler

使用场景

  • Spring 事件(Events)
    • SimpleApplicationEventMulticaster Spring 4.1 开始支持
  • Spring 本地调度(Scheduling)
    • org.springframework.scheduling.concurrent.ConcurrentTaskScheduler
    • org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler

示例

我们在 AsyncEventHandlerDemo 的基础之上进行改造

  • 设置一个自定义的 ErrorHandler
simpleApplicationEventMulticaster.setErrorHandler(e -> {
  System.out.println("当 Spring 事件异常时,原因:" + e.getMessage());
});
  • 故意抛出异常
        // 故意抛出异常
        context.addApplicationListener(new ApplicationListener<MySpringEvent>() {
            @Override
            public void onApplicationEvent(MySpringEvent event) {
                throw new RuntimeException("故意抛出异常");
            }
        });

执行结果:

当 Spring 事件异常时,原因:故意抛出异常

19.Spring 事件/监听器实现原理

核心类 - org.springframework.context.event.SimpleApplicationEventMulticaster

  • 设计模式:观察者模式扩展
    • 被观察者 - org.springframework.context.ApplicationListener
      • API 添加
      • 依赖查找
    • 通知对象 - org.springframework.context.ApplicationEvent
  • 执行模式:同步/异步
  • 异常处理:org.springframework.util.ErrorHandler
  • 泛型处理:org.springframework.core.ResolvableType

源码分析

我们来分析下整个事件发布和监听的过程,看看如何通过具体的事件类型来过滤和监听。

首先我们看看核心类 SimpleApplicationEventMulticaster 继承了 AbstractApplicationEventMulticaster

  • org.springframework.context.event.AbstractApplicationEventMulticaster 部分代码如下:
public abstract class AbstractApplicationEventMulticaster
		implements ApplicationEventMulticaster, BeanClassLoaderAware, BeanFactoryAware {

	private final ListenerRetriever defaultRetriever = new ListenerRetriever(false);

	final Map<ListenerCacheKey, ListenerRetriever> retrieverCache = new ConcurrentHashMap<>(64);
    ...

最关键的设计在于 retrieverCache 这个缓存设计,我们分别看看这个 Map 中的 Key 和 Value 是什么。

Key:ListenerCacheKey 中有一个 eventType 表示事件的类型

	private static final class ListenerCacheKey implements Comparable<ListenerCacheKey> {

		private final ResolvableType eventType;

		@Nullable
		private final Class<?> sourceType;
        ...

Value:ListenerRetriever 数据结构

	private class ListenerRetriever {

		public final Set<ApplicationListener<?>> applicationListeners = new LinkedHashSet<>();

		public final Set<String> applicationListenerBeans = new LinkedHashSet<>();
        ...

从源代码中可以看到 ListenerRetriever 内部也是集合对象,表示一个 ListenerRetriever 包含多个 ApplicationListener 对象。

联系起来,我们可以猜想这个 retrieverCache Map 对象存储的应该是事件类型和监听的对应关系集合。

源码调试

我们用之前的一个例子来进行调试

public class CustomizedSpringEventDemo {
    public static void main(String[] args) {
        GenericApplicationContext context = new GenericApplicationContext();

        // 1.添加自定义 Spring 事件监听器
        context.addApplicationListener(new MyApplicationListener());

        // 2.启动 Spring 应用上下文
        context.refresh();

        // 3.发布自定义 Spring 事件
        context.publishEvent(new MySpringEvent("hello,world"));

        // 4.关闭应用上下文
        context.close();
    }
}

将断点打在 AbstractApplicationEventMulticaster#addApplicationListener 中,当示例执行到第 6 行时,会进入到此方法中,可以看到此时的参数就是我们自定义的 MyApplicationListener,将 MyApplicationListener 加入到 defaultRetriever 集合中。
在这里插入图片描述
继续往下走,然后在应用上下启动过程中 AbstractApplicationContext#refresh 方法调用 initApplicationEventMulticaster 方法来初始化 ApplicationEventMulticaster 对象,之后到 finishRefresh() 方法中,publishEvent(new ContextRefreshedEvent(this)); 发布 ContextRefreshedEvent 事件,我们将断点打在 AbstractApplicationEventMulticaster#getApplicationListeners() 178 行
在这里插入图片描述
可以看到将 eventType=ContextRefreshedEvent 封装成一个 ListenerCacheKey,在 198 行 this.retrieverCache.put(cacheKey, retriever); 将 key 和对应的 listener 放到 retrieverCache 缓存中。
在这里插入图片描述
同理,当我们发布自定义事件 MySpringEvent 的时候也经历了同样的过程,最后 retrieverCache 缓存的结果如下:
在这里插入图片描述
存放了两个对象,而对象的 Key 值分别是 MySpringEvent 和 ContextRefreshedEvent。

发布事件调用的方法如下:

	public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
		ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
		Executor executor = getTaskExecutor();
		for (ApplicationListener<?> listener : getApplicationListeners(event, type)) {
			if (executor != null) {
				executor.execute(() -> invokeListener(listener, event));
			}
			else {
				invokeListener(listener, event);
			}
		}
	}

当程序执行到 3.发布自定义 Spring 事件 的时候,此时getApplicationListeners(event, type) 会做两件事

  • 将 MySpringEvent 放到 retrieverCache 缓存中上面已经分析过了
  • 根据 MySpringEvent 类型返回 MyApplicationListener 对象

在这里插入图片描述
继续往下走会调用 SimpleApplicationEventMulticaster#invokeListener 执行监听器中的 onApplicationEvent 方法
在这里插入图片描述
最后执行我们自定义 MyApplicationListener 中的方法
在这里插入图片描述
如何根据 MySpringEvent 事件类型来匹配对应的 ApplicationListener?

AbstractApplicationEventMulticaster#retrieveApplicationListeners 中的关键代码如下:

		for (ApplicationListener<?> listener : listeners) {
			if (supportsEvent(listener, eventType, sourceType)) {
				if (retriever != null) {
					retriever.applicationListeners.add(listener);
				}
				allListeners.add(listener);
			}
		}

在这里插入图片描述

20.Spring Boot 事件

以下的事件继承 SpringApplicationEvent ,而 SpringApplicationEvent 继承 ApplicationEvent,所以 Spring Boot 的事件源自于 Spring 中的事件。

事件类型发生时机
ApplicationStartingEvent当 Spring Boot 应用启动时
ApplicationStartedEvent当 Spring Boot 应用已启动时
ApplicationEnviromentPreparedEvent当 Spring Boot Enviroment 实例已经准备时
ApplicationPreparedEvent当 Spring Boot 应用预备时
ApplicationReadyEvent当 Spring Boot 应用完全可用时
ApplicationFailedEvent当 Spring Boot 应用启动失败时

21.Spring Cloud 事件

同样的 Spring Cloud 相关的事件也都是继承 ApplicationEvent,所以 Spring Cloud 的事件也源自于 Spring 中的事件。

事件类型发生时机
EnvironmentChangeEvent当 Environment 示例配置属性发生变化时
HeartbeatEvent当 DiscoveryClient 客户端发送心跳时
InstancePreRegisteredEvent当服务实例注册前
InstanceRegisteredEvent当服务实例注册后
RefreshEvent当 RefreshEndpoint 被调用时
RefreshScopeRefreshedEvent当 Refresh Scope Bean 刷新后

22.面试

22.1 Spring 事件核心接口/组件?

  • Spring 事件 - org.springframework.context.ApplicationEvent

  • Spring 事件监听器 - org.springframework.context.ApplicationListener

  • Spring 事件发布器 - org.springframework.context.ApplicationEventPublisher

  • Spring 事件广播器 - org.springframework.context.event.ApplicationEventMulticaster

22.2 Spring 同步和异步事件处理的使用场景?

  • Spring 同步事件 - 绝大多数 Spring Event 使用场景,如 ContextRefresbEvent
  • Spring 异步事件 - 主要 @EventListener 与 @Async 配合,实现异步处理,不阻塞主线程,比如长事件的数据计算任务等。不要轻易调整 SimpleApplicationEventMulticaster 中关联的 taskExecutor 对象,除非使用者非常了解 Spring 事件机制,否认容易出现异常行为。

23.参考

  • 极客时间-小马哥《小马哥讲Spring核心编程思想》
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值