浅谈Spring的事件驱动机制

浅谈Spring的事件驱动机制

前言

每次去翻源码都是出于项目需要,越发觉得自己的主动性比较弱,但偶尔被逼着把源码翻一遍,也着实能收获不少。这次翻看Spring对事件处理机制的代码,主要是因为现在的项目将会话Session放在了ThreadLocal里,而异步的事件处理对线程是不共享的,为了确认这一点,将整个过程DEBUG了多遍,记录一些收获。


Spring对事件的支持
  • ApplicationEvent

    Spring的所有事件的父类,Spring还提供了一个比较好用的子类PayloadApplicationEvent,发布事件的时候只需要发布一个定义好的对象,处理的时候接收这个对象即可。

    Spring自身的大部分事件是通过SpringApplicationEvent发生的,也是ApplicationEvent的子类。

  • ApplicationEventPublisher

    Spring的ApplicationContext接口继承自ApplicationEventPublisher,发布事件方法的默认实现在AbstractApplicationContext中找到:

        /**
         * Publish the given event to all listeners.
         * @param event the event to publish (may be an {@link ApplicationEvent}
         * or a payload object to be turned into a {@link PayloadApplicationEvent})
         * @param eventType the resolved event type, if known
         * @since 4.2
         */
        protected void publishEvent(Object event, @Nullable ResolvableType eventType) {
            Assert.notNull(event, "Event must not be null");
    
            // Decorate event as an ApplicationEvent if necessary
            ApplicationEvent applicationEvent;
            if (event instanceof ApplicationEvent) {
                applicationEvent = (ApplicationEvent) event;
            }
            else {
                applicationEvent = new PayloadApplicationEvent<>(this, event);
                if (eventType == null) {
                    eventType = ((PayloadApplicationEvent) applicationEvent).getResolvableType();
                }
            }
    
            // 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); // 广播事件,从这里进入事件发布和处理机制
            }
    
            // Publish event via parent context as well...
            if (this.parent != null) {
                if (this.parent instanceof AbstractApplicationContext) {
                    ((AbstractApplicationContext) this.parent).publishEvent(event, eventType);
                }
                else {
                    this.parent.publishEvent(event);
                }
            }
        }

    简单来讲,Spring允许你发布一个ApplicationEvent的子类,或者直接发布一个对象即可,它会帮你转成一个PayloadApplicationEvent,然后发布到所有的EventListener

  • ApplicationEventMulticaster

    多路广播的作用就是将事件匹配到对应的事件监听器,然后通过监听器执行相应的处理逻辑。广播事件的代码在SimpleApplicationEventMulticaster中找到:

        /**
         * Multicast the given application event to appropriate listeners.
         * <p>If the {@code eventType} is {@code null}, a default type is built
         * based on the {@code event} instance.
         * @param event the event to multicast
         * @param eventType the type of event (can be null)
         * @since 4.2
         */
        @Override
        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);
                }
            }
        }

    getApplicationListeners方法获取事件相对应的ApplicationListener

    看到这里终于找到了异步处理机制的影子,广播的事件会先由Executor来执行,来异步处理我们的事件。

      /**
       * Set a custom executor (typically a {@link org.springframework.core.task.TaskExecutor})
       * to invoke each listener with.
       * <p>Default is equivalent to {@link org.springframework.core.task.SyncTaskExecutor},
       * executing all listeners synchronously in the calling thread.
       * <p>Consider specifying an asynchronous task executor here to not block the
       * caller until all listeners have been executed. However, note that asynchronous
       * execution will not participate in the caller's thread context (class loader,
       * transaction association) unless the TaskExecutor explicitly supports this.
       * @see org.springframework.core.task.SyncTaskExecutor
       * @see org.springframework.core.task.SimpleAsyncTaskExecutor
       */
      public void setTaskExecutor(@Nullable Executor taskExecutor) {
          this.taskExecutor = taskExecutor;
      }

    但是实际上,这只是事件发布的异步机制,这和我想说的的事件异步处理机制还不是一回事儿。实际上,我们自定义发布的事件走到这里,executor是没有的(可能需要其它的配置),发布的过程完全是在一个线程中同步完成的,直到invokeListener方法进入EventListeneronApplicationEvent方法。也就是说真正的异步处理并不是在这里,我们后面继续这个话题。

  • ApplicationListener

    这个类是事件监听执行器的父类,很多Spring的内部事件会通过这个类的子类去完成很多事件驱动的操作,比如:BootstrapApplicationListenerConfigFileApplicationListener等。

    而我们系统在使用事件的时候,Spring会为我们new一个ApplicationListenerMethodAdapter的子类,这个子类是为适配@EventListener注解的事件处理机制的实现。

    这个类在调用onApplicationEvnet方法的时候,会跳到这里的核心方法doInvoke方法中去,通过反射机制去执行处理方法。

      /**
       * Invoke the event listener method with the given argument values.
       */
      @Nullable
      protected Object doInvoke(Object... args) {
          Object bean = getTargetBean();
          ReflectionUtils.makeAccessible(this.method);
          try {
              return this.method.invoke(bean, args);
          }
          catch (IllegalArgumentException ex) {
              // Exception handle, omitted
          }
      }

    ApplicationListenerMethodAdapter中注册了包含@EventListener注解的所有方法和类。

