架构(二)Spring异步发布监听的使用

一、引言

        开发过程中一般使用mq进行功能解耦,但是当主业务上开启许多分支功能,不可能为每一个分支功能开启各自的mq,因此使用Spring的ApplicationEventPublisher进行发布监听。

        同时大多数情况下分支功能与主业务需要解耦,因此发布的事件需要异步处理。

二、工具

1、线程池

        作者使用的拒绝策略是主线程执行,一般对于线程池容量都会配置的高于平时的qps,极其少的情况下流量突发才会使用主线程。


@Configuration
public class ThreadPoolConfig {

    @Resource
    private MeterRegistry meterRegistry;

    private String commonThreadName = "common-executor-service";


    /**
     * 公共线程池
     *
     * @return
     */
    @Bean(name = "commonExecutorService")
    public ExecutorService getCommonExecutorService() {
        return getUseMainThreadExecutorService(getCommonThreadPoolProperties(), new BaseThreadFactory(ThreadPoolEnum.COMMON), commonThreadName);
    }

    /**
     * 公共线程池参数
     *
     * @return
     */
    @Bean
    @ConfigurationProperties(prefix = "common.thread-pool")
    public ThreadPoolProperties getCommonThreadPoolProperties() {
        return new ThreadPoolProperties();
    }


    //当队列与线程池塞满之后使用主线程执行
    private ExecutorService getUseMainThreadExecutorService(ThreadPoolProperties poolProperties, ThreadFactory factory, String threadName) {
        ThreadPoolExecutor executorService = new ThreadPoolExecutor(poolProperties.getCorePoolSize(), poolProperties.getMaxPoolSize(),
                poolProperties.getKeepAliveSeconds(), TimeUnit.SECONDS, new ArrayBlockingQueue<>(poolProperties.getMaxQueueSize()),
                factory, new ThreadPoolExecutor.CallerRunsPolicy());
        ExecutorServiceMetrics.monitor(meterRegistry, executorService, threadName);
        return executorService;
    }



}

2、事件

        一个普通的实体类就可以作为事件,发布这个实体类事件,所有的监听者都会收到并进行处理。

@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class ShopOnlineDTO extends BaseEntity {

    private Long shopId;

    private Integer yearTotalOnlineDay;

}

三、使用

1、发布者

        通过ApplicationEventPublisher将事件发布出去。


    @Resource
    private ApplicationEventPublisher publisher;

    public void shopOnline(ShopDTO shopDTO) {
        //门店业务处理
        *******
        *******


        //计算门店在线天数
        publisher.publishEvent(ShopOnlineDTO
                .builder()
                .shopId(billReportDayDTO.getShopId())
                .build());
    }

2、监听者

        通过@EventListener表明这是一个监听者,spring会将它加载到监听者中,并通过监听实体进行匹配。

        通过@Async("commonExecutorService")表明这是异步处理,并且需要注入到线程池中执行。

        由于需要控制并发,需要加分布式锁,这里使用的是作者自己封装的分布式代理锁,感兴趣的同学可以看一下作者的另外一篇博客,对于作者封装的分布式代理锁有详细解读。Redis分布式代理锁的两种实现_tingmailang的博客-CSDN博客


    @Async("commonExecutorService")
    @EventListener
    public void shopMath(ShopOnlineDTO shopOnlineDTO) {
        //在aop开启了自动清除
        LockUtil.set(shopReportDayDTO.getShopId().toString());
        operateShopOnlineMath.shopMath(shopReportDayDTO);
    }

