WebLogic热部署时EJB @Timeout方法多次调用问题咨询
这个问题我之前在维护EJB应用的时候踩过一模一样的坑!核心问题出在热部署时旧的Singleton Bean实例没被彻底清理,加上EJB定时器默认持久化的特性,导致多个定时器同时在后台跑,自然会出现同一间隔内多次触发@Timeout的情况,甚至重启服务器也没用——因为旧的持久化定时器会被容器重新激活。
先拆解下具体原因:
- 热部署时,容器通常只会加载新的Bean版本,但旧的Singleton实例并没有被正确销毁,它的定时器还在按原间隔运行;新实例启动后又会创建新的定时器,等于多个定时器同时触发。
- 你的代码里
startTimer方法没有做“是否已有定时器存在”的检查,哪怕是正常部署,如果多次调用这个方法,也会生成多个定时器,热部署后这个问题会被放大。 - 大多数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
相关产品推荐
相关产品推荐

