[整理]在Spring MVC中使用Quartz实现定时任务动态管理

项目应用中有许多定时任务,当需要修改定时器时,往往需要停服务,这不是我们想要的。

于是动态管理项目中的定时任务就成了要解决的问题。

项目原来的定时任务是直接使用spring自己的scheduled-tasks实现的,因为是无状态的,没法满足我们的需求。

需要改造原来的定时任务,实现StatefulMethodInvokingJob类来解决。

大概的思路是把定时任务的参数数据保存到数据库,应用启动的时候从数据库读取配置它们,通过页面来实时启动和关闭它们。

项目比较老,使用的quartz-all-1.6.jar,spring用的是4.0.5

1、首先定义entity和数据库表,实现数据库的CRUD,这里列出entity其它的很简单就不列出来了

public class QuartzJobModel {
    private String jobId;
    private String jobName;
    private String targetClassName;
    private String cronExpression;
    private String jobStatus;
    private String runOnHoliday;
    private String jobDesc;
    private Date createTime;
    private String createrName;
    private Date updateTime;
    private String updaterName;
    // getter &setter
}

2、改造定时任务,这里我们新建两个定时任务,一个打印三角形,一个打印菱形

public class PrintRhombusJob extends StatefulMethodInvokingJob {
    private static final Logger logger = Logger.getLogger(PrintRhombusJob.class);

    /**
     * @see org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean.MethodInvokingJob#executeInternal(org.quartz.JobExecutionContext)
     */

    @Override
    protected void executeInternal(JobExecutionContext cts) throws JobExecutionException {
        logger.info("================== print rhombus job begin =================");
        System.out.println("print rhombus");
        logger.info("================== print rhombus job end =================");
    }

}

public class PrintTraingleJob extends StatefulMethodInvokingJob {
    private static final Logger logger = Logger.getLogger(PrintTraingleJob.class);

    /**
     * @see org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean.MethodInvokingJob#executeInternal(org.quartz.JobExecutionContext)
     */

    @Override
    protected void executeInternal(JobExecutionContext ctx) throws JobExecutionException {
        logger.info("================== print traingle job begin =================");
        System.out.println("print traingle");
        logger.info("================== print traingle job end =================");
    }

}

3、编写定时任务管理器,实现对定时任务的开关

@Component("quartzJobManager")
public class QuartzJobManager {
    private static final Logger logger = Logger.getLogger(QuartzJobManager.class);
    @Autowired
    private SchedulerFactoryBean schedulerFactoryBean;
    @Autowired
    private QuartzJobDAO quartzJobDAO;
    /** 默认任务组名称 */
    private static final String DEFAULT_JOB_GROUP_NAME = "DefaultJobGroup";
    /** 默认触发器组名称 */
    private static final String DEFAULT_TRIGGER_GROUP_NAME = "DefaultTriggerGroup";

    /**
     * 加载数据库中已定义的定时任务
     * 
     * @Title: loadJobs
     * @Description: 加载数据库中已定义的定时任务
     */
    public void loadJobs() {
        List<QuartzJobModel> jobs = quartzJobDAO.getAll();
        if (null != jobs && !jobs.isEmpty()) {
            for (QuartzJobModel job : jobs) {
                addJob(job);
            }
        }
    }

    /**
     * 重新加载数据库中已定义的定时任务
     * 
     * @Title: reloadJobs
     * @Description: 重新加载数据库中已定义的定时任务
     */
    public void reloadJobs() {
        removeAll();
        loadJobs();
    }   

    /**
     * 添加一个新的定时任务
     * 
     * @Title: addJob
     * @Description: 添加一个新的定时任务
     * @param job QuartzJobModel
     */
    public void addJob(QuartzJobModel job) {
        Scheduler scheduler = schedulerFactoryBean.getScheduler();
        try {
            // 使用"jobName+默认任务组名称"作为定时任务的Key
            JobDetail jobDetail = new JobDetail(job.getJobName(), DEFAULT_JOB_GROUP_NAME,
                Class.forName(job.getTargetClassName()));
            // 使用"jobName+默认触发器组名称"作为定时任务触发器的Key
            CronTrigger trigger = new CronTrigger(job.getJobName(), DEFAULT_TRIGGER_GROUP_NAME,
                job.getCronExpression());
            scheduler.scheduleJob(jobDetail, trigger);
            logger.info("******注册定时任务:" + job + " ******");
            if (!scheduler.isShutdown()) {
                scheduler.start();
            }
        } catch (ClassNotFoundException e) {
            logger.error("注册定时任务失败:" + job + e.getMessage());
            e.printStackTrace();
        } catch (ParseException e) {
            logger.error("注册定时任务失败:" + job + e.getMessage());
            e.printStackTrace();
        } catch (SchedulerException e) {
            logger.error("注册定时任务失败:" + job + e.getMessage());
            e.printStackTrace();
        }

    }

