ApplicationListener源码解读(异步通知、统一ErrorHandler)

从spring的ApplicationListener的角度了解异步机制 背景 接触新的项目的几天时间,除了传统的CRUD业务之外,项目用到的集群锁,以及事件监听广播机制等使用,使人联系其java.util.concurrent.futureTask.的使用,本节将从项目里遇到的各种业务场景中使用的广播事件来对异步线程池的处理广播事件总结,并且对这种事件的广播机制延伸的处理方案的提出改良。 如何使用异步线程池处理任务事件 笔者从事电商项目有一段时间,对于传统的电商项目的... 阅读详情

简单使用(老司机直接跳过~)

定义一个实体类

@Data@AllArgsConstructor@NoArgsConstructorpublicclassUserInfo{
​
   private Integer age;
​
   private String name;
​
}

定义一个事件,需要继承ApplicationEvent

@Getter@SetterpublicclassCustomEventextendsApplicationEvent{
​
   private UserInfo userInfo;
​
   publicCustomEvent(UserInfo userInfo){
      super(userInfo);
      this.userInfo = userInfo;
   }
​
}

定义一个监听器,需要实现ApplicationListener接口

@ComponentpublicclassCustomListenerimplementsApplicationListener<CustomEvent> {
​
   @OverridepublicvoidonApplicationEvent(@NonNull CustomEvent event){
      System.out.println("CustomListener接收到事件~");
      System.out.println(JSONObject.toJSONString(event.getUserInfo()));
   }
​
}

测试

@SpringBootTestpublicclassListenerTest{
​
  @Resourceprivate ApplicationContext applicationContext;
​
  @Testpublicvoidtest(){
    UserInfo userInfo = new UserInfo(1, "1");
    // 发布事件
    applicationContext.publishEvent(new CustomEvent(userInfo));
  }
​
}

可见,监听器成功监听并处理了我们发布的事件~


源码分析

通过上面的案例,我们了解到,发布事件是调用的publishEvent方法,所以我们直接从publishEvent开始看~

publishEvent

protectedvoidpublishEvent(Object event, @Nullable ResolvableType eventType){
   Assert.notNull(event, "Event must not be null");
​
   // 如果event是ApplicationEvent实例,则向上转型
   ApplicationEvent applicationEvent;
   if (event instanceof ApplicationEvent) {
      applicationEvent = (ApplicationEvent) event;
   }
   else {
      applicationEvent = new PayloadApplicationEvent<>(this, event);
      if (eventType == null) {
         eventType = ((PayloadApplicationEvent<?>) applicationEvent).getResolvableType();
      }
   }
​
   // 早期发布的事件,此时listener还未注册,所以先将event存起来if (this.earlyApplicationEvents != null) {
      this.earlyApplicationEvents.add(applicationEvent);
   }
   else {
      // 获取事件发布器,并进行发布
      getApplicationEventMulticaster().multicastEvent(applicationEvent, eventType);
   }
​
   // 如果父context不为空,则要通过父context再发布一次if (this.parent != null) {
      if (this.parent instanceof AbstractApplicationContext) {
         ((AbstractApplicationContext) this.parent).publishEvent(event, eventType);
      }
      else {
         this.parent.publishEvent(event);
      }
   }
}

publishEvent方法,核心逻辑在于获取时间发布器进行发布,即下面这行代码

getApplicationEventMulticaster().multicastEvent(applicationEvent, eventType);


applicationEventMulticaster(事件多播器的获取和初始化)

getApplicationEventMulticaster只是简单的获取applicationEventMulticaster,并做一下参数校验,没什么好说的。

@Nullableprivate ApplicationEventMulticaster applicationEventMulticaster;
​
ApplicationEventMulticaster getApplicationEventMulticaster()throws IllegalStateException {
   if (this.applicationEventMulticaster == null) {
      thrownew IllegalStateException("ApplicationEventMulticaster not initialized - " +
            "call 'refresh' before multicasting events via the context: " + this);
   }
   returnthis.applicationEventMulticaster;
}

重点在于,applicationEventMulticaster是怎么初始化的?

熟悉Spring源码的小伙伴们,对下面这张图肯定不会陌生~

没错,正是Spring初始化过程中非常重要的refresh方法 ,在refresh方法中,会调用initApplicationEventMulticaster方法初始化事件多播器