关于异步
  • @Async

    这是异步方法的统一注解,可以加在方法或类上。具体的实现是通过一个Interceptor拦截,获取异步执行的Executor(可指定),实现异步线程处理。

    注意:如果不加这个注解,且ApplicationEventMulticatser也没有配置异步执行器,事件监听处理的过程就是同步的!

  • AsyncConfigurer

    通过实现AsyncConfigurergetAsyncExecutor方法可以指定自定义异步方法的执行器,要生效还需要在该配置上加@EnableAsync注解来开启异步功能。

  • TaskDecorator

    任务装饰器,可以对执行器进行修饰,在任务执行前后做一些其余的操作。

      /**
       * Decorate the given {@code Runnable}, returning a potentially wrapped
       * {@code Runnable} for actual execution.
       * @param runnable the original {@code Runnable}
       * @return the decorated {@code Runnable}
       */
      Runnable decorate(Runnable runnable);
对Spring事件驱动机制的思考

事件驱动的设计是利用了观察者模式的设计方法,ApplicationListener是观察者,ApplicationEvent是被观察者,ApplicationEventPublisher是主题发布者。观察者模式只是一种设计方法,它并没有强制要求同步还是异步,只是这样地设计可以很好地兼容异步方案的设计。

从源码的角度来看,有两个地方嵌入了异步地设计:

  1. 广播事件,交由监听器处理的时候;
  2. 监听器接收事件后,实际交由AOP代理去执行事件处理逻辑的时候;

明白了这一点,就可以有针对性地去设计我们自己的系统,避免异步线程不能共享线程本地变量带来地问题。

设计方案

  1. 通过让所有自定义事件继承自一个包含上下文的超类SessionEvent,然后定义一个包装类SessionEventPublisher,内部调用ApplicationEventPublisher来发布SessionEvent,在发布前后做一些上下文嵌入的操作。

    @Component
    public class SessionEventPublisher {
        private final ApplicationEventPublisher publisher;
    
        public SessionEventPublisher(ApplicationEventPublisher publisher) {
            this.publisher = publisher;
        }
    
        public void publishEvent(SessionEvent event) {
            // 设置上下文的 Session
            event.setTokenSession(SessionContext.getCurrentSession());
            this.publisher.publishEvent(event);
        }
    }
  2. 考虑异步机制,所有的异步操作必定伴随着使用Executor调用新的线程,而Executor可以使用装饰器Decorator进行预处理和后处理,所以可以通过自定义配置异步执行的线程池调度器来实现异步上下文同步的问题。

    @EnableAsync
    @Configuration
    public class SpringAsyncConfig implements AsyncConfigurer {
        public SpringAsyncConfig() {
        }
    
        public Executor getAsyncExecutor() {
            ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
            executor.setTaskDecorator(new SessionTaskDecorator());
            executor.initialize();
            return executor;
        }
    }
    public class SessionTaskDecorator implements TaskDecorator {
        public SessionTaskDecorator() {
        }
    
        public Runnable decorate(Runnable runnable) {
            TokenSession session = SessionContext.getCurrentSession();
            return () -> {
                try {
                    SessionContext.setCurrentSession(session);
                    runnable.run();
                } finally {
                    SessionContext.clearSession();
                }
    
            };
        }
    }

    相比之下,显然第二种方案更好。Spring在设计这个问题的时候已经给了可配置的解决方案,我们在使用Spring框架的时候,应该优先想到是否可以通过自动配置来解决问题,当然,这也必须建立在对问题背后的原理和对Spring框架有足够了解的前提下。

转载于:https://www.cnblogs.com/mdmory/p/10877285.html

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值