Tomcat部署的JSF项目如何实现服务端启动自动运行Java Timer任务
问题原因
你把Timer初始化逻辑写在了Job1类的main方法里,这个方法只有你手动执行java Job1指令时才会被JVM调用。当项目打包部署到Tomcat后,服务器只会遵循Web规范初始化Servlet、监听器、JSF托管Bean这类Web组件,不会主动执行任意类的main方法,这就是定时任务在服务端不运行的根本原因。
解决方案
方案1:ServletContextListener(最推荐,无额外依赖,兼容所有Tomcat版本)
这个是Java Web原生提供的扩展点,容器启动/关闭应用时会自动触发对应方法,不需要引入任何第三方框架,最适配你当前的JSF+Tomcat技术栈。
- 新建监听器类,代码如下:
import javax.servlet.ServletContextEvent; import javax.servlet.ServletContextListener; import javax.servlet.annotation.WebListener; import java.util.Timer; import java.util.TimerTask; // Tomcat7及以上版本支持@WebListener注解,加了就不需要额外配置web.xml @WebListener public class TimerInitListener implements ServletContextListener { private Timer timer; @Override public void contextInitialized(ServletContextEvent sce) { // 应用启动完成后自动执行该方法,在这里初始化定时任务 risk riskInstance = new risk(); // 构造Timer时传入true标记为守护线程,避免阻塞Tomcat正常关闭 timer = new Timer(true); timer.scheduleAtFixedRate(new TimerTask() { @Override public void run() { try { riskInstance.review_date2(); } catch (Exception e) { e.printStackTrace(); } } }, 0, 24 * 60 * 60 * 1000); // 你需要的24小时间隔,测试时可临时改为1分钟 } @Override public void contextDestroyed(ServletContextEvent sce) { // 应用关闭时主动终止定时器,避免内存泄漏 if (timer != null) { timer.cancel(); } } }
- 如果你的项目用的是Servlet3.0以下版本(不支持注解),在
web.xml中添加如下配置注册监听器:
<listener> <listener-class>替换成你这个监听器类的全限定类名,比如com.xxx.util.TimerInitListener</listener-class> </listener>
方案2:JSF 预初始化托管Bean
如果你不想写Servlet监听器,也可以利用JSF的托管Bean生命周期实现启动自动运行:
import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; import javax.faces.bean.ApplicationScoped; import javax.faces.bean.ManagedBean; import java.util.Timer; import java.util.TimerTask; // eager=true表示容器启动时立刻初始化Bean,不等用户第一次请求 @ManagedBean(eager = true) @ApplicationScoped // 整个应用生命周期内只实例化一次 public class RiskScheduleBean { private Timer timer; @PostConstruct public void initSchedule() { risk riskInstance = new risk(); timer = new Timer(true); timer.scheduleAtFixedRate(new TimerTask() { @Override public void run() { try { riskInstance.review_date2(); } catch (Exception e) { e.printStackTrace(); } } }, 0, 24 * 60 * 60 * 1000); } @PreDestroy public void destroySchedule() { if (timer != null) { timer.cancel(); } } }
注意:必须配置eager=true,否则JSF默认会在Bean第一次被前端/后端请求访问时才初始化,没有请求的话定时任务永远不会启动。
生产环境注意事项
- 不建议长期用
java.util.Timer做生产调度:Timer内部是单线程执行,一旦你的review_date2()方法抛出未捕获的异常,整个Timer线程会直接终止,后续所有定时任务都会停止运行。稳定性要求高的场景可以替换为JDK自带的ScheduledThreadPoolExecutor,或者引入Quartz这类专业调度框架。 - 如果你的定时逻辑需要调用JSF托管Bean、带数据库事务的服务类,不要直接new实例,要从JSF上下文/容器中获取托管的实例,否则会出现依赖注入失效、事务不生效的问题。
- 如果后续项目做集群部署(多台Tomcat同时运行),一定要加分布式锁或者用统一调度入口,避免定时任务在多台机器上重复执行。
内容的提问来源于stack exchange,提问作者Loran
相关产品推荐
相关产品推荐

