当前位置: 首页 > news >正文

spring的事件监听

概述

对于 Spring 容器的一些事件,可以监听并且触发相应的方法

  • Spring监听器是一种特殊的类,它们能帮助开发者监听 web 中特定的事件

    比如 ServletContext, HttpSession, ServletRequest 的创建和销毁;
    变量的创建、销毁等等。

  • 当Web容器启动后,Spring的监听器会启动监听

首先,这种监听机制使用下面几种模式

  • 观察者模式:主要是观察者模式
  • 工厂模式(EventListenerFactory)
  • 适配器模式(ApplicationListenerMethodAdapter),

事件监听方式

Spring 提供了两种事件监听方式:ApplicationListener 接口 和 @EventListener 注解。以下是它们的差异:

特性ApplicationListener 接口@EventListener 注解
实现方式需实现接口并重写方法直接在方法上标注注解
事件类型绑定通过泛型参数指定通过方法参数或注解的 classes/value 指定
多事件监听每个监听器只能监听一种事件单个方法可监听多种事件(通过参数类型推断或显式指定)
条件过滤需在代码中手动判断支持通过 condition 属性配置 SpEL 表达式
事务性事件需自行实现事务逻辑支持 @TransactionalEventListener 注解
代码侵入性较高(需实现接口)较低(无侵入)

(1) 优先使用 ApplicationListener 的场景

  • 需要强类型事件绑定:明确监听特定类型的事件。
  • 旧代码兼容:遗留系统已基于接口实现事件监听。
  • 复杂事件处理逻辑:需在监听器中封装多步骤逻辑。

(2) 优先使用 @EventListener 的场景

  • 简化代码:无需实现接口,直接通过注解绑定方法。
  • 动态条件过滤:需要根据事件属性动态决定是否处理。
  • 多事件监听:单个方法处理多种事件类型。

Spring内置的事件

Spring内置了的事件类型,分别在以下情况下触发

  1. ContextRefreshedEvent:当应用程序上下文被刷新时触发。这个事件在ApplicationContext初始化或刷新时被发布,适用于执行初始化操作和启动后的后续处理。例如,初始化缓存、预加载数据等。
  2. ContextStartedEvent:当应用程序上下文启动时触发。这个事件在调用ApplicationContext的start()方法时被发布,适用于在应用程序启动时执行特定的操作。例如,启动定时任务、启动异步消息处理等。
  3. ContextStoppedEvent:当应用程序上下文停止时触发。这个事件在调用ApplicationContext的stop()方法时被发布,适用于在应用程序停止时执行清理操作。例如,停止定时任务、关闭数据库连接等。
  4. ContextClosedEvent:当应用程序上下文关闭时触发。这个事件在调用ApplicationContext的close()方法时被发布,适用于在应用程序关闭前执行最后的清理工作。例如,释放资源、保存日志等。
  5. RequestHandledEvent:在Web应用程序中,当一个HTTP请求处理完成后触发。这个事件在Spring的DispatcherServlet处理完请求后被发布,适用于记录请求日志、处理统计数据等。
  6. ApplicationEvent:这是一个抽象的基类,可以用于定义自定义的应用程序事件。你可以创建自定义事件类,继承自ApplicationEvent,并定义适合你的应用场景的事件类型

@EventListener注解

使用示例

