SpringMVC之监听器(Listener)

一、概述

Listenerservlet规范中定义的一种特殊类。用于监听servletContextHttpSessionservletRequest等域对象的创建和销毁事件。监听域对象的属性发生修改的事件。用于在事件发生前、发生后做一些必要的处理。一般是获取在线人数等业务需求。

如做完某一件事情以后,需要广播一些消息或者通知,告诉其他的模块进行一些事件处理,一般来说,可以一个一个发送请求去通知,但是有一种更好的方式,那就是事件监听,事件监听也是设计模式中发布-订阅模式、观察者模式的一种实现。

观察者模式:

简单的来讲就是你在做事情的时候身边有人在盯着你,当你做的某一件事情是旁边观察的人感兴趣的事情的时候,他会根据这个事情做一些其他的事,但是盯着你看的人必须要到你这里来登记,否则你无法通知到他(或者说他没有资格来盯着你做事情)。

二、定义监听器

Spring提供了两种实现方式:

  • ApplicationListener接口
  • @EventListener注解
  • SmartApplicationListener接口:继承于ApplicationListener接口,支持事件类型过滤和顺序控制

要想顺利的创建监听器,并起作用,这个过程中需要这样几个角色:

  1. 事件(event)可以封装和传递监听器中要处理的参数,如对象或字符串,并作为监听器中监听的目标。
  2. 监听器(listener)具体根据事件发生的业务处理模块,这里可以接收处理事件中封装的对象或字符串。
  3. 事件发布者(publisher)事件发生的触发者。

三、ApplicationListener接口的实现形式

ApplicationListener接口的定义如下:

java 复制代码
public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {

    /**
     * Handle an application event.
     * @param event the event to respond to
     */
    void onApplicationEvent(E event);
}

它是一个泛型接口,泛型的类型必须是ApplicationEvent及其子类,只要实现了这个接口,那么当容器有相应的事件触发时,就能触发onApplicationEvent方法**(自动去执行)**。ApplicationEvent类的子类有很多.

3.1 简单使用1

使用方法很简单,就是实现一个ApplicationListener接口,并且将加入到容器中就行。

这里监听的是Application启动时候的事情。

java 复制代码
@Component
public class MyApplicationListener implements ApplicationListener<ApplicationEvent> {

    @Override
    public void onApplicationEvent(ApplicationEvent event) {
        System.out.println("事件触发:" + event.getClass().getName());
    }
} 

然后启动自己的springboot项目:

java 复制代码
@SpringBootApplication
@ServletComponentScan
@EnableAsync
@EnableTransactionManagement
public class SpringBootApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringBootApplication.class, args);
    }
}

结果

事件触发非注解的方式:org.springframework.boot.web.servlet.context.ServletWebServerInitializedEvent

3.2 简单使用2 自定义事件和监听器

定义事件

首先,我们需要定义一个事件(MyTestEvent),需要继承SpringApplicationEvent

java 复制代码
public class MyTestEvent extends ApplicationEvent {

    private String msg;

    public MyTestEvent(Object source, String msg) {
        super(source);
        this.msg = msg;
    }

    public String getMsg() {
        return msg;
    }

    public void setMsg(String msg) {
        this.msg = msg;
    }
}

定义监听器

需要定义一下监听器,自己定义的监听器需要实现ApplicationListener,同时泛型参数要加上自己要监听的事件Class名,在重写的方法onApplicationEvent中,添加自己的业务处理:

当监听到具体的时间的时候,会自动调用onApplication方法

java 复制代码
@Component
public class MyNoAnnotationListener implements ApplicationListener<MyTestEvent> {

    @Override
    public void onApplicationEvent(MyTestEvent event) {
        System.out.println("非注解监听器:" + event.getMsg());
    }
}

事件发布

有了事件,有了事件监听者,那么什么时候触发这个事件呢?

每次想让监听器收到事件通知的时候,就可以调用一下事件发布的操作。首先在类里自动注入ApplicationEventPublisher,这个也就是我们的ApplicationContext,它实现了这个接口。

java 复制代码
@Component
public class MyTestEventPublisher {
    @Autowired
    private ApplicationEventPublisher applicationEventPublisher;

    /**
     *  事件发布方法
     */
    public void pushListener(String msg) {
        applicationEventPublisher.publishEvent(new MyTestEvent(this, msg));
    }
}