initApplicationEventMulticaster源码如下:

publicstaticfinal String APPLICATION_EVENT_MULTICASTER_BEAN_NAME = "applicationEventMulticaster";
​
protectedvoidinitApplicationEventMulticaster(){
   // 获取bean工厂 
   ConfigurableListableBeanFactory beanFactory = getBeanFactory();

   // 检查IOC容器中是否已经有 beanName = applicationEventMulticaster的bean了// 如果存在则直接使用已有的就行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 {
      // 如果不存在,则new一个SimpleApplicationEventMulticaster,并注册到IOC容器中this.applicationEventMulticaster = new SimpleApplicationEventMulticaster(beanFactory);

      // 作为单例Bean注册到IOC容器中
      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() + "]");
      }
   }
}

initApplicationEventMulticaster中逻辑很简单

  1. 如果IOC容器中是否已经有beanName = applicationEventMulticaster的bean了,则直接使用

  1. 如果容器里没有,则new一个SimpleApplicationEventMulticaster作为事件多播器,并注册到IOC容器中.

这里比较重点,我们可以自定义applicationEventMulticaster,这样事件发布就会走我们自定义的发布器了
关于这里,文末会给出案例

multicastEvent

ok,了解到事件多播器的获取初始化逻辑之后,我们接着继续来看事件的发布

// 获取事件发布器,并进行事件发布
getApplicationEventMulticaster().multicastEvent(applicationEvent, eventType);
publicvoidmulticastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType){
   // 解析event的类型
   ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
   // 获取线程池
   Executor executor = getTaskExecutor();
   // 通过event、eventType匹配到符合的监听器for (ApplicationListener<?> listener : getApplicationListeners(event, type)) {

      // 如果线程池不为null,则通过线程池提交任务异步发布if (executor != null) {
         executor.execute(() -> invokeListener(listener, event));
      }
      else {
         // 否则,同步发布
         invokeListener(listener, event);
      }
   }
}

multicastEvent的逻辑也很简单,我们简单总结一下

  1. 通过event和eventType匹配到所有符合的事件监听器进行发布

  1. 事件的发布可以同步也可以异步,但默认是同步

划重点了!

Spring的ApplicationListener观察者模式,默认是同步的!!!

还记得上面我们分析的,事件多播器初始化逻辑吗?

默认是new SimpleApplicationEventMulticaster(),并不会设置taskExecutor

文末会给出异步发布模式,这里就不过多介绍了~

getApplicationListeners(获取符合event的监听器)

final Map<ListenerCacheKey, CachedListenerRetriever> retrieverCache = new ConcurrentHashMap<>(64);
​
protected Collection<ApplicationListener<?>> getApplicationListeners(
      ApplicationEvent event, ResolvableType eventType) {
​
   // 获取event Class
   Object source = event.getSource();
   Class<?> sourceType = (source != null ? source.getClass() : null);

   // 将事件class和类型封装在ListenerCacheKey中,并将其作为缓存的key// 并且重写了equals方法,会去比较event的Class和类型
   ListenerCacheKey cacheKey = new ListenerCacheKey(eventType, sourceType);
​
   CachedListenerRetriever newRetriever = null;
​
   // 从缓存中获取,如果没有则新建CachedListenerRetriever// CachedListenerRetriever里会存放着符合该event的所有监听器
   CachedListenerRetriever existingRetriever = this.retrieverCache.get(cacheKey);
   if (existingRetriever == null) {
      if (this.beanClassLoader == null ||
            (ClassUtils.isCacheSafe(event.getClass(), this.beanClassLoader) &&
                  (sourceType == null || ClassUtils.isCacheSafe(sourceType, this.beanClassLoader)))) {
         newRetriever = new CachedListenerRetriever();

         // putIfAbsent 如果缓存中不存在对应key才会进行put// 会返回老的value值,所以第一次会返回null
         existingRetriever = this.retrieverCache.putIfAbsent(cacheKey, newRetriever);
         if (existingRetriever != null) {
            newRetriever = null;  // no need to populate it in retrieveApplicationListeners
         }
      }
   }
​
   if (existingRetriever != null) {
      // 如果缓存中已经存在,则直接取出该事件对应的监听器返回
      Collection<ApplicationListener<?>> result = existingRetriever.getApplicationListeners();
      if (result != null) {
         return result;
      }
   }
​
   // 解析event对应的监听器return retrieveApplicationListeners(eventType, sourceType, newRetriever);
}