    /**
     * 修改定时任务
     * 
     * @Title: modifyJob
     * @Description: 修改定时任务
     * @param job QuartzJobModel
     */
    public void modifyJob(QuartzJobModel job) {
        removeJob(job);
        addJob(job);
    }

    /**
     * 删除定时任务
     * 
     * @Title: removeJob
     * @Description: 删除定时任务
     * @param job QuartzJobModel
     */
    public void removeJob(QuartzJobModel job) {
        Scheduler scheduler = schedulerFactoryBean.getScheduler();
        try {
            // 根据""暂停触发器
            scheduler.pauseTrigger(job.getJobName(), DEFAULT_TRIGGER_GROUP_NAME);
            // 根据""移除触发器
            scheduler.unscheduleJob(job.getJobName(), DEFAULT_TRIGGER_GROUP_NAME);
            // 根据""删除定时任务
            scheduler.deleteJob(job.getJobName(), DEFAULT_JOB_GROUP_NAME);
            logger.info("******删除定时任务:" + job + " ******");
        } catch (SchedulerException e) {
            logger.error("移除定时任务失败:" + job + e.getMessage());
            e.printStackTrace();
        }
    }

    /**
     * 移除所有定时任务
     * 
     * @Title: removeAll
     * @Description: 移除所有定时任务
     */
    public void removeAll() {
        List<QuartzJobModel> jobs = quartzJobDAO.getAll();
        if (null != jobs && !jobs.isEmpty()) {
            for (QuartzJobModel job : jobs) {
                removeJob(job);
            }
        }
    }

    /**
     * 暂停定时任务
     * 
     * @Title: pauseJob
     * @Description: 暂停定时任务
     * @param job QuartzJobModel
     */
    public void pauseJob(QuartzJobModel job) {
        Scheduler scheduler = schedulerFactoryBean.getScheduler();
        try {
            scheduler.pauseJob(job.getJobName(), DEFAULT_JOB_GROUP_NAME);
            logger.info("******暂停定时任务:" + job + " ******");
        } catch (SchedulerException e) {
            logger.error("暂停定时任务失败:" + job + e.getMessage());
            e.printStackTrace();
        }
    }

    /**
     * 恢复定时任务
     * 
     * @Title: resumeJob
     * @Description: 恢复定时任务
     * @param job QuartzJobModel
     */
    public void resumeJob(QuartzJobModel job) {
        Scheduler scheduler = schedulerFactoryBean.getScheduler();
        try {
            scheduler.resumeJob(job.getJobName(), DEFAULT_JOB_GROUP_NAME);
            logger.info("******恢复定时任务:" + job + " ******");
        } catch (SchedulerException e) {
            logger.error("恢复定时任务失败:" + job + e.getMessage());
            e.printStackTrace();
        }
    }

    /**
     * 立刻执行一次任务
     * 
     * @Title: triggerJob
     * @Description: 立刻执行一次任务
     * @param job QuartzJobModel
     */
    public void triggerJob(QuartzJobModel job) {
        Scheduler scheduler = schedulerFactoryBean.getScheduler();
        try {
            scheduler.triggerJob(job.getJobName(), DEFAULT_JOB_GROUP_NAME);
            logger.info("******立刻执行定时任务:" + job + " ******");
        } catch (SchedulerException e) {
            logger.error(" 立刻执行定时任务失败:" + job + e.getMessage());
            e.printStackTrace();
        }
    }
}

4、编写Contoller和页面,实现Web化管理,这里页面就不贴了

@Controller
@RequestMapping(value = "/jobs")
public class JobController extends BaseController {
    private static Logger logger = Logger.getLogger(JobController.class);

    @Autowired
    private QuartzJobService quartzJobService;
    @Autowired
    private QuartzJobManager quartzJobManager;

    @RequestMapping(value = "/list")
    public String list() {
        return "list";
    }

    @RequestMapping(value = "/queryListByPage")
    @RunningControllerLog(description = "分页查询")
    public void queryListByPage(HttpServletRequest request, HttpServletResponse response) {
        Map<String, Object> params = HttpServletUtils
            .parseReqToSearchCondition(new String[] { "jobStatus", "runOnHoliday", "jobName" }, request);
        // 权限条件追加
        PermUtils.appendPermParams(params);

        PageModel<QuartzJobModel> pageModel = new PageModel<>(request);
        pageModel.setSearchCdtns(params);
        try {
            pageModel = quartzJobService.findListByPage(pageModel);
            returnJSONData(response, pageModel.toJSONString());
        } catch (Exception e) {
            logger.error("分页查询异常,异常信息:" + e);
        }
    }