测试

用一个http请求来模拟

java 复制代码
@RestController
public class TestEventListenerController1 {

    @Autowired
    private MyTestEventPubLisher publisher;

    @RequestMapping(value = "/test/testPublishEvent111")
    public void testPublishEvent() {
        publisher.pushListener("我来了!");
    }
}

启动项目,可以看到控制台输出,测试完成:

text 复制代码
事件触发:com.test.personal.unannotation.MyTestEvent // 这个是上面那个事件监控的的输出
非注解监听器:我来了!

四、EventLister的使用

4.1 使用实例

新建事件类PersonUpdateEvent

java 复制代码
@Component
@Data
@NoArgsConstructor
@AllArgsConstructor
public class PersonUpdateEvent {
    private Integer id;
    private String name;
    private Integer age;
}

PersonSaveEvent

java 复制代码
@Component
@Data
@NoArgsConstructor
@AllArgsConstructor
public class PersonSaveEvent {
    private Integer id;
    private String name;
    private Integer age;
}

4.2.1 单一事件监听器

发布事件

java 复制代码
@Service
public class EventPublisher {

    private ApplicationEventPublisher eventPublisher;

    @Autowired
    public void setEventPublisher(ApplicationEventPublisher eventPublisher) {
        this.eventPublisher = eventPublisher;
    }

    public void publishPersonSaveEvent() {
        PersonSaveEvent saveEvent = new PersonSaveEvent();
        saveEvent.setId(1);
        saveEvent.setName("i余数");
        saveEvent.setAge(18);
        eventPublisher.publishEvent(saveEvent);
    }
}

监听事件

java 复制代码
@Slf4j
@Service
public class EventListenerService {

    @EventListener
    public void handleForPersonSaveEvent(PersonSaveEvent saveEvent) {
        log.info("saveEvent -> {}", saveEvent);
    }
}

结果

text 复制代码
saveEvent -> PersonSaveEvent(id=1, name=i余数, age=18)

4.2.2 使用classes实现多事件监听器

发布事件

在上一个示例的基础上,再多加一个PersonUpdateEvent事件。

java 复制代码
public void publishPersonUpdateEvent() {
    PersonUpdateEvent updateEvent = new PersonUpdateEvent();
    updateEvent.setId(1);
    updateEvent.setName("i余数");
    updateEvent.setAge(19);
    eventPublisher.publishEvent(updateEvent);
}

监听事件

java 复制代码
@EventListener(classes = {PersonSaveEvent.class, PersonUpdateEvent.class})
public void handleForPersonSaveAndUpdateEvent(Object event) {
    log.info("multi handle event -> {}", event);
}

验证结果

可以监听到多个事件

text 复制代码
multi handle event -> PersonSaveEvent(id=1, name=i余数, age=18)
multi handle event -> PersonUpdateEvent(id=1, name=i余数, age=19)

4.2.3 使用condition筛选监听的事件

可以通过condition属性指定一个SpEL表达式,如果返回"true", "on", "yes", or "1"中的任意一个,则事件会被处理,否则不会。

发布事件

java 复制代码
public void publishPersonSaveEvent() {
    PersonSaveEvent saveEvent = new PersonSaveEvent();
    saveEvent.setId(1);
    saveEvent.setName("i余数");
    saveEvent.setAge(18);
    eventPublisher.publishEvent(saveEvent);

    PersonSaveEvent saveEvent2 = new PersonSaveEvent();
    saveEvent2.setId(2);
    saveEvent2.setName("i余数");
    saveEvent2.setAge(18);
    eventPublisher.publishEvent(saveEvent2);
}

监听事件

java 复制代码
public void publishPersonSaveEvent() {
    PersonSaveEvent saveEvent = new PersonSaveEvent();
    saveEvent.setId(1);
    saveEvent.setName("i余数");
    saveEvent.setAge(18);
    eventPublisher.publishEvent(saveEvent);

    PersonSaveEvent saveEvent2 = new PersonSaveEvent();
    saveEvent2.setId(2);
    saveEvent2.setName("i余数");
    saveEvent2.setAge(18);
    eventPublisher.publishEvent(saveEvent2);
}

结果验证

id2的事件不满足条件,所以不会执行。

text 复制代码
只处理id等于1的 -> PersonSaveEvent(id=1, name=i余数, age=18)