上面的源码还没有涉及到匹配逻辑,只是做的一层缓存(ConcurrentHashMap)优化~

首先,会将event的Class和Type封装在ListenerCacheKey中,作为缓存的key

ListenerCacheKey中也重写了equalse方法,会比较eventType和sourceType

privatefinal ResolvableType eventType;
​
@Nullableprivatefinal Class<?> sourceType;
​
publicListenerCacheKey(ResolvableType eventType, @Nullable Class<?> sourceType){
  Assert.notNull(eventType, "Event type must not be null");
  this.eventType = eventType;
  this.sourceType = sourceType;
}
​
publicbooleanequals(@Nullable Object other){
   if (this == other) {
      returntrue;
   }
   if (!(other instanceof ListenerCacheKey)) {
      returnfalse;
   }
   ListenerCacheKey otherKey = (ListenerCacheKey) other;
   return (this.eventType.equals(otherKey.eventType) &&
         ObjectUtils.nullSafeEquals(this.sourceType, otherKey.sourceType));
}

其次就比较简单了,如果缓存中有就用缓存的,缓存中没有就新建一个CachedListenerRetriever,并放入缓存里

这里有个小细节,往缓存里添加数据用的是putIfAbsent方法,也就是key不存在才会添加数据,并且返回的是oldValue

也就是说,当事件第一次发布时,缓存里没有,putIfAbsent会返回null,即existingRetriever = null

则,下面的if判断,第一次是false,还是会走retrieveApplicationListeners

existingRetriever = this.retrieverCache.putIfAbsent(cacheKey, newRetriever);
​
if (existingRetriever != null) {
  // 如果缓存中已经存在,则直接取出该事件对应的监听器返回
  Collection<ApplicationListener<?>> result = existingRetriever.getApplicationListeners();
  if (result != null) {
    return result;
  }
}
​
// 解析event对应的监听器return retrieveApplicationListeners(eventType, sourceType, newRetriever);

retrieveApplicationListeners(监听器匹配)

privatefinal DefaultListenerRetriever defaultRetriever = new DefaultListenerRetriever();
​
private Collection<ApplicationListener<?>> retrieveApplicationListeners(
      ResolvableType eventType, @Nullable Class<?> sourceType, @NullableCachedListenerRetrieverretriever) {
​
   // 存放event对应的所有监听器实例List<ApplicationListener<?>> allListeners = new ArrayList<>();

   // retriever != null说明是第一次event匹配,需要解析
   Set<ApplicationListener<?>> filteredListeners = (retriever != null ? new LinkedHashSet<>() : null);
   // 存放event对应的监听器beanName
   Set<String> filteredListenerBeans = (retriever != null ? new LinkedHashSet<>() : null);
​
   Set<ApplicationListener<?>> listeners;
   Set<String> listenerBeans;

   // defaultRetriever存放了所有的监听器实例及beanName
   synchronized (this.defaultRetriever) {
      listeners = new LinkedHashSet<>(this.defaultRetriever.applicationListeners);
      listenerBeans = new LinkedHashSet<>(this.defaultRetriever.applicationListenerBeans);
   }
​
   // 遍历所有监听器实例,检索出event对应的监听器实例for (ApplicationListener<?> listener : listeners) {
      // 是否能作为当前event的监听器if (supportsEvent(listener, eventType, sourceType)) {
         if (retriever != null) {
            filteredListeners.add(listener);
         }
         allListeners.add(listener);
      }
   }
​
   // 遍历所有监听器beanName,检索出event对应的监听器beanNameif (!listenerBeans.isEmpty()) {
      // 获取bean工厂
      ConfigurableBeanFactory beanFactory = getBeanFactory();
      for (String listenerBeanName : listenerBeans) {
         try {
            // 是否能作为当前event的监听器if (supportsEvent(beanFactory, listenerBeanName, eventType)) {
               // 获取监听器bean
               ApplicationListener<?> listener =
                     beanFactory.getBean(listenerBeanName, ApplicationListener.class);

               // 如果listener不在改event对应的监听器集合中,但又符合该eventif (!allListeners.contains(listener) && supportsEvent(listener, eventType, sourceType)) {
                  if (retriever != null) {
                     // 这块if-else没太懂,有没有大佬指点!!!if (beanFactory.isSingleton(listenerBeanName)) {
                        filteredListeners.add(listener);
                     }
                     else {
                        filteredListenerBeans.add(listenerBeanName);
                     }
                  }
                  allListeners.add(listener);
               }
            }
            else {
               Object listener = beanFactory.getSingleton(listenerBeanName);
               if (retriever != null) {
                  filteredListeners.remove(listener);
               }
               allListeners.remove(listener);
            }
         }
         catch (NoSuchBeanDefinitionException ex) {
         }
      }
   }
