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

来源:互联网 发布:购房增值税的算法 编辑:程序博客网 时间:2024/05/23 18:32

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

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

项目原来的定时任务是直接使用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托管的bean进行依赖注入时会报空指针异常,这里需要改造SchedulerFactoryBean 。

@Componentpublic 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@EnableSchedulingpublic 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;    }}