深入浅出Spring/SpringBoot 事件监听机制

监听器模型

说明

事件监听机制可以理解为是一种观察者模式,有数据发布者(事件源)和数据接受者(监听器);

在Java中,事件对象都是继承java.util.EventObject对象,事件监听器都是java.util.EventListener实例;

EventObject对象不提供默认构造器,需要外部传递source参数,即用于记录并跟踪事件的来源;

Spring事件

Spring事件对象为ApplicationEvent,继承EventObject,源码如下:

public abstract class ApplicationEvent extends EventObject {

	/**
	 * Create a new ApplicationEvent.
	 * @param source the object on which the event initially occurred (never {@code null})
	 */
	public ApplicationEvent(Object source) {
		super(source);
		this.timestamp = System.currentTimeMillis();
	}

}
复制代码

Spring事件监听器为ApplicationListener,继承EventListener, 源码如下:

public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {
    void onApplicationEvent(E var1);
}
复制代码

实现Spring事件监听有两种方式

  1. 面向接口编程,实现ApplicationListener接口;
  2. 基于注解驱动,@EventListener(Spring自定义的注解);

实例:

  1. 面向接口编程,实现ApplicationListener接口:

自定义事件对象:

public class MyApplicationEvent extends ApplicationEvent {
    public MyApplicationEvent(Object source) {
        super(source);
    }
}
复制代码

自定义事件监听器:

public class MyApplicationListener implements ApplicationListener<MyApplicationEvent> {
    @Override
    public void onApplicationEvent(MyApplicationEvent event) {
        System.out.println("收到事件:" + event);
    }
}
复制代码

启动服务并发布事件:

public class ApplicationEventBootstrap {

    public static void main(String[] args) {
        AnnotationConfigApplicationContext context =
                new AnnotationConfigApplicationContext();
        // 注册自定义事件监听器
        context.addApplicationListener(new MyApplicationListener());
        // 启动上下文
        context.refresh();
        // 发布事件,事件源为Context
        context.publishEvent(new MyApplicationEvent(context));
        // 结束
        context.close();
    }
}
复制代码

运行结果:

收到事件:com.xx.MyApplicationEvent[source=org.springframework.context.annotation.AnnotationConfigApplicationContext@cb0ed20, started on Sat May 16 16:32:04 CST 2020]
复制代码

  1. 使用注解 @EventListener实现Spring事件监听:
@Component
public class MyApplicationListener2 {

    @EventListener(MyApplicationEvent.class)
    public void onEvent(MyApplicationEvent event) {
        System.out.println("收到事件:" + event);
    }
}
复制代码

启动并发布事件:

public class ApplicationEventBootstrap {

    public static void main(String[] args) {
        AnnotationConfigApplicationContext context =
                new AnnotationConfigApplicationContext();
        // 注册自定义事件监听器
        context.register(MyApplicationListener2.class);
        // 启动上下文
        context.refresh();
        // 发布事件,事件源为Context
        context.publishEvent(new MyApplicationEvent(context));
        // 结束
        context.close();
    }
}
复制代码

运行结果:

收到事件:com.xx.MyApplicationEvent[source=org.springframework.context.annotation.AnnotationConfigApplicationContext@cb0ed20, started on Sat May 16 16:32:04 CST 2020]
复制代码

通过实例可以看出,上面两种方式都可正常发布和接收事件。

实现原理

通过上面实例可以看出,context 可以发布事件,那底层是怎么发布的,让我们继续看源码:

public abstract class AbstractApplicationContext extends DefaultResourceLoader
		implements ConfigurableApplicationContext {
      protected void publishEvent(Object event, @Nullable ResolvableType eventType) {
        ...
        getApplicationEventMulticaster().multicastEvent(applicationEvent, eventType);
        ...
      }
}
复制代码

通过源码我们可以看出,事件应该是通过
ApplicationEventMulticaster发布的,我们继续看:

public class SimpleApplicationEventMulticaster extends AbstractApplicationEventMulticaster
复制代码

Spring 中事件发布都是通过
SimpleApplicationEventMulticaster来实现的

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

可以看出,如果设置了Executor则异步发送,否则同步;而且可以看出通过 resolveDefaultEventType(event) 对发布的事件类型进行了校验,这就是为什么我们可以直接使用泛型来指定我们想接收的事件对象, 比如上面的 ApplicationListener。

private void doInvokeListener(ApplicationListener listener, ApplicationEvent event) {
		try {
			listener.onApplicationEvent(event);
		
复制代码

最后就使用对应的ApplicationListener进行接收和处理就行了,那么ApplicationListener是什么时候注册的呢?

如何添加ApplicationListener?

  1. 直接添加,使用content.addApplicationListener(上面实例中有使用);
  2. 将自定义的ApplicationListener注册为一个Bean,Spring再初始化Bean之后会添加,具体代码在ApplicationListenerDetector#postProcessAfterInitialization,判断一个Bean如果是ApplicationListener,则也是使用context.addApplicationListener添加;
  3. 使用注解@EventListener,在初始化Bean之后,会在EventListenerMethodProcessor中进行处理和添加;

第三种实现的源码如下(
EventListenerMethodProcessor中):

private void processBean(final String beanName, final Class<?> targetType) {
  ....
  // 获取public 且有@EventListener的方法 
  AnnotatedElementUtils.findMergedAnnotation(method, EventListener.class));
  ... 
  ApplicationListener<?> applicationListener = factory.createApplicationListener(beanName, targetType, methodToUse);                 
  // 添加监听器
  context.addApplicationListener(applicationListener); 
}
复制代码

Spring内建事件

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

Spring Boot事件

Spring Boot事件是在Spring事件基础上进行的封装

public abstract class SpringApplicationEvent extends ApplicationEvent
复制代码

事件对象改为SpringApplicationEvent,事件源为SpringApplication(Spring事件源为Context);

底层发布事件还是使用
SimpleApplicationEventMulticaster 对象,不过有点需要说明的是,Spring Boot 1.4开始,SpringApplication和ApplicationContext使用的都是SimpleApplicationEventMulticaster实例,但是两者属于不同的对象(1.0 ~ 1.3版本是同一个对象);

事件回顾:

public class EventBootstrap {

    public static void main(String[] args) {
        new SpringApplicationBuilder(Object.class)
                .listeners(event -> {
                    System.out.println("事件对象:"
                    + event.getClass().getSimpleName()
                    + " ,事件源:" + event.getSource().getClass().getSimpleName());
                })
                .web(WebApplicationType.NONE)
                .run(args)
                .close();
    }
}
复制代码

运行结果:

事件对象:ApplicationContextInitializedEvent ,事件源:SpringApplication
事件对象:ApplicationPreparedEvent ,事件源:SpringApplication
事件对象:ContextRefreshedEvent ,事件源:AnnotationConfigApplicationContext
事件对象:ApplicationStartedEvent ,事件源:SpringApplication
事件对象:ApplicationReadyEvent ,事件源:SpringApplication
事件对象:ContextClosedEvent ,事件源:AnnotationConfigApplicationContext
复制代码

从结果可以看出,事件对象类型和事件源,以及事件发布顺序。

原文链接:https://juejin.cn/post/7050348275144065031
如果你觉的本文对你有帮助,麻烦点赞关注支持一下

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值