Spring Event事件通知机制 源码学习

小鱼儿 2021-10-13 01:57 610阅读 0赞

笔记简述
本学习笔记主要是介绍Spring中的事件通知是如何实现的,同步和异步事件通知的用法和实现细节以及Spring提供的常见的Event,如果实际开发中需要根据事件推送完成相应的功能,该如何选择Event
Spring更多可查看Spring 源码学习

目录

Spring Event事件通知机制 源码学习
1、监听者模式
2、DEMO(同步)
3、Spring实现细节
4、Spring Event
4.1 ContextRefreshedEvent
4.2 ServletRequestHandledEvent
5、异步Pushlish以及DEMO

1、监听者模式

学习spring的事件通知机制肯定要先了解监听者模式(监听者模式和观察者模式有什么区别?

监听者模式包含了一个监听者Listener与之对应的事件Event,还有一个事件发布者EventPublish,过程就是EventPublish发布一个事件,被监听者捕获到,然后执行事件相应的方法

观察者模式是一对多的模式,一个被观察者Observable和多个观察者Observer,被观察者中存储了所有的观察者对象,当被观察者接收到一个外界的消息,就会遍历广播推算消息给所有的观察者
例如日常生活中的订阅报纸,报纸老板A,现在小明和老板打招呼说我要订报纸(这个过程就相当于观察者的注册),老板A就会拿出自己的小本本记下小明,下次小王、小芳也是类似的操作,现在老板A就有了三个观察者了,然后老板会自动的把报纸送到三位的家里,突然有一天小明说不想订报纸了,老板就在自己的小本本上划掉小明的名字(观察者的取消注册),等到送报纸的时候就不再送到小明家里。

2、DEMO(同步)

事件定义

  1. public class EventDemo extends ApplicationEvent {
  2. private String message;
  3. public EventDemo(Object source, String message) {
  4. super(source);
  5. this.message = message;
  6. }
  7. public String getMessage() {
  8. return message;
  9. }
  10. }

事件监听者

  1. @Component
  2. public class EventDemoListern implements ApplicationListener<EventDemo> {
  3. @Override
  4. public void onApplicationEvent(EventDemo event) {
  5. System.out.println("receiver " + event.getMessage());
  6. }
  7. }

事件发布

  1. @Component
  2. public class EventDemoPublish {
  3. @Autowired
  4. private ApplicationEventPublisher applicationEventPublisher;
  5. public void publish(String message){
  6. EventDemo demo = new EventDemo(this, message);
  7. applicationEventPublisher.publishEvent(demo);
  8. }
  9. }

最后获取到EventDemoPublish这个bean,直接publish就可以完成操作了。

3、Spring实现细节

在spring中由于类的细节太多,参数方法也非常的多,不太建议通篇的一个一个看,优先关注我们关注的点,然后打断点的方式具体了解需要的参数和必备的方法等

AbstractApplicationContext类就有publishEvent()方法,先分析下。

  1. protected void publishEvent(Object event, ResolvableType eventType) {
  2. Assert.notNull(event, "Event must not be null");
  3. if (logger.isTraceEnabled()) {
  4. logger.trace("Publishing event in " + getDisplayName() + ": " + event);
  5. }
  6. ApplicationEvent applicationEvent;
  7. if (event instanceof ApplicationEvent) {
  8. // 如果是ApplicationEvent对象
  9. applicationEvent = (ApplicationEvent) event;
  10. }
  11. else {
  12. applicationEvent = new PayloadApplicationEvent<Object>(this, event);
  13. if (eventType == null) {
  14. eventType = ((PayloadApplicationEvent)applicationEvent).getResolvableType();
  15. }
  16. // 否则就包装为PayloadApplicationEvent时间,并获取对应的事件类型
  17. }
  18. if (this.earlyApplicationEvents != null) {
  19. //初始化时候的事件容器,默认为null的
  20. this.earlyApplicationEvents.add(applicationEvent);
  21. }
  22. else {
  23. getApplicationEventMulticaster().multicastEvent(applicationEvent, eventType);
  24. // 监听者容器?大概的就这个意思,推送消息给监听器
  25. }
  26. // 如果当前命名空间还有父亲节点,也需要给父亲推送该消息
  27. if (this.parent != null) {
  28. if (this.parent instanceof AbstractApplicationContext) {
  29. ((AbstractApplicationContext) this.parent).publishEvent(event, eventType);
  30. }
  31. else {
  32. this.parent.publishEvent(event);
  33. }
  34. }
  35. }
  36. ApplicationEventMulticaster getApplicationEventMulticaster() throws IllegalStateException {
  37. if (this.applicationEventMulticaster == null) {
  38. // 意味着肯定需要提前初始化这个监听器容器
  39. throw new IllegalStateException("ApplicationEventMulticaster not initialized - " +
  40. "call 'refresh' before multicasting events via the context: " + this);
  41. }
  42. return this.applicationEventMulticaster;
  43. }

通过分析,又回到了refresh这个核心函数中

aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy8yMDY0MTk3LWYxODNkOWJhNzU3NWFkMmEucG5nP2ltYWdlTW9ncjIvYXV0by1vcmllbnQvc3RyaXAlN0NpbWFnZVZpZXcyLzIvdy85NjIvZm9ybWF0L3dlYnA

image.png

  1. public static final String APPLICATION_EVENT_MULTICASTER_BEAN_NAME = "applicationEventMulticaster";
  2. // 初始化监听器容器
  3. protected void initApplicationEventMulticaster() {
  4. ConfigurableListableBeanFactory beanFactory = getBeanFactory();
  5. if (beanFactory.containsLocalBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME)) {
  6. // 手动实现了名称为applicationEventMulticaster的bean
  7. this.applicationEventMulticaster =
  8. beanFactory.getBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, ApplicationEventMulticaster.class);
  9. // 还必须得是ApplicationEventMulticaster类
  10. if (logger.isDebugEnabled()) {
  11. logger.debug("Using ApplicationEventMulticaster [" + this.applicationEventMulticaster + "]");
  12. }
  13. }
  14. else {
  15. // 否则就默认自定义一个名称为SimpleApplicationEventMulticaster的监听器容器
  16. this.applicationEventMulticaster = new SimpleApplicationEventMulticaster(beanFactory);
  17. beanFactory.registerSingleton(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, this.applicationEventMulticaster);
  18. if (logger.isDebugEnabled()) {
  19. logger.debug("Unable to locate ApplicationEventMulticaster with name '" +
  20. APPLICATION_EVENT_MULTICASTER_BEAN_NAME +
  21. "': using default [" + this.applicationEventMulticaster + "]");
  22. }
  23. }
  24. }
  25. private final Set<ApplicationListener<?>> applicationListeners = new LinkedHashSet<ApplicationListener<?>>();
  26. // 默认是一个空列表,而不是null
  27. // 注册监听器,并且加入到监听器容器中
  28. protected void registerListeners() {
  29. // Register statically specified listeners first.
  30. for (ApplicationListener<?> listener : getApplicationListeners()) {
  31. getApplicationEventMulticaster().addApplicationListener(listener);
  32. // 把提前存储好的监听器添加到监听器容器中
  33. }
  34. String[] listenerBeanNames = getBeanNamesForType(ApplicationListener.class, true, false);
  35. // 获取类型是ApplicationListener的beanName集合,此处不会去实例化bean
  36. for (String listenerBeanName : listenerBeanNames) {
  37. getApplicationEventMulticaster().addApplicationListenerBean(listenerBeanName);
  38. }
  39. Set<ApplicationEvent> earlyEventsToProcess = this.earlyApplicationEvents;
  40. this.earlyApplicationEvents = null;
  41. // 初始化事件为null
  42. if (earlyEventsToProcess != null) {
  43. for (ApplicationEvent earlyEvent : earlyEventsToProcess) {
  44. getApplicationEventMulticaster().multicastEvent(earlyEvent);
  45. }
  46. }
  47. }