Spring中一个内置监听器的简单示例:当启动一个基于Spring的应用程序时,当应用程序上下文被刷新时ContextRefreshedEvent事件将被触发,然后MyContextRefreshedListener监听器的onApplicationEvent方法将被调用

  1. 加上@EventListener(ContextRefreshedEvent.class),Spring会在加载这个类时,为其创建一个监听器,这个监听器监听的事件类型是ContextRefreshedEvent,当此事件发生时,将触发执行该方法methodA
  2. 使用 @Component 注解将该类声明为一个Spring管理的组件,这样Spring会自动将其纳入到应用程序上下文中,并在适当的时候触发监听
  3. 我们可以在这个类中写上多个方法,每个方法通过注解监听着不同的事件类型,这样我们就仅需使用一个类,却构建了多个监听器
    @Component
    public class MyListener {@EventListener(ContextRefreshedEvent.class)public void methodA(ContextRefreshedEvent event) {System.out.println("应用程序上下文已刷新");// 在这里可以执行一些初始化操作}
    }
    

注解参数说明

注解源码如下,有两个参数:
在这里插入图片描述

有两个参数:

  1. classes 参数 / classes 参数
    • 作用:指定监听的事件类型(支持多个事件类)。

      用于指定要监听的事件类型
      可以指定一个或多个事件类型,以数组形式传递

    • 如下示例:
      // 监听单一事件
      @EventListener(value = UserCreatedEvent.class)
      public void handleUserEvent(UserCreatedEvent event) { ... }// 监听多个事件
      @EventListener(classes = {OrderPaidEvent.class, OrderCanceledEvent.class})
      public void handleOrderEvents(ApplicationEvent event) { if (event instanceof OrderPaidEvent) { ... }
      }
  2. condition 参数
    • 作用:通过SpEL表达式动态过滤事件,表达式可访问事件对象的属性(通过#root.event或直接引用)。

      用于指定一个 SpEL 表达式作为条件,只有当条件满足时,才会执行监听器方法。

    • 示例如下:
      // 示例中,方法 handleEvent() 只有当事件源为 source 时才会执行
      @EventListener(condition = "#event.source == 'source'")
      public void handleEvent(Event event) {// 处理事件逻辑
      }@EventListener(condition = "#root.event.user.age > 18")
      public void onUserUpdate(UserUpdateEvent event) {// 只有当user对象的age属性大于18时,才会执行这个方法
      }@EventListener(condition = "#root.args[0].user.gender == '男'")
      public void onUserUpdate(UserUpdateEvent event) {// 只有当user对象的gender属性为男时,才会执行这个方法
      }@EventListener(condition = "@userService.isAdmin(#event.user)")
      public void onUserUpdate(UserUpdateEvent event) {// 只有当userService Bean的isAdmin方法返回true时,才会执行这个方法
      }
      
  3. 若value()和classes()没有设置值,则标识@EventListener的方法必须有且只能有一个参数

    如上demo中,其实可以不使用参数,直接使用注解即可

    @Component
    public class MyListener {@EventListenerpublic void methodA(ContextRefreshedEvent event) {System.out.println("应用程序上下文已刷新");// 在这里可以执行一些初始化操作}
    }
    
  4. id 参数:
    • 作用:为监听器定义唯一ID,常用于事务性事件(需结合@TransactionalEventListener)。
    • 示例
      @TransactionalEventListener(id = "orderListener", phase = TransactionPhase.AFTER_COMMIT)
      public void handleOrderAfterCommit(OrderEvent event) { ... }

ApplicationListener

基本概念

作用:ApplicationListener 是 事件监听器的通用接口,定义了监听器处理事件的标准方式。

  • 监听事件:通过泛型参数指定要监听的事件类型(如 ApplicationEvent 或其子类)。
  • 处理事件:实现 onApplicationEvent 方法,编写事件处理逻辑

核心方法 onApplicationEvent

onApplicationEvent(E event)

  • 作用:处理接收到的事件。
  • 触发时机:当 Spring 上下文发布与监听器泛型类型匹配的事件时调用。
  • 参数:事件对象(如 UserCreatedEvent)。
  • 示例
    @Override
    public void onApplicationEvent(UserCreatedEvent event) {// 从事件中获取数据String username = event.getUsername();// 执行业务逻辑(如发送邮件、记录日志)notificationService.sendWelcomeEmail(username);
    }

使用注意

未指定泛型类型:监听器未通过泛型绑定事件类型,导致监听所有事件。如下:

// 错误:会监听所有类型的 ApplicationEvent
public class MyListener implements ApplicationListener { ... }

需要明确指定泛型类型。如下

public class MyListener implements ApplicationListener<MyEvent> { ... }

事件处理阻塞主线程:在 onApplicationEvent 中执行耗时操作(如 IO、网络请求),导致主线程卡顿。可以使用 @Async 注解异步处理。如下

@Async
@Override
public void onApplicationEvent(UserCreatedEvent event) { ... }

使用ApplicationListener 示例

实现一个内置监听器的简单示例:当启动一个基于Spring的应用程序时,当应用程序上下文被刷新时,ContextRefreshedEvent事件将被触发,然后MyContextRefreshedListener监听器的onApplicationEvent方法将被调用

  1. 实现了ApplicationListener接口,这意味着这个监听器监听的事件类型是ContextRefreshedEvent,并重写了 onApplicationEvent 方法。该方法在应用程序上下文被刷新时触发。
  2. 使用 @Component 注解将该监听器声明为一个Spring管理的组件,这样Spring会自动将其纳入到应用程序上下文中,并在适当的时候触发监听
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.stereotype.Component;
@Component
public class MyContextRefreshedListener implements ApplicationListener<ContextRefreshedEvent> {@Overridepublic void onApplicationEvent(ContextRefreshedEvent event) {System.out.println("应用程序上下文已刷新");// 在这里可以执行一些初始化操作}
}

自定义事件与监听器

自定义事件

// 事件A
public class CustomEventA extends ApplicationEvent {private String message;public CustomEventA(Object source, String message) {super(source);this.message = message;}public String getMessage() {return message;}public void setMessage(String message) {this.message = message;}
}
// 事件B
public class CustomEventB extends ApplicationEvent {private String message;public CustomEventB(Object source, String message) {super(source);this.message = message;}public String getMessage() {return message;}public void setMessage(String message) {this.message = message;}
}

构建监听(@EventListener注解)

@Component
public class MyListener {//@EventListener(CustomEventA.class)@EventListenerpublic void methodA(CustomEventA event) {System.out.println("========我监听到事件A了:" + event.getMessage());// 在这里可以执行一些其他操作}//@EventListener(CustomEventB.class)@EventListenerpublic void methodB(CustomEventB event) {System.out.println("========我监听到事件B了:" + event.getMessage());// 在这里可以执行一些其他操作}
}

发布事件

当容器启动后,调用接口,便会触发下述两个事件

import org.springframework.context.ApplicationContext;@Resource
private ApplicationContext applicationContext;@GetMapping("/publishEvent")
public void publishEvent() {CustomEventA eventA = new CustomEventA(applicationContext , "我是AAAA");CustomEventB eventB = new CustomEventB(applicationContext , "我是BBBB");applicationContext.publishEvent(eventA);applicationContext.publishEvent(eventB);
}

@EventListener常见错误使用

下述情况会导致失败:

  • 监听方法的修饰符是private;
    @EventListener
    private void methodB(CustomEventB event) {
    
  • 事件类型不匹配:监听方法是监听的CustomEventB,但入参却是CustomEventA ;
    @EventListener(CustomEventB.class)
    private void methodB(CustomEventA event) {
    
  • 事件监听容器还没有该事件的观察者,就会导致事件发布了,但是没有相应观察者进行监听

Spring监听器原理

整体原理

流程梳理

在Spring中,监听器实体全部放在ApplicationContext中,事件也是通过ApplicationContext来进行发布

  • 通过ApplicationContext发布的事件,其并不是自己进行事件的发布,而是引入了一个处理器—— EventMulticaster,直译就是事件多播器
  • 事件多播器:它负责在大量的监听器中,针对每一个要广播的事件,找到事件对应的监听器,然后调用该监听器的响应方法,图中就是调用了监听器1、3、6
  • 只有在某类事件第一次广播时,EventMulticaster才会去做遍历所有监听器的事,当它针对该类事件广播过一次后,就会把对应监听器保存起来了,最后会形成一个缓存Map,下一次就能直接找到这些监听器
    final Map<ListenerCacheKey, ListenerRetriever> retrieverCache = new ConcurrentHashMap<>(64);
    

在这里插入图片描述

关键角色

组件作用
ApplicationEvent事件基类,所有自定义事件必须继承它(或 PayloadApplicationEvent)
ApplicationListener< E>监听器接口,泛型 < E> 指定监听的事件类型
ApplicationEventPublisher事件发布接口,由 ApplicationContext 实现,提供 publishEvent() 方法
ApplicationEventMulticaster事件多播器,负责将事件分发给匹配的监听器(默认实现为 SimpleApplicationEventMulticaster)

组件协作流程

  • 发布事件:通过 ApplicationEventPublisher.publishEvent() 发布事件。
  • 事件广播:ApplicationEventMulticaster 接收事件,遍历所有监听器。
  • 筛选监听器:根据事件类型匹配对应的 ApplicationListener。
  • 触发监听器:调用监听器的 onApplicationEvent(E event) 方法。

ApplicationListener接口

大致代码逻辑

spring 提供的EventListenerFactory有两个:

  • DefaultEventListenerFactoryspring 默认的
    • 支持所有标记了@EventListener的方法
    • 建ApplicationListener的方法是createApplicationListener,它调用的是ApplicationListenerMethodAdapter的构造方法,
    • 在ApplicationListenerMethodAdapter中,其中一堆的赋值操作,从此处可以得出结论a.被@EventListener标记的方法,参数最多只能有一个;b.方法能监听的事件可以有多个,由@EventListener指定
      1. 通过反射调用被@EventListener标记的方法
      2. 处理@EventListener方法的返回结果,在handleResult(…)方法里可以将返回的结果当作事件再发布出去
  • TransactionalEventListenerFactory处理事务监听的
    • 只支持标记了 @TransactionalEventListener的方法;
      • 这个注解标记了@EventListener,因此具有与@EventListener相同的功能
      • 多了两个属性:phase()与fallbackExecution(),看来是用来控制事务的
    • 建的ApplicationListener实际类型为ApplicationListenerMethodTransactionalAdapter
      • ApplicationListenerMethodTransactionalAdapter继承了ApplicationListenerMethodAdapter,其构造方法也是先调用ApplicationListenerMethodAdapter的构造方法,然后再给annotation赋值
      • 该方法主要是监听事件
      • 继承来自ApplicationListenerMethodAdapter的方法
      • ApplicationListenerMethodTransactionalAdapter并没有重写supportsEventType(…)方法,因此也是使用ApplicationListenerMethodAdapter的supportsEventType(…)方法来判断事件的支持情况
      • 在处理事件时,ApplicationListenerMethodTransactionalAdapter会进行事务相关的处理,
        • 若是当前线程不存在事务,则判断@TransactinalEventListener的fallbackExecution属性是否是true。若是true,则表示若当前线程没有正在运行事务运行,该监听器也会监听事件发布,运行processEvent方法调用方法处理事件

原理总结

注册入口

Spring 容器启动时:通过 ApplicationContext 自动扫描所有实现了 ApplicationListener 接口的 Bean

底层机制

  • 在 Bean 初始化阶段,Spring 检测到 Bean 实现了 ApplicationListener 接口。
  • 将该 Bean 注册到 ApplicationEventMulticaster 的监听器列表中。
  • 关键源码(AbstractApplicationContext):
protected void registerListeners() {// 注册静态指定的监听器for (ApplicationListener<?> listener : getApplicationListeners()) {getApplicationEventMulticaster().addApplicationListener(listener);}// 注册 Bean 形式的监听器String[] listenerBeanNames = getBeanNamesForType(ApplicationListener.class, true, false);for (String listenerBeanName : listenerBeanNames) {getApplicationEventMulticaster().addApplicationListenerBean(listenerBeanName);}
}
事件类型匹配机制

泛型类型解析Spring 通过 泛型参数 确定 ApplicationListener 监听的事件类型。

  • 示例:Spring 会解析 MyEvent 作为该监听器关注的事件类型。
    public class MyListener implements ApplicationListener<MyEvent> { ... }

匹配逻辑

  • 事件类型检查:判断当前事件是否是监听器泛型类型的实例(instanceof)。
  • 继承支持:如果监听器监听的是父类事件(如 ApplicationEvent),则会接收到所有子类事件。

缓存机制Spring 会缓存监听器与事件类型的映射关系,避免每次事件发布时重复解析泛型。

事件广播与分发

事件发布入:调用 ApplicationContext.publishEvent() 时,实际委托给 ApplicationEventMulticaster

public void publishEvent(ApplicationEvent event) {// 委托给多播器getApplicationEventMulticaster().multicastEvent(event);
}

多播器核心逻辑:SimpleApplicationEventMulticaster 的 multicastEvent() 方法:

  • 遍历所有监听器:检查是否支持当前事件类型。
  • 同步/异步执行:根据配置决定是否使用线程池异步触发监听器。

@EventListener原理

大致代码逻辑

在Bean生成流程中,会对每个Bean都使用PostProcessor来进行加工
BeanFactoryPostProcessor的实现类EventListenerMethodProcessor会处理@EventListener注解

EventListenerMethodProcessor实现了两个接口

  • BeanFactoryPostProcessor:可以定制化BeanFactory的一些行为

    • 在postProcessBeanFactory()方法在中,从容器中获取了EventListenerFactory并赋值给了eventListenerFactories
    • 从这个地方可以看出来,EventListenerFactory有两个:
      • DefaultEventListenerFactory:spring 默认的
      • TransactionalEventListenerFactory:处理事务监听的
  • SmartInitializingSingleton处理单例bean的初始化操作,执行时机是在bean初始化完成之后

    • 在该实现里面的afterSingletonsInstantiated()方法里面:将@EventListener标记的方法转换成ApplicationListener对象
      • 历这些bean,对每一个bean,如果是代理对象,则调用AutoProxyUtils.determineTargetClass(beanFactory, beanName)方法获取其目标类,我们知道注解是不能继承的,要获取@EventListener标记的方法,需要从目标类去获取;如果不是代理对象,则目标类就是bean对应的类;
      • 其次,调用processBean(beanName, type)方法进一步处理,如果标记了@EventListener,都会被找到,最终会把找到的所有方法放到一个Map中
      • 遍历上述得到的map,将每个方法转换为ApplicationListener 对象,再添加到applicationContext的ApplicationListener中
  • @EventListener 标记的方法之所以能对事件进行监听,是因为 spring 将该方法包装成一个ApplicationListener添加到监听器,之后这个监听器就跟实现了ApplicationListener接口的监听器一样能对事件进行监听了。

原理总结

@EventListener 的整个生命周期可以分为以下步骤:

  • Bean 后处理阶段:Spring 容器启动时,通过 EventListenerMethodProcessor 扫描所有 Bean 中被 @EventListener 注解标记的方法。
  • 生成监听器适配器:将每个注解方法包装成一个 ApplicationListenerMethodAdapter,这是一个 ApplicationListener 接口的实现类。
  • 注册监听器
    • Spring 启动时,通过 EventListenerMethodProcessor 扫描所有 Bean 的方法,找到标记 @EventListener 的方法。
    • 将这些方法包装成 ApplicationListenerMethodAdapter(实现了 ApplicationListener 接口),并注册到事件广播器 ApplicationEventMulticaster 中。
  • 事件触发:当调用 ApplicationContext.publishEvent() 发布事件时,事件广播器会遍历所有监听器,找到匹配的监听方法并执行。

异步监听

使用 @EventListener注解标注事件被触发时执行的方法(默认是同步执行,异步需要额外配置

当事件触发时,Spring Boot 事件监听器会在同一个线程中处理该事件。这种处理方式的好处是可以保证处理事件的顺序和一致性,但如果事件处理比较耗时,可能会阻塞主线程

配置方式有两种:

  • 配置监听器异步执行
  • 使用@Async 注解

配置监听器异步执行

探索

监听器最终是通过ApplicationEventMulticaster内部的实现来调用的

  • 这个类默认有个实现类SimpleApplicationEventMulticaster,这个类是支持监听器异步调用的
  • SimpleApplicationEventMulticaster中事件监听器的调用,最终会执行下面这个方法
    在这里插入图片描述
  • 在 executor 不为空时会使用线程池去执行 invokeListener(自定义监听事件)
  • 所以:我们只要定义一个SimpleApplicationEventMulticaster 的bean并设置他的taskExecutor就可以异步了

实现

配置便可以分为下述两个步骤

  1. 自定义事件广播器:SimpleApplicationEventMulticaster
  2. 线程池配置
// 第一步:自定义事件广播器
@Configuration
public class ApplicationEventAsyncConfig {@Resourceprivate ThreadPoolTaskExecutor myExecutor;@Beanpublic ApplicationEventMulticaster applicationEventMulticaster() { //@1//创建一个事件广播器SimpleApplicationEventMulticaster result = new SimpleApplicationEventMulticaster();//设置异步执行器,来完成异步执行监听事件这样会导致所有的监听器都异步执行result.setTaskExecutor(myExecutor);return result;}
}// 第二步:线程池配置
@Configuration
public class ThreadPoolConfig {@Bean("myExecutor")public Executor taskExecutor() {ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();//设置线程池参数信息taskExecutor.setCorePoolSize(8);taskExecutor.setMaxPoolSize(20);taskExecutor.setQueueCapacity(50);taskExecutor.setKeepAliveSeconds(60);taskExecutor.setThreadNamePrefix("myExecutor--");taskExecutor.setWaitForTasksToCompleteOnShutdown(true);// 设置线程池中任务的等待时间,如果超过这个时候还没有销毁就强制销毁,以确保应用最后能够被关闭,而不是阻塞住。taskExecutor.setAwaitTerminationSeconds(60);//修改拒绝策略为使用当前线程执行taskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());//初始化线程池taskExecutor.initialize();return taskExecutor;}
}

使用@Async 注解

探索

@Async默认情况下用的是SimpleAsyncTaskExecutor线程池,该线程池不是真正意义上的线程池

使用此线程池无法实现线程重用,每次调用都会新建一条线程

若系统中不断的创建线程,最终会导致系统占用内存过高,引发OutOfMemoryError错误

实现

四步便可以实现:(如果想要简单一点的话,直接使用第一步和第二步即可)

  1. 在事件监听器上加上 @Async 注解来实现异步处理事件
  2. 项目启动类上添加@EnableAsync注解
  3. 配置线程池:也可以通过@Async("线程池bean名称“)来决定使用哪个线程池,如果有多个线程池,但是在@Async注解里面没有指定的话,会默认加载第一个配置的线程池
  4. 配置异常处理(子线程执行异常无法被主线程捕获)

    定义SpringAsyncConfiguration 类实现AsyncConfigurer 接口去定义执行的线程池和异常处理类(只能处理无返回值的异常)

第一步:在事件监听器上加上 @Async 注解来实现异步处理事件

 //1. 在事件监听器上加上 @Async 注解来实现异步处理事件
@Component
@Async
public class MyListener {//@EventListener(CustomEventA.class)@EventListenerpublic void methodA(CustomEventA event) {System.out.println("========我监听到事件A了:" + event.getMessage());// 在这里可以执行一些其他操作}//@EventListener(CustomEventB.class)@EventListenerpublic void methodB(CustomEventB event) {System.out.println("========我监听到事件B了:" + event.getMessage());// 在这里可以执行一些其他操作}
}

第二步:项目启动类上添加@EnableAsync注解

// 2. 项目启动类上添加@EnableAsync注解
@EnableAsync 
@SpringBootApplication
public class EventApplication {public static void main(String[] args) {SpringApplication.run(EventApplication.class, args);}
}

第三步: 和上述使用“配置监听器异步执行”差不多,这里就不赘述了
第四步:配置异常处理(子线程执行异常无法被主线程捕获)

// 第四步:
@Configuration
@Slf4j
public class SpringAsyncConfiguration implements AsyncConfigurer {@Resourceprivate ThreadPoolTaskExecutor myExecutor;@Overridepublic Executor getAsyncExecutor() {return myExecutor;}@Overridepublic AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {return new SpringAsyncExceptionHandler();}class SpringAsyncExceptionHandler implements AsyncUncaughtExceptionHandler {@Overridepublic void handleUncaughtException(Throwable throwable, Method method, Object... obj) {log.error("Exception occurs in async method:{} ,err:{}",method.getName(), throwable.getMessage());}}
}

监听器执行顺序

同步执行

使用 @Order(int 类型的值value) 注解可以定义监听器的执行顺序,value越小优先级越高。

异步执行

异步情况下无法控制执行顺序。即便是使用了@Older()注解

http://www.xdnf.cn/news/328807.html

相关文章:

  • 【Machine Learning Q and AI 读书笔记】- 05 利用数据减少过拟合现象
  • 【JAVA】BigDecimal判断是否为0, / by zero的问题修复
  • leetcode 2395. Find Subarrays With Equal Sum
  • MySQL 数据备份与恢复
  • Nginx篇之限制公网IP访问特定接口url实操
  • QUIC协议优化:HTTP_3环境下的超高速异步抓取方案
  • Qt重写相关事件,原来的默认功能是不是丢失了?
  • FFmpeg(7.1版本)编译生成ffplay
  • AI Agent(5):多Agent协作系统
  • 5.6-DAE实现
  • 背单词软件开发英语app开发,超级单词表开发,河南数匠软件开发
  • 数据结构之栈与队列
  • QT6 源(83)篇二:日期类型 QDate 的源代码,及功能测试:日期与字符串互相转换时候的格式指定,
  • 中级注册安全工程师的《安全生产专业实务》科目如何选择专业?
  • Media3 中 Window 的时间相关属性详解
  • MySQL 1205错误:Lock wait timeout exceeded问题处理
  • 词编码模型和回答问题的LLM是否为同一个; 词编码模型和回答问题模型分开时:需要保证词嵌入维度一致吗
  • 软考【软考高级QA】
  • DSENT (Design Space Exploration of Networks Tool) 配合gem5
  • 时间序列数据集增强构造方案(时空网络建模)
  • 【网络编程】二、UDP网络套接字编程详解
  • 项目文档归档的最佳实践有哪些?
  • Nacos源码—Nacos集群高可用分析(二)
  • java实现一个操作日志模块功能,怎么设计
  • 【云备份】项目展示项目总结
  • 深入理解Redis缓存与数据库不一致问题及其解决方案
  • Matlab 多策略改进蜣螂优化算法及其在CEC2017性能
  • PCI-Compatible Configuration Registers--BIST Register (Offset 0Fh)
  • 跨物种交流新时代!百度发布动物语言转换专利,听懂宠物心声
  • 电池管理系统BMS三级架构——BMU、BCU和BAU详解