4.3 有返回值的监听器

4.3.1 返回一个单一对象

发布事件

java 复制代码
public void publishPersonSaveEvent() {
    PersonSaveEvent saveEvent = new PersonSaveEvent();
    saveEvent.setId(1);
    saveEvent.setName("i余数");
    saveEvent.setAge(18);
    eventPublisher.publishEvent(saveEvent);
}

监听事件

java 复制代码
@EventListener
public void handleForPersonUpdateEvent(PersonUpdateEvent updateEvent) {
    log.info("handle update event -> {}", updateEvent);
}

@EventListener
public PersonUpdateEvent handleHaveReturn(PersonSaveEvent saveEvent) {
    log.info("handle save event -> {}", saveEvent);
    PersonUpdateEvent updateEvent = new PersonUpdateEvent();
    updateEvent.setId(saveEvent.getId());
    updateEvent.setName(saveEvent.getName());
    updateEvent.setAge(saveEvent.getAge());
    return updateEvent;
}

验证结果

可以看到我们监听到了2个事件,PersonSaveEvent是我们主动发布的事件,PersonUpdateEventhandleHaveReturn方法的返回值,会被Spring自动当作一个事件被发送。

text 复制代码
handle save event -> PersonSaveEvent(id=1, name=i余数, age=18)
handle update event -> PersonUpdateEvent(id=1, name=i余数, age=18)

4.3.2 返回一个集合

将监听器稍作修改,使其返回一个集合。

java 复制代码
@EventListener
public List<PersonUpdateEvent> handleHaveReturn(PersonSaveEvent saveEvent) {
    log.info("handle save event -> {}", saveEvent);
    List<PersonUpdateEvent> events = new ArrayList<>();
    PersonUpdateEvent updateEvent = new PersonUpdateEvent();
    updateEvent.setId(saveEvent.getId());
    updateEvent.setName(saveEvent.getName());
    updateEvent.setAge(saveEvent.getAge());
    events.add(updateEvent);

    PersonUpdateEvent updateEvent2 = new PersonUpdateEvent();
    BeanUtils.copyProperties(updateEvent, updateEvent2);
    events.add(updateEvent2);
    return events;
}

查看结果可以发现,集合中的每个对象都被当作一个单独的事件进行发送。

text 复制代码
handle save event -> PersonSaveEvent(id=1, name=i余数, age=18)
handle update event -> PersonUpdateEvent(id=1, name=i余数, age=18)
handle update event -> PersonUpdateEvent(id=1, name=i余数, age=18)

4.3.3 返回一个数组

和返回值为集合一样,数组中的每个对象都被当作一个单独的事件进行发送。

4.4 异步监听器

创建两个监听器,一个同步一个异步,异步监听器就是在方法上加一个@Async注解,但需要注意以下限制:

  • 监听器报错不会传递给事件发起者,因为双方已经不在同一个线程了。
  • 异步监听器的非空返回值不会被当作新的事件发布。如果需要发布新事件,需要注入ApplicationEventPublisher后手动发布。
java 复制代码
@SpringBootApplication
@ServletComponentScan
@EnableAsync
@EnableTransactionManagement
public class SpringBootApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringBootApplication.class, args);
    }
}
@EventListener
public void handleForPersonSaveEvent(PersonSaveEvent saveEvent) {
    log.info("handle event -> {}", saveEvent);
}

@Async
@EventListener
public void handleForPersonSaveEventAsync(PersonSaveEvent saveEvent) {
    log.info("async handle event -> {}", saveEvent);
}

从执行结果可以看出,异步线程是task-1,不是主线程main,即异步是生效的。

text 复制代码
handle event -> PersonSaveEvent(id=1, name=i余数, age=18)
async handle event -> PersonSaveEvent(id=1, name=i余数, age=18)

4.5 监听器异常处理

4.5.1 同步异常处理

使用SimpleApplicationEventMulticaster处理同步监听器抛出异常。先定义一个ErrorHandler:

java 复制代码
@Slf4j
@Component
public class MyErrorHandler implements ErrorHandler {
    @Override
    public void handleError(Throwable t) {
        log.info("handle error -> {}", t.getClass());
    }
}

将自定义ErrorHandler绑定到SimpleApplicationEventMulticaster这里涉及一个@PostConstruce

java 复制代码
@Slf4j
@Service
public class EventListenerService {