这样就很清楚了,在spring进行refresh的时候就完成了监听器容器和监听器的初始化工作(可以很方便的注册自己需要的监听器或者自定义的监听器容器对象),只需要获取到容器就可以直接publish事件了。

4、Spring Event

前面已经说了监听器一般和具体的某一个事件绑定的(这点就和观察者模式非常不一样了),那么就来看看spring已经帮我们实现了哪些Event,具体如下图

aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy8yMDY0MTk3LTNmOTgzMmE3OTViYTk5OWIucG5nP2ltYWdlTW9ncjIvYXV0by1vcmllbnQvc3RyaXAlN0NpbWFnZVZpZXcyLzIvdy8xMDAwL2Zvcm1hdC93ZWJw

image.png

  • ApplicationContextEvent(Context…的抽象类)
  • ContextClosedEvent 生命周期关闭
  • ContextRefreshedEvent refresh完成
  • ContextStartedEvent 生命周期启动
  • ContextStoppedEvent 生命周期停止
  • PayloadApplicationEvent
  • RequestHandledEvent
  • ServletRequestHandledEvent RequestHandledEvent的子类,Spring MVC 请求完成之后推送的事件

下面就ContextRefreshedEvent和ServletRequestHandledEvent具体分析下如何使用以及其实现的原理

4.1 ContextRefreshedEvent

aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy8yMDY0MTk3LTJjMjE3YTFjZWRiZmI4OGYucG5nP2ltYWdlTW9ncjIvYXV0by1vcmllbnQvc3RyaXAlN0NpbWFnZVZpZXcyLzIvdy85MTgvZm9ybWF0L3dlYnA

image.png

在refresh结束之后推送的一个事件,这个时候大部分bean的实例化已经完成了,并且传递了this这个数据,那么如果有需要的话,可以在这个地方实现对Spring上下文数据的统计或者监控,简直不能爽歪歪,但是个人开发目前还没遇到具体的使用场景

4.2 ServletRequestHandledEvent

Spring MVC 中的事件,直接跳到DispatcherServlet类,后来到了FrameworkServlet 抽象类

aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy8yMDY0MTk3LTJhODhlMThiZWRmMzk2ZTMucG5nP2ltYWdlTW9ncjIvYXV0by1vcmllbnQvc3RyaXAlN0NpbWFnZVZpZXcyLzIvdy8xMDAwL2Zvcm1hdC93ZWJw

image.png

如果可以推送事件的话,则利用web的上下文推送事件,其中包含了一个请求的基本信息,如果需要定制化统计整个HTTP请求的情况,完全可以通过这个事件推送实现

不过这里有一点需要注意到,还有个publishEvents字段,如果需要使用,需要设置为true,如下图web.xml配置即可

aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy8yMDY0MTk3LTEzZWE3NjNkNzY5Nzk1NGYucG5nP2ltYWdlTW9ncjIvYXV0by1vcmllbnQvc3RyaXAlN0NpbWFnZVZpZXcyLzIvdy8xMDAwL2Zvcm1hdC93ZWJw

image.png

5、异步Pushlish以及DEMO

异步推送采用的是多线程的方法,具体看SimpleApplicationEventMulticaster类,也就是上面说的监听器容器

  1. public void multicastEvent(final ApplicationEvent event, ResolvableType eventType) {
  2. ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
  3. for (final ApplicationListener<?> listener : getApplicationListeners(event, type)) {
  4. // 获取符合事件类型的监听器集合
  5. Executor executor = getTaskExecutor();
  6. if (executor != null) {
  7. // 如果线程池不为null,则提交一个新任务交由线程池运行
  8. executor.execute(new Runnable() {
  9. @Override
  10. public void run() {
  11. invokeListener(listener, event);
  12. }
  13. });
  14. }
  15. else {
  16. // 否则就是同步执行
  17. invokeListener(listener, event);
  18. }
  19. }
  20. }
  21. protected void invokeListener(ApplicationListener listener, ApplicationEvent event) {
  22. ErrorHandler errorHandler = getErrorHandler();
  23. if (errorHandler != null) {
  24. // 错误处理器不为null
  25. try {
  26. listener.onApplicationEvent(event);
  27. }
  28. catch (Throwable err) {
  29. errorHandler.handleError(err);
  30. // 当推送消息出现异常,利用错误处理器去处理该错误
  31. }
  32. }
  33. else {
  34. try {
  35. listener.onApplicationEvent(event);
  36. }
  37. catch (ClassCastException ex) {
  38. String msg = ex.getMessage();
  39. if (msg == null || msg.startsWith(event.getClass().getName())) {
  40. // Possibly a lambda-defined listener which we could not resolve the generic event type for
  41. Log logger = LogFactory.getLog(getClass());
  42. if (logger.isDebugEnabled()) {
  43. logger.debug("Non-matching event type for listener: " + listener, ex);
  44. }
  45. }
  46. else {
  47. throw ex;
  48. }
  49. }
  50. }
  51. }
  • 注入一个线程池可实现异步处理
  • 使用errorHandler可以做到最后的事件处理的兜底方案

那么问题来了,如何注入一个线程池呢?往哪里注入呢?

只需要手动实现applicationEventMulticaster的bean,并且利用Set注入的方法注入了一个线程池,线程池也需要实例化,就直接使用了spring自带的简单异步任务线程池

aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy8yMDY0MTk3LTJjODkwMzVjNmU3YWQyNjAucG5nP2ltYWdlTW9ncjIvYXV0by1vcmllbnQvc3RyaXAlN0NpbWFnZVZpZXcyLzIvdy8xMDAwL2Zvcm1hdC93ZWJw

image.png

从同步改成异步,代码不需要修改,直接注入这么一个bean即可(当然了XML配置和Config配置均可)

作者:jwfy
链接:https://www.jianshu.com/p/21984b08875c
来源:简书
简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。

发表评论

表情:
评论列表 (有 0 条评论,610人围观)

还没有评论,来说两句吧...

相关阅读