You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

WebLogic热部署时EJB @Timeout方法多次调用问题咨询

这个问题我之前在维护EJB应用的时候踩过一模一样的坑!核心问题出在热部署时旧的Singleton Bean实例没被彻底清理,加上EJB定时器默认持久化的特性,导致多个定时器同时在后台跑,自然会出现同一间隔内多次触发@Timeout的情况,甚至重启服务器也没用——因为旧的持久化定时器会被容器重新激活。

先拆解下具体原因:

  1. 热部署时,容器通常只会加载新的Bean版本,但旧的Singleton实例并没有被正确销毁,它的定时器还在按原间隔运行;新实例启动后又会创建新的定时器,等于多个定时器同时触发。
  2. 你的代码里startTimer方法没有做“是否已有定时器存在”的检查,哪怕是正常部署,如果多次调用这个方法,也会生成多个定时器,热部署后这个问题会被放大。
  3. 大多数Java EE容器的EJB定时器默认是持久化的,重启服务器时,容器会把之前所有创建的持久化定时器都重新启动,所以重启也解决不了问题。

下面是我当时总结的解决方案,亲测有效:

1. 给Bean加销毁逻辑,清理旧定时器

在Singleton Bean里实现@PreDestroy方法,让Bean被销毁(比如热部署替换时)自动取消所有关联的定时器,避免旧实例的定时器继续跑:

import javax.ejb.*;
@Remote(TimerRemote.class)
@Singleton
public class TimerBean implements TimerRemote {
    @Resource
    private SessionContext context;

    public void startTimer() {
        // 先检查有没有已存在的定时器,避免重复创建
        for (Timer timer : context.getTimerService().getTimers()) {
            // 这里用定时器的info来匹配我们创建的定时器(我们创建时info是null)
            if (timer.getInfo() == null) {
                return; // 已有定时器,直接返回不重复创建
            }
        }
        // 确认没有才创建新定时器
        context.getTimerService().createTimer(2000, 2000, null);
    }

    @Timeout
    public void timeoutCallback(javax.ejb.Timer timer) {
        System.out.println("timeoutCallback is called: " + timer);
    }

    @PreDestroy
    public void cleanupOldTimers() {
        // Bean销毁时,取消所有属于当前实例的定时器
        for (Timer timer : context.getTimerService().getTimers()) {
            try {
                timer.cancel();
                System.out.println("Cleaned up old timer: " + timer);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}
2. 可选:使用非持久化定时器

如果你的业务不需要重启服务器后恢复定时器,可以创建非持久化的定时器,这样重启后旧的定时器就不会被激活了:

// 用TimerConfig设置非持久化
TimerConfig timerConfig = new TimerConfig();
timerConfig.setPersistent(false);
// 创建非持久化的间隔定时器
context.getTimerService().createIntervalTimer(2000, 2000, timerConfig);
3. 清理已存在的残留定时器

如果已经出现了一堆残留的定时器,重启也没用,可以通过容器的管理工具手动清理:

  • 比如WildFly/JBoss可以用CLI命令:
    # 先列出所有EJB定时器
    /subsystem=ejb3/timer-service:list-timers
    # 根据列出的ID删除指定定时器
    /subsystem=ejb3/timer-service:cancel-timer(timer-id="你的定时器ID")
    
  • WebLogic、GlassFish这类容器也有对应的控制台界面,能直接管理和删除EJB定时器。
额外的优化建议
  • 可以给Bean加上@Startup和@PostConstruct,让Bean启动时自动创建定时器,避免手动调用startTimer带来的重复触发问题:
    @Singleton
    @Startup // 容器启动时自动初始化这个Bean
    public class TimerBean implements TimerRemote {
        @PostConstruct
        public void initTimer() {
            // Bean初始化时自动创建定时器,确保只执行一次
            startTimer();
        }
        // ... 其他代码不变
    }
    
  • 建议给定时器设置唯一的info参数(比如传入Bean的类名),这样在清理时能更精准地识别属于当前Bean的定时器,不会误删其他应用的定时器。

内容的提问来源于stack exchange,提问作者Pallavi Mane

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.12 03:56:49