3、业务处理

    @RedisLock(key = RedisConsts.REPORT_SHOP_DAY_MATH_LOCK, atuoRemove = true)
    public void shopMath(ShopReportDayDTO shopReportDayDTO) {
        //取当年生成的在线单据
        LocalDate firstDayOfYear = LocalDate.now().with(TemporalAdjusters.firstDayOfYear());
        List<ShopOnlineBill> shopOnlineBillList = shopOnlineBillService.getShopYearBill(shopReportDayDTO.getShopId(), firstDayOfYear);
        int day = shopOnlineBillList 
                .stream()
                .mapToInt(ShopOnlineBill::getActualOnlineDay)
                .sum();
        if (day == shopReportDayDTO.getYearTotalReportDay()) {
            return;
        }
        ShopDetail update = new ShopDetail();
        update.setId(shopReportDayDTO.getId());
        update.setYearTotalOnlineDay(day);
        log.info("更新门店在线天数:{}",update);
        ShopDetailService.update(update);
    }

四、原理分析

        进入publishEvent方法准备事件发布,获取事件类型,将包装好的事件与对应类型传入applicationEventMulticaster

protected void publishEvent(Object event, ResolvableType eventType) {
   Assert.notNull(event, "Event must not be null");
   if (logger.isTraceEnabled()) {
      logger.trace("Publishing event in " + getDisplayName() + ": " + event);
   }
 
   // Decorate event as an ApplicationEvent if necessary
   ApplicationEvent applicationEvent;
   if (event instanceof ApplicationEvent) {
      applicationEvent = (ApplicationEvent) event;
   }
   else {
      applicationEvent = new PayloadApplicationEvent<Object>(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);
      }
   }
}

        multicastEvent方法获取匹配事件类型的监听者,遍历匹配的监听者并唤醒,下面先看一下getApplicationListeners如何匹配监听者

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

        getApplicationListeners根据事件类型和类包装缓存键,找到事件类型对应的缓存工厂,返回工厂中的监听者

protected Collection<ApplicationListener<?>> getApplicationListeners(
      ApplicationEvent event, ResolvableType eventType) {
 
   Object source = event.getSource();
   Class<?> sourceType = (source != null ? source.getClass() : null);
   ListenerCacheKey cacheKey = new ListenerCacheKey(eventType, sourceType);
 
   // 找到事件类型对应的监听工厂
   ListenerRetriever retriever = this.retrieverCache.get(cacheKey);
   if (retriever != null) {
       //返回工厂中的监听者
      return retriever.getApplicationListeners();
   }
 
   if (this.beanClassLoader == null ||
         (ClassUtils.isCacheSafe(event.getClass(), this.beanClassLoader) &&
               (sourceType == null || ClassUtils.isCacheSafe(sourceType, this.beanClassLoader)))) {
      // Fully synchronized building and caching of a ListenerRetriever
      synchronized (this.retrievalMutex) {
         retriever = this.retrieverCache.get(cacheKey);
         if (retriever != null) {
            return retriever.getApplicationListeners();
         }
         retriever = new ListenerRetriever(true);
         Collection<ApplicationListener<?>> listeners =
               retrieveApplicationListeners(eventType, sourceType, retriever);
         this.retrieverCache.put(cacheKey, retriever);
         return listeners;
      }
   }
   else {
      // No ListenerRetriever caching -> no synchronization necessary
      return retrieveApplicationListeners(eventType, sourceType, null);
   }
}

        invokeListener唤醒监听者其实是进入onApplicationEvent方法

protected void invokeListener(ApplicationListener listener, ApplicationEvent event) {
   ErrorHandler errorHandler = getErrorHandler();
   if (errorHandler != null) {
      try {
         listener.onApplicationEvent(event);
      }
      catch (Throwable err) {
         errorHandler.handleError(err);
      }
   }
   else {
      try {
         listener.onApplicationEvent(event);
      }
      catch (ClassCastException ex) {
         String msg = ex.getMessage();
         if (msg == null || msg.startsWith(event.getClass().getName())) {
            Log logger = LogFactory.getLog(getClass());
            if (logger.isDebugEnabled()) {
               logger.debug("Non-matching event type for listener: " + listener, ex);
            }
         }
         else {
            throw ex;
         }
      }
   }
}

        doInvoke根据beanName从applicationContext获取bean,进入增强方法