​
   // 排序,根据@Order注解,或者Ordered接口定义的优先级排序,值越小优先级越大
   AnnotationAwareOrderComparator.sort(allListeners);

   if (retriever != null) {
      // 将event对应的监听器缓存起来if (filteredListenerBeans.isEmpty()) {
         retriever.applicationListeners = new LinkedHashSet<>(allListeners);
         retriever.applicationListenerBeans = filteredListenerBeans;
      }
      else {
         retriever.applicationListeners = filteredListeners;
         retriever.applicationListenerBeans = filteredListenerBeans;
      }
   }
   return allListeners;
}

上面源码一句话可以概括,获取到所有listener遍历,根据eventType, sourceType匹配到符合当前event的监听器

但其中会有些小细节

  1. defaultRetriever存放了所有的监听器实例及beanName

  1. retriever != null说明event是第一次来解析对应的监听器


invokeListener(通知对应的监听器)

获取event对应监听器分析完了,接下来就回过头来分析通知监听器的源码了~

protectedvoidinvokeListener(ApplicationListener<?> listener, ApplicationEvent event){
  // 获取error的处理器
  ErrorHandler errorHandler = getErrorHandler();

  // 如果存在errorHandler,则对doInvokeListener进行try-catchif (errorHandler != null) {
    try {
      // 通知监听器
      doInvokeListener(listener, event);
    }
    catch (Throwable err) {
      errorHandler.handleError(err);
    }
  }
  else {
    doInvokeListener(listener, event);
  }
}
​
privatevoiddoInvokeListener(ApplicationListener listener, ApplicationEvent event){
  try {
    // 调用对应监听器实现的onApplicationEvent方法
    listener.onApplicationEvent(event);
  }
  catch (ClassCastException ex) {
    String msg = ex.getMessage();
    if (msg == null || matchesClassCastMessage(msg, event.getClass()) ||
        (event instanceof PayloadApplicationEvent &&
         matchesClassCastMessage(msg, ((PayloadApplicationEvent) event).getPayload().getClass()))) {
      Log loggerToUse = this.lazyLogger;
      if (loggerToUse == null) {
        loggerToUse = LogFactory.getLog(getClass());
        this.lazyLogger = loggerToUse;
      }
      if (loggerToUse.isTraceEnabled()) {
        loggerToUse.trace("Non-matching event type for listener: " + listener, ex);
      }
    }
    else {
      throw ex;
    }
  }
}

invokeListener的内容也比较简单,就是调用对应监听器实现onApplicationEvent方法,实现通知效果~


扩展知识点


实现异步事件通知

在上面探究applicationEventMulticaster初始化源码时,我提到了,Spring的观察者模式默认是同步的,如下图所示

@Component
public class CustomListener implements ApplicationListener<CustomEvent> {
​
   @Override
   public void onApplicationEvent(@NonNull CustomEvent event) {
      System.out.println(Thread.currentThread().getName() + " CustomListener接收到事件~");
      System.out.println(JSONObject.toJSONString(event.getUserInfo()));
   }
​
}

但是其底层也是支持通过线程池进行异步通知

那么该如何做呢?

在上面的applicationEventMulticaster初始化源码中,我们也能看到,如果容器中有beanName = applicationEventMulticaster的bean,就会将其作为事件多播器,所以我们可以自定义一个applicationEventMulticaster

@ConfigurationpublicclassSpringConfig{
​
   @Bean(name = "applicationEventMulticaster")
   public ApplicationEventMulticaster applicationEventMulticaster(){
      SimpleApplicationEventMulticaster simpleApplicationEventMulticaster = new SimpleApplicationEventMulticaster();
      simpleApplicationEventMulticaster.setTaskExecutor(Executors.newFixedThreadPool(10, new DefaultThreadFactory("custom")));
      return simpleApplicationEventMulticaster;
   }
​
}