    @Autowired
    private SimpleApplicationEventMulticaster simpleApplicationEventMulticaster;

    @Autowired
    private MyErrorHandler errorHandler;

    @PostConstruct
    public void init() {
        simpleApplicationEventMulticaster.setErrorHandler(errorHandler);
    }

    @Order(1)
    @EventListener
    public void handleForPersonSaveEvent(PersonSaveEvent saveEvent) throws AuthException {
        log.info("handle event -> {}", saveEvent);
        throw new AuthException("test exception");
    }
}

结果:可以看到捕获的异常是UndeclaredThrowableException

text 复制代码
handle event -> PersonSaveEvent(id=1, name=i余数, age=18)
handle error -> class java.lang.reflect.UndeclaredThrowableException

4.5.2 异步异常处理

使用SimpleAsyncUncaughtExceptionHandler来处理@Async抛出的异常。

java 复制代码
@Configuration
public class AsyncConfig implements AsyncConfigurer {
    @Override
    public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
        return new SimpleAsyncUncaughtExceptionHandler();
    }
}

监听器代码:人为的抛出一个异常。

java 复制代码
@Async
@EventListener
public void handleForPersonSaveEvent(PersonSaveEvent saveEvent) throws AuthException {
    log.info("handle event -> {}", saveEvent);
    throw new AuthException("test exception");
}

结果:SimpleAsyncUncaughtExceptionHandler捕获到了@Async方法抛出的异常

text 复制代码
INFO 4416 - [task-1] i.k.s.e.listener.EventListenerService: handle event -> PersonSaveEvent(id=1, name=i余数, age=18)
ERROR 4416 - [task-1] .a.i.SimpleAsyncUncaughtExceptionHandler: Unexpected exception occurred invoking async method: 
    public void xxxx.handleForPersonSaveEvent(xxxx.PersonSaveEvent) throws javax.security.auth.message.AuthException

4.6 监听器排序

如果同时有多个监听器监听同一个事件,默认情况下监听器的执行顺序是随机的,如果想要他们按照某种顺序执行,可以借助Spring的另外一个注解@Order实现。

创建三个监听器,并使用@Order排好序。

java 复制代码
@Order(1)
@EventListener
public void handleForPersonSaveEvent(PersonSaveEvent saveEvent) {
    log.info("handle event1 -> {}", saveEvent);
}

@Order(2)
@EventListener
public void handleForPersonSaveEvent2(PersonSaveEvent saveEvent) {
    log.info("handle event2 -> {}", saveEvent);
}

@Order(3)
@EventListener
public void handleForPersonSaveEvent3(PersonSaveEvent saveEvent) {
    log.info("handle event3 -> {}", saveEvent);
}

从执行结果可以看到,确实是按照@Order中指定的顺序执行的。

text 复制代码
handle event1 -> PersonSaveEvent(id=1, name=i余数, age=18)
handle event2 -> PersonSaveEvent(id=1, name=i余数, age=18)
handle event3 -> PersonSaveEvent(id=1, name=i余数, age=18)

五、使用场景

5.1 应用启动时缓存预热(系统事件监听)

需求描述: 在应用启动完成后,自动加载热门商品数据到Redis缓存,提升接口响应速度。

java 复制代码
@Component
public class CacheWarmUpListener {

    private final ProductService productService;
    private final RedisTemplate<String, Product> redisTemplate;

    @Autowired
    public CacheWarmUpListener(ProductService productService,
                               RedisTemplate<String, Product> redisTemplate) {
        this.productService = productService;
        this.redisTemplate = redisTemplate;
    }

    @EventListener(ApplicationReadyEvent.class)
    public void warmUpCache() {
        List<Product> hotProducts = productService.getTop100HotProducts();
        hotProducts.forEach(product ->
                redisTemplate.opsForValue().set("product:" + product.getId(), product));

        System.out.println("=== 已预热" + hotProducts.size() + "条商品数据到Redis ===");
    }
}

关键点说明:

  • 使用ApplicationReadyEvent而非ApplicationStartedEvent,确保数据库连接等基础设施已就绪
  • 通过构造函数注入依赖,避免字段注入的循环依赖问题
  • 预热数据量较大时建议采用分页异步加载

5.2 订单创建后发送多平台通知(自定义事件)

需求描述: 当订单创建成功后,需要同时发送短信通知用户、邮件通知客服、更新ERP系统库存。