protected Object doInvoke(Object... args) {
  根据beanName从applicationContext获取bean
   Object bean = getTargetBean();
   ReflectionUtils.makeAccessible(this.bridgedMethod);
   try {
      进入cglib的增强方法
      return this.bridgedMethod.invoke(bean, args);
   }
   catch (IllegalArgumentException ex) {
      assertTargetBean(this.bridgedMethod, bean, args);
      throw new IllegalStateException(getInvocationErrorMessage(bean, ex.getMessage(), args), ex);
   }
   catch (IllegalAccessException ex) {
      throw new IllegalStateException(getInvocationErrorMessage(bean, ex.getMessage(), args), ex);
   }
   catch (InvocationTargetException ex) {
      // Throw underlying exception
      Throwable targetException = ex.getTargetException();
      if (targetException instanceof RuntimeException) {
         throw (RuntimeException) targetException;
      }
      else {
         String msg = getInvocationErrorMessage(bean, "Failed to invoke event listener method", args);
         throw new UndeclaredThrowableException(targetException, msg);
      }
   }
}

        intercept获取拦截器链,此处有@async方法定义的异步线程,通过cglib生成拦截方法

public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
   Object oldProxy = null;
   boolean setProxyContext = false;
   Class<?> targetClass = null;
   Object target = null;
   try {
      if (this.advised.exposeProxy) {
         oldProxy = AopContext.setCurrentProxy(proxy);
         setProxyContext = true;
      }
      target = getTarget();
      if (target != null) {
         targetClass = target.getClass();
      }
      获取拦截器链
      List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
      Object retVal;
      if (chain.isEmpty() && Modifier.isPublic(method.getModifiers())) {
         Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);
         retVal = methodProxy.invoke(target, argsToUse);
      }
      else {
         // 生成cglib增强方法
         retVal = new CglibMethodInvocation(proxy, target, method, args, targetClass, chain, methodProxy).proceed();
      }
      retVal = processReturnType(proxy, target, method, retVal);
      return retVal;
   }
   finally {
      if (target != null) {
         releaseTarget(target);
      }
      if (setProxyContext) {
         // Restore old proxy.
         AopContext.setCurrentProxy(oldProxy);
      }
   }
}

        invokeJoinpoint进入代理类

public Object proceed() throws Throwable {
   if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) {
      return invokeJoinpoint();
   }
 
   Object interceptorOrInterceptionAdvice =
         this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);
   if (interceptorOrInterceptionAdvice instanceof InterceptorAndDynamicMethodMatcher) {
      InterceptorAndDynamicMethodMatcher dm =
            (InterceptorAndDynamicMethodMatcher) interceptorOrInterceptionAdvice;
      if (dm.methodMatcher.matches(this.method, this.targetClass, this.arguments)) {
         return dm.interceptor.invoke(this);
      }
      else {
         return proceed();
      }
   }
   else {
      return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this);
   }
}
 
 
 
 
protected Object invokeJoinpoint() throws Throwable {
   if (this.publicMethod) {
      通过反射进入代理类
      return this.methodProxy.invoke(this.target, this.arguments);
   }
   else {
      return super.invokeJoinpoint();
   }
}

        代理类通过反射进入监听者执行监听逻辑

public Object invoke(Object obj, Object[] args) throws Throwable {
    try {
        this.init();
        MethodProxy.FastClassInfo fci = this.fastClassInfo;
        return fci.f1.invoke(fci.i1, obj, args);
    } catch (InvocationTargetException var4) {
        throw var4.getTargetException();
    } catch (IllegalArgumentException var5) {
        if (this.fastClassInfo.i1 < 0) {
            throw new IllegalArgumentException("Protected method: " + this.sig1);
        } else {
            throw var5;
        }
    }
}

五、总结

        Spring的发布监听机制封装的比较完善,使用也较为简单,还有EventBus等工具也可以实现自定义的发布监听,用哪个看个人习惯吧。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

胖当当技术

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值