此时再次测试事件发布,可以看到打印的当前线程不是main线程了,而是使用我们自定义的线程池中的线程


自定义监听器执行异常处理器

如果,我们项目中定义了许多的监听器,那么为了避免在处理事件的时候出现异常,我们难免都会去进行try-catch,会写出很多冗余代码。

那么怎么解决呢?

其实,在上面分析invokeListener源码的时候我们也能直接看到,可以支持统一的ErrorHandler,而且对代码没有侵入性~

protectedvoidinvokeListener(ApplicationListener<?> listener, ApplicationEvent event){
  // 获取error的处理器
  ErrorHandler errorHandler = getErrorHandler();

  // 如果存在errorHandler,则对doInvokeListener进行try-catchif (errorHandler != null) {
    try {
      // 通知监听器
      doInvokeListener(listener, event);
    }
    catch (Throwable err) {
      errorHandler.handleError(err);
    }
  }
  else {
    doInvokeListener(listener, event);
  }
}

自定义ErrorHandler

@Slf4j
publicclassCustomErrorHandlerimplementsErrorHandler{
​
   @OverridepublicvoidhandleError(@NonNull Throwable throwable){
      System.out.println("自定义error处理");
      log.error("CustomErrorHandler", throwable);
   }
​
}
@Bean(name = "applicationEventMulticaster")
public ApplicationEventMulticaster applicationEventMulticaster(){
   SimpleApplicationEventMulticaster simpleApplicationEventMulticaster = new SimpleApplicationEventMulticaster();
   simpleApplicationEventMulticaster.setTaskExecutor(Executors.newFixedThreadPool(10, new DefaultThreadFactory("custom")));
​
   // 设置自定义errorHandler
   simpleApplicationEventMulticaster.setErrorHandler(new CustomErrorHandler());
​
   return simpleApplicationEventMulticaster;
}
@ComponentpublicclassCustomListenerimplementsApplicationListener<CustomEvent> {
​
   @OverridepublicvoidonApplicationEvent(@NonNull CustomEvent event){
      // 手动模拟异常int i = 1 / 0;
      System.out.println(Thread.currentThread().getName() + " CustomListener接收到事件~");
      System.out.println(JSONObject.toJSONString(event.getUserInfo()));
   }
​
}

测试


registerListeners(早期event发布、listener缓存)

在refesh方法中,会调用registerListeners注册监听器,在上面的retrieveApplicationListeners源码分析中,我们也说到了,会从defaultRetriever中拿到所有listener

而此时的registerListeners就是将listener缓存到defaultRetriever

此外,还有publishEvent中提到的earlyApplicationEvents

listener还未缓存,就开始调用publishEvent了,这时先将event缓存到earlyEventsToProcess中,待缓存完毕后进行事件发布~

protectedvoidregisterListeners(){
   // 缓存listener实例for (ApplicationListener<?> listener : getApplicationListeners()) {
      getApplicationEventMulticaster().addApplicationListener(listener);
   }
​
   // 缓存listener beanName
   String[] listenerBeanNames = getBeanNamesForType(ApplicationListener.class, true, false);
   for (String listenerBeanName : listenerBeanNames) {
      getApplicationEventMulticaster().addApplicationListenerBean(listenerBeanName);
   }
​
   // 发布早期事件(listener还未缓存,就开始调用publishEvent了,这时先缓存到earlyEventsToProcess中)
   Set<ApplicationEvent> earlyEventsToProcess = this.earlyApplicationEvents;
   // 发布早期事件,并将this.earlyApplicationEvents = null;this.earlyApplicationEvents = null;
   if (!CollectionUtils.isEmpty(earlyEventsToProcess)) {
      for (ApplicationEvent earlyEvent : earlyEventsToProcess) {
         getApplicationEventMulticaster().multicastEvent(earlyEvent);
      }
   }
}
​
@OverridepublicvoidaddApplicationListener(ApplicationListener<?> listener){
  synchronized (this.defaultRetriever) {
    Object singletonTarget = AopProxyUtils.getSingletonTarget(listener);
    if (singletonTarget instanceof ApplicationListener) {
      this.defaultRetriever.applicationListeners.remove(singletonTarget);
    }
    this.defaultRetriever.applicationListeners.add(listener);
    this.retrieverCache.clear();
  }
}
​
@OverridepublicvoidaddApplicationListenerBean(String listenerBeanName){
  synchronized (this.defaultRetriever) {
    this.defaultRetriever.applicationListenerBeans.add(listenerBeanName);
    this.retrieverCache.clear();
  }
}