步骤1:定义自定义事件

java 复制代码
public class OrderCreatedEvent extends ApplicationEvent {
    private final Order order;

    public OrderCreatedEvent(Object source, Order order) {
        super(source);
        this.order = order;
    }

    public Order getOrder() {
        return order;
    }
}

步骤2:在Service中发布事件

java 复制代码
@Service
public class OrderService {

    private final ApplicationEventPublisher eventPublisher;

    @Autowired
    public OrderService(ApplicationEventPublisher eventPublisher) {
        this.eventPublisher = eventPublisher;
    }

    @Transactional
    public Order createOrder(OrderCreateRequest request) {
        Order newOrder = createNewOrder();
        eventPublisher.publishEvent(new OrderCreatedEvent(this, newOrder));
        return newOrder;
    }
}

步骤3:多监听器处理事件

java 复制代码
@Component
public class OrderNotificationListener {

    // 短信通知(最高优先级)
    @EventListener
    @Order(Ordered.HIGHEST_PRECEDENCE)
    public void sendSms(OrderCreatedEvent event) {
        Order order = event.getOrder();
        SmsService.send(order.getUserPhone(),
                "您的订单#" + order.getId() + "已创建,金额:" + order.getAmount());
    }

    // 邮件通知(异步处理)
    @Async
    @EventListener
    public void sendEmail(OrderCreatedEvent event) {
        Order order = event.getOrder();
        EmailTemplate template = EmailTemplate.buildOrderConfirm(order);
        EmailService.send(template);
    }

    // ERP系统库存更新(条件过滤)
    @EventListener(condition = "#event.order.items.?[isPhysicalProduct].size() > 0")
    public void updateErpInventory(OrderCreatedEvent event) {
        ERPInventoryService.updateStock(event.getOrder().getItems());
    }
}

配置异步支持:

java 复制代码
@Configuration
@EnableAsync
public class AsyncConfig {

    @Bean(name = "notificationTaskExecutor")
    public Executor taskExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(5);
        executor.setMaxPoolSize(10);
        executor.setQueueCapacity(100);
        executor.setThreadNamePrefix("Notification-");
        executor.initialize();
        return executor;
    }
}

优势:

  • 解耦核心业务与通知逻辑
  • 通过@Order控制短信优先于邮件发送
  • 使用@Async避免邮件发送阻塞主线程
  • 条件表达式跳过虚拟商品库存更新

5.3 全局请求耗时统计(ServletRequestListener)

需求描述: 统计所有API请求的处理时间,识别慢接口。

java 复制代码
@Component
public class RequestMetricsListener implements ServletRequestListener {

    private static final ThreadLocal<Long> startTimeHolder = new ThreadLocal<>();

    @Override
    public void requestInitialized(ServletRequestEvent sre) {
        startTimeHolder.set(System.currentTimeMillis());
    }

    @Override
    public void requestDestroyed(ServletRequestEvent sre) {
        long startTime = startTimeHolder.get();
        long duration = System.currentTimeMillis() - startTime;

        HttpServletRequest request = (HttpServletRequest) sre.getServletRequest();
        String endpoint = request.getRequestURI();
        String method = request.getMethod();

        MetricsService.recordRequestMetrics(endpoint, method, duration);

        // 慢请求预警
        if (duration > 3000) {
            AlarmService.notifySlowRequest(endpoint, method, duration);
        }

        startTimeHolder.remove();
    }
}

注册监听器:

java 复制代码
@Bean
public ServletListenerRegistrationBean<RequestMetricsListener> metricsListener() {
    return new ServletListenerRegistrationBean<>(new RequestMetricsListener());
}

统计结果示例:

text 复制代码
GET /api/products 平均耗时 45ms | 95分位 120ms
POST /api/orders 平均耗时 250ms | 最大耗时 3200ms(需优化)

5.4 应用优雅停机(ContextClosedEvent)

需求描述:在应用关闭时,确保完成:1)停止接收新请求 2)等待进行中的任务完成 3)释放资源。

java 复制代码
@Component
public class GracefulShutdownListener implements ApplicationListener<ContextClosedEvent> {

    private final ThreadPoolTaskExecutor taskExecutor;
    private final DataSource dataSource;

