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

Quartz1.5.2+Spring3.2.1重调度任务首次执行两次问题求助

解决Quartz 1.5.2 + Spring 3.2.1重调度后任务首次执行两次的问题

我先帮你拆解问题根源,再给出适配1.5.2版本的针对性修复方案——毕竟你没法升级版本,咱们就盯着现有环境解决问题。

问题根源分析

你遇到的重复执行问题,主要来自两个方面:

  1. Spring上下文重复加载导致Scheduler实例重复
    看你的web.xml配置,ContextLoaderListener和DispatcherServlet都加载了相同的spring-context-*.xml配置文件。这会导致Spring初始化两个独立的上下文,Quartz的SchedulerFactoryBean被实例化两次——相当于有两个调度器在同时运行任务,重调度后自然会出现重复执行的情况。

  2. Quartz 1.5.2自身的rescheduleJob逻辑缺陷
    在Quartz 1.x版本中,调用rescheduleJob复用原有触发器时,如果旧触发器的下一次触发时间已经临近或到达当前时间,Quartz内部可能会在移除旧触发器前,已经把触发事件放入执行队列;而新触发器添加后,又会生成一次触发事件,最终导致两次执行。即使你设置了concurrent=false,也只是防止任务并发执行,队列里的两次事件还是会依次执行。

修复方案

第一步:优先修复Spring上下文重复加载问题

修改web.xml中DispatcherServlet的配置,让它只加载Spring MVC专属的配置文件,不要和根上下文重复加载Quartz的配置:

<servlet>
    <servlet-name>springmvc</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:spring-mvc.xml</param-value> <!-- 单独存放MVC相关配置,比如控制器、视图解析器等 -->
    </init-param>
</servlet>

根上下文(由ContextLoaderListener加载)负责加载Quartz、业务层等核心配置,子上下文(DispatcherServlet加载)只负责MVC相关配置,这样就避免了Scheduler被实例化两次。

第二步:修改重调度逻辑,避免触发器触发重叠

如果你坚持复用原有触发器,可以在重调度时先暂停触发器,修改表达式后设置延迟启动时间,避免旧触发事件残留:

public void resetJob(String expression){
    System.out.println("****************change to:\t" + expression);
    ApplicationContext applicationContext = WebApplicationContextUtils.getRequiredWebApplicationContext(context);
    Scheduler scheduler = (Scheduler) applicationContext.getBean("testScheduler");
    try {
        CronTriggerBean trigger = (CronTriggerBean) scheduler.getTrigger("testJobTrigger", Scheduler.DEFAULT_GROUP);
        
        // 先暂停触发器,防止在修改过程中触发
        scheduler.pauseTrigger("testJobTrigger", Scheduler.DEFAULT_GROUP);
        
        // 更新cron表达式
        trigger.setCronExpression(expression);
        
        // 设置触发器的启动时间为当前时间1秒后,避免立即触发旧的时间点
        trigger.setStartTime(new Date(System.currentTimeMillis() + 1000));
        
        // 重新调度触发器
        scheduler.rescheduleJob("testJobTrigger", Scheduler.DEFAULT_GROUP, trigger);
        
        // 恢复触发器运行
        scheduler.resumeTrigger("testJobTrigger", Scheduler.DEFAULT_GROUP);
    } catch (SchedulerException e) {
        e.printStackTrace();
    } catch (ParseException e) {
        e.printStackTrace();
    }
}

如果上面的方法还是有问题,你可以改为先删除旧触发器,再创建新的触发器添加进去(你提到这种方式能解决问题,本质是彻底清除了旧触发器的残留事件):

public void resetJob(String expression){
    System.out.println("****************change to:\t" + expression);
    ApplicationContext applicationContext = WebApplicationContextUtils.getRequiredWebApplicationContext(context);
    Scheduler scheduler = (Scheduler) applicationContext.getBean("testScheduler");
    try {
        // 获取旧触发器和对应的JobDetail
        CronTriggerBean oldTrigger = (CronTriggerBean) scheduler.getTrigger("testJobTrigger", Scheduler.DEFAULT_GROUP);
        
        // 先暂停并移除旧触发器
        scheduler.pauseTrigger("testJobTrigger", Scheduler.DEFAULT_GROUP);
        scheduler.unscheduleJob("testJobTrigger", Scheduler.DEFAULT_GROUP);
        
        // 创建新的触发器
        CronTriggerBean newTrigger = new CronTriggerBean();
        newTrigger.setName("testJobTrigger");
        newTrigger.setGroup(Scheduler.DEFAULT_GROUP);
        newTrigger.setJobDetail(oldTrigger.getJobDetail());
        newTrigger.setCronExpression(expression);
        // 同样设置延迟启动,避免立即触发
        newTrigger.setStartTime(new Date(System.currentTimeMillis() + 1000));
        
        // 添加新触发器并启动
        scheduler.scheduleJob(newTrigger);
        scheduler.resumeTrigger("testJobTrigger", Scheduler.DEFAULT_GROUP);
    } catch (SchedulerException e) {
        e.printStackTrace();
    } catch (ParseException e) {
        e.printStackTrace();
    }
}

补充说明

你提到Quartz 2.3.0复用触发器也有这个问题,其实是因为不同版本的Quartz在触发事件队列的处理逻辑上有差异,但2.x版本的优化更多,出现这个问题的概率更低。而1.5.2作为较老的版本,这个缺陷比较明显,所以需要通过手动控制触发器的暂停/延迟启动来规避。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.15 08:27:16