上面registerListeners会调用getApplicationListeners获取所有listener,哪这里是怎么获取的呢?

答案就是: Spring中存在ApplicationListenerDetector实现了BeanPostProcessor,如果bean instanceof ApplicationListener,且是单例bean,则会添加到AbstractApplicationContext类的applicationListeners这个集合中

getApplicationListeners也是直接获取的applicationListeners

public Object postProcessAfterInitialization(Object bean, String beanName) {
   if (bean instanceof ApplicationListener) {
      // potentially not detected as a listener by getBeanNamesForType retrievalBoolean flag = this.singletonNames.get(beanName);
      if (Boolean.TRUE.equals(flag)) {
         // singleton bean (top-level or inner): register on the flythis.applicationContext.addApplicationListener((ApplicationListener<?>) bean);
      }
      elseif (Boolean.FALSE.equals(flag)) {
         if (logger.isWarnEnabled() && !this.applicationContext.containsBean(beanName)) {
            // inner bean with other scope - can't reliably process events
            logger.warn("Inner bean '" + beanName + "' implements ApplicationListener interface " +
                  "but is not reachable for event multicasting by its containing ApplicationContext " +
                  "because it does not have singleton scope. Only top-level listener beans are allowed " +
                  "to be of non-singleton scope.");
         }
         this.singletonNames.remove(beanName);
      }
   }
   return bean;
}
Spring中ApplicationListener源码分析 说ApplicationListener之前先要知道EventListener。EventListener本身是一个接口,它的作用跟前面讲到的Aware类似,都是只定义最顶级的接口,并没有实习对应的方法,并且该接口也是由JDK提供的,并不是直接由Spring提供,Spring只是基于该接口实现了自己的一套事件监听功能。在Spring中实现事件监听的接口是ApplicationListener,该接口继承了EventListener,并做了对应的实现。/***/复制代码。 阅读详情

相关推荐

【spring】 ApplicationListener的使用及原理简析

ApplicationListener 是spring提供的一个监听器,它可以实现一个简单的发布-订阅功能,用有点外行但最简单通俗的话来解释:监听到主业务在执行到了某个节点之后,在监听器里面做出相应的其它业务变更。下面我们具体看段代码,则能很快的理解

孟秋与你的博客 8100

Spring注解驱动之ApplicationListener异步处理事件详解

Spring的ApplicationListener异步处理事件

抽象的螺旋 2164

保姆级教程:用sw_urdf_exporter插件将Solidworks机械臂模型转为ROS可用的URDF

本文提供了一份详细的保姆级教程,指导用户使用sw_urdf_exporter插件将Solidworks机械臂模型转换为ROS可用的URDF文件。从环境准备、插件配置到模型预处理和关键参数设置,再到ROS环境集成测试和性能优化,全面覆盖了机械臂URDF转换的全流程。特别适合机器人工程师和ROS开发者快速实现Solidworks模型到ROS环境的无缝迁移。

weixin_29300931的博客 400

spring容器初始化过程笔记(五) applicationListeners()初始化

这篇讲了spring源码相关,主要是对spring源码做笔记,需要读者下载spring源码编译执行辅助。编译spring源码步骤详解(一)

weixin_43064364的博客 608

使用openair包分析气象数据

气象数据通常是有规律的时间序列,分析气象数据时需要对空气质量测量、气象数据和扩散模型输出进行分析。openair 是用于分析空气质量数据或更广泛的大气成分数据的R软件包。该软件包广泛应用于学术界、公共和私营部门。该项目最初由英国自然环境研究委员会(NERC)资助,并由英国环境部(Defra)提供额外资金。

Christina 2519

别再混淆了!5分钟搞懂机器人导纳控制与阻抗控制的本质区别(附Simulink对比仿真)