    @Autowired
    public GracefulShutdownListener(ThreadPoolTaskExecutor taskExecutor,
                                    DataSource dataSource) {
        this.taskExecutor = taskExecutor;
        this.dataSource = dataSource;
    }

    @Override
    public void onApplicationEvent(ContextClosedEvent event) {
        // 1. 关闭线程池
        shutdownExecutor(taskExecutor);

        // 2. 关闭数据库连接池
        if (dataSource instanceof HikariDataSource) {
            ((HikariDataSource) dataSource).close();
        }

        // 3. 其他清理工作...
        System.out.println("=== 资源释放完成,应用安全退出 ===");
    }

    private void shutdownExecutor(ExecutorService executor) {
        executor.shutdown();
        try {
            if (!executor.awaitTermination(30, TimeUnit.SECONDS)) {
                executor.shutdownNow();
            }
        } catch (InterruptedException e) {
            executor.shutdownNow();
            Thread.currentThread().interrupt();
        }
    }
}

停机流程**:**

  1. 收到SIGTERM信号
  2. 关闭新请求入口
  3. 等待30秒处理进行中请求
  4. 强制关闭剩余任务
  5. 释放数据库连接池
  6. 应用退出

5.5 分布式锁异常恢复

需求描述: 当获取Redis分布式锁失败时,触发重试机制并记录竞争情况。

自定义事件

java 复制代码
public class LockAcquireFailedEvent extends ApplicationEvent {
    private final String lockKey;
    private final int retryCount;

    public LockAcquireFailedEvent(Object source, String lockKey, int retryCount) {
        super(source);
        this.lockKey = lockKey;
        this.retryCount = retryCount;
    }

    // getters...
}

事件发布

java 复制代码
public class DistributedLock {

    private final ApplicationEventPublisher eventPublisher;

    public boolean tryLock(String key, int maxRetries) {
        int attempts = 0;
        while (attempts < maxRetries) {
            if (RedisClient.acquireLock(key)) {
                return true;
            }
            attempts++;
            eventPublisher.publishEvent(new LockAcquireFailedEvent(this, key, attempts));
            Thread.sleep(100 * attempts);
        }
        return false;
    }
}

监听处理

java 复制代码
@Component
public class LockFailureHandler {

    private static final Map<String, AtomicInteger> LOCK_CONTENTION = new ConcurrentHashMap<>();

    @EventListener
    public void handleLockFailure(LockAcquireFailedEvent event) {
        String lockKey = event.getLockKey();
        LOCK_CONTENTION.computeIfAbsent(lockKey, k -> new AtomicInteger(0))
                .incrementAndGet();

        // 竞争激烈时动态调整策略
        if (event.getRetryCount() > 3) {
            adjustBackoffStrategy(lockKey);
        }
    }

    @Scheduled(fixedRate = 10_000)
    public void reportContention() {
        LOCK_CONTENTION.forEach((key, count) ->
                MetricsService.recordLockContention(key, count.get()));
    }

    private void adjustBackoffStrategy(String key) {
        // 动态增加等待时间或告警
    }
}

六、总结

  • 过滤器:用于属性甄别,对象收集(不可改变过滤对象的属性和行为)
  • 监听器:用于对象监听,行为记录(不可改变监听对象的属性和行为)
  • 拦截器:用于对象拦截,行为干预(可以改变拦截对象的属性和行为)
相关推荐
爱勇宝2 小时前
第 1 章:别把“需求文档”当成真正的需求
前端·后端·程序员
IT_陈寒7 小时前
闭包陷阱让我加了两天班,JavaScript你真行
前端·人工智能·后端
易协同低代码8 小时前
通达OA核心类库TD类深度解析
后端
Gopher_HBo8 小时前
Go语言学习笔记(十八)Gin处理Session
后端
谭光志9 小时前
工具塞满上下文窗口怎么办?深度拆解 AI Agent Tool Search 按需加载实现原理
前端·后端·ai编程
她说..9 小时前
Java 默认值设置方式
java·开发语言·后端·springboot
foggyprojects9 小时前
从0开始,一句话启动AI DataAgent
后端·数据分析·ai编程
郡杰9 小时前
一些基础和问题解决
后端
陈随易9 小时前
前端项目部署只要30秒
前端·后端·程序员
YIAN9 小时前
从零手写文件读取 MCP 服务:一文吃透 Model Context Protocol 全链路通信原理
前端·后端·mcp