    @RequestMapping(value = "/toAdd")
    public String toAdd() {
        return "add";
    }

    @RequestMapping(value = "/add",
        method = RequestMethod.POST)
    public void add(HttpServletRequest request, HttpServletResponse response) {
        String jobName = request.getParameter("jobName");
        String targetClassName = request.getParameter("targetClassName");
        String cronExpression = request.getParameter("cronExpression");
        String runOnHoliday = request.getParameter("runOnHoliday");
        String jobDesc = request.getParameter("jobDesc");

        Date date = new Date();
        SysUser user = getSysUser(request);
        String userName = user.getUserName();

        QuartzJobModel job = new QuartzJobModel();
        job.setJobName(jobName);
        job.setTargetClassName(targetClassName);
        job.setCronExpression(cronExpression);
        if (!StringUtil.isEmpty(jobDesc)) {
            job.setJobDesc(jobDesc);
        } else {
            job.setJobDesc("");
        }
        job.setRunOnHoliday(runOnHoliday);
        job.setJobStatus(JobStatusEnum.RUNNING.getValue());
        job.setCreaterName(userName);
        job.setCreateTime(date);
        job.setUpdaterName(userName);
        job.setUpdateTime(date);

        quartzJobManager.addJob(job);
        logger.info("^^^^^^^(add " + job + " by " + getSysUser(request).getUserName() + ")^^^^^^");
        int result = quartzJobService.save(job);
        returnJSONData(response, JSON.toJSONString(result));
    }

    @RequestMapping(value = "/toEdit/{jobId}")
    public String toEdit(HttpServletRequest request, HttpServletResponse response,
        @PathVariable("jobId") String jobId) {
        QuartzJobModel job = quartzJobService.getById(jobId);
        request.setAttribute("item", job);
        return "edit";
    }

    @RequestMapping(value = "/edit",
        method = RequestMethod.POST)
    public void edit(HttpServletRequest request, HttpServletResponse response) {
        String jobId = request.getParameter("jobId");
        String cronExpression = request.getParameter("cronExpression");
        String runOnHoliday = request.getParameter("runOnHoliday");
        String jobDesc = request.getParameter("jobDesc");

        Date date = new Date();
        SysUser user = getSysUser(request);
        String userName = user.getUserName();

        QuartzJobModel job = quartzJobService.getById(jobId);

        job.setCronExpression(cronExpression);
        if (!StringUtil.isEmpty(jobDesc)) {
            job.setJobDesc(jobDesc);
        } else {
            job.setJobDesc("");
        }
        job.setRunOnHoliday(runOnHoliday);
        job.setUpdaterName(userName);
        job.setUpdateTime(date);
        int result = quartzJobService.update(job);
        job = quartzJobService.getById(jobId);
        quartzJobManager.modifyJob(job);
        logger.info("^^^^^^^(edit " + job + " by " + getSysUser(request).getUserName() + ")^^^^^^");
        
        returnJSONData(response, JSON.toJSONString(result));
    }

    @RequestMapping(value = "/pause/{jobId}",
        method = RequestMethod.POST)
    public void pause(HttpServletRequest request, HttpServletResponse response, @PathVariable("jobId") String jobId) {
        Date date = new Date();
        SysUser user = getSysUser(request);
        String userName = user.getUserName();

        QuartzJobModel job = quartzJobService.getById(jobId);

        job.setJobStatus(JobStatusEnum.PAUSED.getValue());
        job.setUpdaterName(userName);
        job.setUpdateTime(date);

        quartzJobManager.pauseJob(job);
        logger.info("^^^^^^^(pause " + job + " by " + getSysUser(request).getUserName() + ")^^^^^^");
        int result = quartzJobService.update(job);
        returnJSONData(response, JSON.toJSONString(result));
    }

    @RequestMapping(value = "/resume/{jobId}",
        method = RequestMethod.POST)
    public void resume(HttpServletRequest request, HttpServletResponse response, @PathVariable("jobId") String jobId) {
        Date date = new Date();
        SysUser user = getSysUser(request);
        String userName = user.getUserName();

        QuartzJobModel job = quartzJobService.getById(jobId);
        if (job.getJobStatus().equals(JobStatusEnum.PAUSED.getValue())) {
            quartzJobManager.resumeJob(job);
        } else if (job.getJobStatus().equals(JobStatusEnum.STOPPED.getValue())) {
            quartzJobManager.addJob(job);
        }
        logger.info("^^^^^^^(sesume " + job + " by " + getSysUser(request).getUserName() + ")^^^^^^");
        job.setJobStatus(JobStatusEnum.RUNNING.getValue());
        job.setUpdaterName(userName);
        job.setUpdateTime(date);
        int result = quartzJobService.update(job);
        returnJSONData(response, JSON.toJSONString(result));
    }