本文深入解析机器人柔顺控制中的导纳控制与阻抗控制的本质区别,通过信号流方向、架构对比和应用场景三个关键视角进行详细阐述。文章还提供Simulink对比仿真案例,帮助读者快速理解两种控制策略的差异及其适用场景,特别适合机器人控制领域的研究者和工程师参考。

weixin_42543046的博客 604

SpringBoot--自定义事件--同步与异步

原文网址: 简介 说明 本文用示例介绍SpringBoot中的事件的用法及原理。 SpringBoot的启动过程用到了事件监听机制,它属于观察者模式。学习事件监听机制可以让我们更好地理解SpringBoot的启动流程,也有利于将观察者模式应用于项目。 事件监听简述 事件的发布与监听从属于观察者模式;和MQ相比,事件的发布与监听偏向于处理“体系内”的某些逻辑。 多个监听器可以监听同一个事件。例如:发布了事件A,监听器A和监听器B都.........

IT利刃出鞘的博客 2439

在win10系统上安装opnet14.5注意事项和步聚

opnet 14.5安装避坑!

miracle_eicont的专栏 1038

springboot监听器

Springboot 监听器 主要是通过观察者模式对一个事件发布之后进行处理,对应spring中为ApplicationListener对监听的ApplicationEvent进行处理 源码解析 注册监听器 进入AnnotationConfigApplicationContext代码片构造函数 public AnnotationConfigApplicationContext(Class<?>... componentClasses) { this(); this

qq_36566262的博客 258

开关电源环路学习笔记(1)-为啥要知道传递函数?

做硬件都会接触到开关电源,或多或少会接触到环路补偿。很多兄弟们也提出来让我说说这个,说明大家也都很关心,也说明这个好像有点难。 就我个人而言,我也想搞明白环路这一块,也查了一些资料,视频,不过说实话,一直都还是懵的,感觉很困难。 具体感觉就是,看资料或视频的时候,感觉好像是那么回事。但是撇开之后,就又云里雾里的感觉,总有一些想法没有找到答案。 问题 比如下面我想过的问题: 开关电源有开关,根本就不是一个线性系统,传递函数是咋弄出来的? 系统环路框图的输入量为什么不是Vin,而是参考电压V

weixin_42005993的博客 1万+

360加固DEX解密与ELF修复技术解析

本文深入解析了360加固APK的逆向分析技术,重点探讨了其DEX解密与ELF文件修复的核心流程。通过动态调试与Frida脚本,详细拆解了壳的启动机制、反调试绕过、自实现Linker技术,并还原了基于RC4和自定义算法的多层DEX解密过程,为移动安全研究人员提供了实用的实战分析思路。

alice7model的博客 701

spring中的ApplicationListener

spring中的ApplicationListener是一个监听器,用来监听容器中发布的事件 监听器也是一种观察者设计模式,该接口有一个onApplicationEvent()方法, 在事件发布时,此方法将会调用,实现监听的功能。 /** * Interface to be implemented by application event listeners. * Based on the...

来一杯可乐 150

大数据分析案例-基于决策树算法构建世界杯比赛预测模型

本项目旨在分析往届世界杯比赛数据,找出规律,最后使用决策树算法构建世界杯比赛预测模型,给出两个球队即可预测胜率,可以给爱看球的小伙伴提供一个参考。本次实验我们使用了2018年及其之前的世界杯比赛数据,通过建立决策树模型,模型准确率为82%,接着我们使用该模型预测了2022年世界杯的5场半决赛及决赛,模型准确率也是接近80%的,说明模型效果还是很不错的。心得与体会:通过这次Python项目实战,我学到了许多新的知识,这是一个让我把书本上的理论知识运用于实践中的好机会。

m0_64336780的博客 1万+

Spring 监听器的简单用法

SpringApplicationListener用法与原理 SpringApplicationListener的简介与基本用法 SpringApplicationListener是Spring为我们提供的事件监听器,它主要是为我们提供事件监听机制,我们可以创建一个监听器,并向该监听器创建相应的事件,当该事件发生时,我们可以我们的监听器就会得到相应的通知,并且触发相应的逻辑。是典型的观察者模式 基本用法 @FunctionalInterface public interface ApplicationLis

anying5823的博客 460

Matplotlib之bar3d画3D柱状图

3D柱状图通常用于展示数据集中不同分类项的数据大小或数量。与普通的柱状图相比,3D柱状图更适合展示数据的三个维度 3D 柱状图 按行/列涂颜色 柱加阴影、描黑边 自定义座标轴名、刻度标签、范围

我走的每一步都算数 1121

复分析 阿尔福斯(Ahlfors) 第三版答案 英文

答案分为十章,有部分习题没有答案,全英文复分析阿尔福斯(Ahlfors) 第三版答案

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

开发过程中一般使用mq进行功能解耦,但是当主业务上开启许多分支功能,不可能为每一个分支功能开启各自的mq,因此使用Spring的ApplicationEventPublisher进行发布监听。 同时大多数情况下分支功能与主业务需要解耦,因此发布的事件需要异步处理。

m0_69270256的博客 2万+

IMSL CNL 7.0.0 (x86-64)Crack

IMSL CNL 7.0.0 (x86-64)Crack intel的算法库破解文件,这是C++版本的破解文件

分析Spring-ApplicationListener监听器

ApplicationListener接口用来监听容器发布的事件,用来事件驱动模型开发。在方法上使用@EventListener注解,作用与实现接口一致,都可用来监听事件。

weixin_37607613的博客 1559

蓝牙模块HM-10手册

蓝牙模块HM-10手册,该手册对蓝牙模块HM-10进行了详细的介绍和使用方法以及一些AT指令。

假人有限元模型

假人模型,车辆碰撞假人有限元模型,适合做乘员安全的研究者利用

spring 之 Listener的异步调用

关于listener的原理我这里就不过多叙述了,大家百度一下,一大堆。 我写一个实际案例:(代码非常简单,很多主要是业务的,我就没有删除,不要觉得麻烦,很简单) 情景描述:我监听到一个事件后,想让监听器里面延时10秒执行一些操作,但是controller继续执行自己的代码,返回视图层。 本人先是用拦截器。(只贴出来相关的配置) 先来spring核心容器的配置

siqiangming的博客 3157

ApplicationListener事件监听、自定义事件、异步执行

ApplicationEvent以及Listener是Spring为我们提供的一个事件监听、订阅的实现,内部实现原理是观察者设计模式,设计初衷也是为了系统业务逻辑之间的解耦,提高可扩展性以及可维护性。事件发布者并不需要考虑谁去监听,监听具体的实现内容是什么,发布者的工作只是为了发布事件而已。 Spring提供的内置事件: ContextRefreshedEvent ContextStartedEvent ContextStoppedEvent ContextClosedEvent 如何使

morris 7925

观察者模式ApplicationListener异步处理事件

配置类中添加 @EnableAsync开启对异步任务的支持, 并在相应的方法中使用 @Async注解来声明一个异步任务 Environment 可以直接获取配置文件的值 @Configuration @EnableAsync public class TaskExecutorConfig { /*** * 创建异步任务执行线程池 * @return */ ...

占星安啦的博客 3370

ApplicationEvent 与ApplicationListener 异步化执行测试

目录 定义事件 定义监听 设置配置 Junit测试 异步执行使用@EnableAsync开启方法异步,在需要异步执行的方法上设置@Async即可。 定义事件 分别定义A、B、C 三类事件:没有特别的都是一个默认构造。 定义监听 这里可以测试两个场景: 包含@Async 不包含@Async 设置配置 Junit测试 非@Async场景是顺序执行,而@Async是异步执行。 ...

纸上得来终觉浅,绝知此事要躬行 966

Spring Boot ApplicationListener异步执行

Spring Boot版本 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot</artifactId> <version>1.3.6.RELEASE</version> &lt...

baozhutang的博客 1950

ApplicationListener,异步线程实现发布订阅

ApplicationListener,异步线程实现发布订阅

qq_39745899的博客 387

springBoot Event实现异步消费机制

ApplicationEvent以及Listener是Spring为我们提供的一个事件监听、订阅的实现,内部实现原理是观察者设计模式,设计初衷也是为了系统业务逻辑之间的解耦,提高可扩展性以及可维护性。

weixin_46237429的博客 989
上一篇: 全方位解读Java面试:阿里扫地僧手码14w字面试速成手册我粉了
下一篇: 学完定级P7!阿里内部10W字性能优化实战手册太强了!上线3天Github获赞68.9K
Java技术那些事儿
博客等级 码龄5年 2306粉丝 2584原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值