    @RequestMapping(value = "/run/{jobId}",
        method = RequestMethod.POST)
    public void runJob(HttpServletRequest request, HttpServletResponse response, @PathVariable("jobId") String jobId) {
        QuartzJobModel job = quartzJobService.getById(jobId);
        logger.info("^^^^^^^(run " + job + " by " + getSysUser(request).getUserName() + ")^^^^^^");
        quartzJobManager.triggerJob(job);
    }

    @RequestMapping(value = "/reloadJobs",
        method = RequestMethod.POST)
    public void reloadJobs(HttpServletRequest request, HttpServletResponse response) {
        logger.info("^^^^^^^(reload jobs by " + getSysUser(request).getUserName() + ")^^^^^^");
        quartzJobManager.reloadJobs();
    }

    @RequestMapping(value = "/remove/{jobId}",
        method = RequestMethod.POST)
    public void remove(HttpServletRequest request, HttpServletResponse response, @PathVariable("jobId") String jobId) {
        QuartzJobModel job = quartzJobService.getById(jobId);
        logger.info("^^^^^^^(remove job " + job + " by " + getSysUser(request).getUserName() + ")^^^^^^");
        quartzJobManager.removeJob(job);
        job.setJobStatus(JobStatusEnum.STOPPED.getValue());
        int result = quartzJobService.update(job);
        returnJSONData(response, JSON.toJSONString(result));
    }

    @RequestMapping(value = "/getJobStatus",
        method = RequestMethod.POST)
    public void getJobStatus(HttpServletRequest request, HttpServletResponse response) {
        StringBuilder options = new StringBuilder();
        List<JobStatusEnum> list = JobStatusEnum.getAll();
        options.append("<option value=''>--请选择--</option>");
        for (JobStatusEnum item : list) {
            options.append("<option value='" + item.getValue() + "'>" + item.getName() + "</option>");
        }
        String json = "{\"html\":\"" + options.toString() + "\"}";
        returnJSONData(response, json);
    }
}

5、还差一点就是应用启动时自动加载数据库内的定时任务,这里需要实现一个ApplicationListener

@Component("StartupListener")
public class InitSystemJobsListener implements ApplicationListener<ContextRefreshedEvent> {
    private int runTime = 0;

    /**
     * @see org.springframework.context.ApplicationListener#onApplicationEvent(org.springframework.context.ApplicationEvent)
     */

    @Override
    public void onApplicationEvent(ContextRefreshedEvent event) {
        runTime++;
        if (2 == runTime) {
            // 获取spring管理的Bean
            ApplicationContext context = event.getApplicationContext();
            QuartzJobManager quartzJobManager = (QuartzJobManager) context.getBean("quartzJobManager");
            quartzJobManager.loadJobs();
        }
    }

}

好了,主要工作完成了,我们启动应用试一试

《[整理]在Spring MVC中使用Quartz实现定时任务动态管理》

《[整理]在Spring MVC中使用Quartz实现定时任务动态管理》

页面如下:

《[整理]在Spring MVC中使用Quartz实现定时任务动态管理》

试验一下暂停:

《[整理]在Spring MVC中使用Quartz实现定时任务动态管理》

暂停以后,控制台没有输出,恢复一下

《[整理]在Spring MVC中使用Quartz实现定时任务动态管理》

其它的就不贴图了。就这样吧。

实际应用中发现一个缺陷是,定时任务内部使用Spring托管的bean进行依赖注入时会报空指针异常,这里需要改造SchedulerFactoryBean 。

@Component
public class MyJobFactory extends AdaptableJobFactory {
    @Autowired
    private AutowireCapableBeanFactory autowireCapableBeanFactory;

    /**
     * @see org.springframework.scheduling.quartz.AdaptableJobFactory#createJobInstance(org.quartz.spi.TriggerFiredBundle)
     */
    @Override
    protected Object createJobInstance(TriggerFiredBundle bundle) throws Exception {
        Object jobInstance = super.createJobInstance(bundle);
        // 实现Job的IOC管理
        autowireCapableBeanFactory.autowireBean(jobInstance);
        return jobInstance;
    }
}

@Configuration
@EnableScheduling
public class QuartzJobConfig {
    @Autowired
    private MyJobFactory myJobFactory;

    @Bean
    public SchedulerFactoryBean schedulerFactoryBean() {
        SchedulerFactoryBean factory = new SchedulerFactoryBean();
        factory.setOverwriteExistingJobs(true);
        factory.setStartupDelay(20);
        factory.setJobFactory(myJobFactory);
        return factory;
    }
}

    原文作者:码YouMe
    原文地址: https://blog.csdn.net/macau2008/article/details/78775176
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