首页 > 代码库 > Quartz 2.x与Spring 动态整合

Quartz 2.x与Spring 动态整合

一、Quartz简介   

    Quartz是一个由James House创立的开源项目,是一个功能强大的作业调度工具,可以计划的执行任务,定时、循环或在某一个时间来执行我们需要做的事,这可以给我们工作上带来很大的帮助。例如,你的程序中需要每个月的一号导出报表、定时发送邮件或程序需要每隔一段执行某一任务……等等,都可以用Quartz来解决。

        Quartz大致可分为三个主要的核心:

    1、调度器Scheduler:是一个计划调度器容器,容器里面可以盛放众多的JobDetail和Trigger,当容器启动后,里面的每个JobDetail都会根据Trigger按部就班自动去执行.

    2、任务Job:要执行的具体内容。JobDetail:具体的可执行的调度程序,包含了这个任务调度的方案和策略。

    3、触发器Trigger:调度参数的配置,什么时候去执行调度。


    可以这么理解它的原理:调度器就相当于一个容器,装载着任务和触发器。任务和触发器又是绑定在一起的,然而一个任务可以对应多个触发器,但一个触发器却只能对应一个任务。当JobDetail和Trigger在scheduler容器上注册后,形成了装配好的任务作业(JobDetail和Trigger所组成的一对儿),就可以伴随容器启动而调度执行了。


二、与spring的整合

    本文的用的是quartz-2.2.1与spring-3.2.2。之所以在这里特别对版本作一下说明,是因为spring和quartz的整合对版本是有要求的。spring3.1以下的版本必须使用quartz1.x系列,3.1以上的版本才支持quartz 2.x,不然会出错。原因主要是:spring对于quartz的支持实现,org.springframework.scheduling.quartz.CronTriggerBean继承了 org.quartz.CronTrigger,在quartz1.x系列中org.quartz.CronTrigger是个类,而在 quartz2.x系列中org.quartz.CronTrigger变成了接口,从而造成无法用spring的方式配置quartz的触发器 (trigger)。


    在Spring中使用Quartz有两种方式实现:第一种是任务类继承QuartzJobBean,第二种则是在配置文件里定义任务类和要执行的方法,类和方法可以是普通类。这里我们就介绍更为方便使用的第二种方法:

Spring配置文件:

<!-- 使用MethodInvokingJobDetailFactoryBean,任务类可以不实现Job接口,通过targetMethod指定调用方法-->
<bean id="taskJob" class="com.tyyd.dw.task.DataConversionTask"/><bean id="jobDetail" class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
    <property name="group" value="http://www.mamicode.com/job_work"/>
     <property name="name" value="http://www.mamicode.com/job_work_name"/>
     <!--false表示等上一个任务执行完后再开启新的任务-->
     <property name="concurrent" value="http://www.mamicode.com/false"/>
     <property name="targetObject">        
         <ref bean="taskJob"/>
     </property>    
     <property name="targetMethod">
         <value>run</value>    
     </property>
</bean> 
<!--  调度触发器 -->
<bean id="myTrigger" class="org.springframework.scheduling.quartz.CronTriggerFactoryBean">
    <property name="name" value="http://www.mamicode.com/work_default_name"/>
    <property name="group" value="http://www.mamicode.com/work_default"/>
       <property name="jobDetail">
       <ref bean="jobDetail" />    
    </property>    
    <property name="cronExpression">        
        <value>0/5 * * * * ?</value>    
    </property>
</bean> 
<!-- 调度工厂 -->
<bean id="scheduler" class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
    <property name="triggers">
        <list>            
            <ref bean="myTrigger"/>        
        </list>    
    </property>
</bean>

    Task类则是一个普通的Java类,没有继承任何类和实现任何接口(当然可以用注解方式来声明bean):

public class DataConversionTask{
    private static final Logger LOG = LoggerFactory.getLogger(DataConversionTask.class);
    public void run() {
        if (LOG.isInfoEnabled()) {
            LOG.info("数据转换任务线程开始执行");
        }
    }
}

    嗯,以上就是简单的一个整合,run方法将每隔5秒执行一次,因为配置了concurrent等于false,所以假如run方法的执行时间超过5秒,在执行完之前即使时间已经超过了5秒下一个定时计划执行任务仍不会被开启,如果是true,则不管是否执行完,时间到了都将开启。大家应该注意到了"cronExpression",程序需要在什么时间执行,都是由它来决定的。所以接下来为大家介绍下这种表达式:


字段 允许值 允许的特殊字符

秒 0-59 , – * /

分 0-59 , – * /

小时 0-23 , – * /

日期 1-31 , – * ? / L W C

月份 1-12 或者 JAN-DEC , – * /

星期 1-7 或者 SUN-SAT , – * ? / L C #

年(可选) 留空, 1970-2099 , – * /

表达式意义

"0 0 12 * * ?" 每天中午12点触发

"0 15 10 ? * *" 每天上午10:15触发

"0 15 10 * * ?" 每天上午10:15触发

"0 15 10 * * ? *" 每天上午10:15触发

"0 15 10 * * ? 2005" 2005年的每天上午10:15触发

"0 * 14 * * ?" 在每天下午2点到下午2:59期间的每1分钟触发

"0 0/5 14 * * ?" 在每天下午2点到下午2:55期间的每5分钟触发

"0 0/5 14,18 * * ?" 在每天下午2点到2:55期间和下午6点到6:55期间的每5分钟触发

"0 0-5 14 * * ?" 在每天下午2点到下午2:05期间的每1分钟触发

"0 10,44 14 ? 3 WED" 每年三月的星期三的下午2:10和2:44触发

"0 15 10 ? * MON-FRI" 周一至周五的上午10:15触发

"0 15 10 15 * ?" 每月15日上午10:15触发

"0 15 10 L * ?" 每月最后一日的上午10:15触发

"0 15 10 ? * 6L" 每月的最后一个星期五上午10:15触发

"0 15 10 ? * 6L 2002-2005" 2002年至2005年的每月的最后一个星期五上午10:15触发

"0 15 10 ? * 6#3" 每月的第三个星期五上午10:15触发

每天早上6点

0 6 * * *

每两个小时

0 */2 * * *

晚上11点到早上8点之间每两个小时,早上八点

0 23-7/2,8 * * *

每个月的4号和每个礼拜的礼拜一到礼拜三的早上11点

0 11 4 * 1-3

1月1日早上4点

0 4 1 1 *


三、动态整合

    上面的整合只能应付简单的需求,但很多时候我们遇到的是需要动态的添加、暂停、修改任务。而spring中所提供的定时任务组件却只能够通过修改xml中trigger的配置才能控制定时任务的时间以及任务的启用或停止,这在带给我们方便的同时也失去了动态配置任务的灵活性。

    所以我们就得换种方式来解决。把任务与cronExpression存放在数据库中,最大化减少xml配置,创建一个工厂类,在实际调用时把任务的相关信息通过参数方式传入,由该工厂类根据任务信息来具体执行需要的操作,从而方便我们的动态修改。

    1.spring配置(其实只要这一行足矣,去掉了原先"taskJob"、"myTrigger"等配置):

<bean id="schedulerFactoryBean" class="org.springframework.scheduling.quartz.SchedulerFactoryBean" />

    2.任务执行入口,实现Job接口,类似工厂类:

package com.quartz;

import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;

import com.quartz.model.ScheduleJob;

/**
 * 定时任务运行工厂
 * @author unique
 *
 */
public class QuartzJobFactory implements Job {

    public void execute(JobExecutionContext context) throws JobExecutionException {
        
         System.out.println("任务成功运行");
         ScheduleJob scheduleJob = (ScheduleJob)context.getMergedJobDataMap().get("scheduleJob");
         System.out.println("任务名称 = [" + scheduleJob.getJobName() + "]");
         
         //根据name 与 group组成的唯一标识来判别该执行何种操作……
         
    }

}

    这里我们实现的是无状态的Job,如果要实现有状态的Job在以前是实现StatefulJob接口,在我使用的quartz 2.2.1中,StatefulJob接口已经不推荐使用了,换成了注解的方式,只需要给你实现的Job类加上注解 @DisallowConcurrentExecution即可实现有状态:

/** 
* 定时任务运行工厂类 
*/
@DisallowConcurrentExecution
public class QuartzJobFactory implements Job {...}

    有无状态的区别请查看:http://fordream.iteye.com/blog/1179097


    3.创建任务。既然要动态修改任务,那任务就得保存在某个地方,所以我们需要个JavaBean来存放任务信息。

package com.quartz.model;

/**
 * 计划任务信息
 * @author unique
 *
 */
public class ScheduleJob {

     /** 任务id */
    private String jobId;
     
    /** 任务名称 */
    private String jobName;
     
    /** 任务分组 */
    private String jobGroup;
     
    /** 任务状态 0禁用 1启用 2删除*/
    private String jobStatus;
     
    /** 任务运行时间表达式 */
    private String cronExpression;
     
    /** 任务描述 */
    private String desc;

    public ScheduleJob() {
        super();
    }

    public ScheduleJob(String jobId, String jobName, String jobGroup,
            String jobStatus, String cronExpression, String desc) {
        super();
        this.jobId = jobId;
        this.jobName = jobName;
        this.jobGroup = jobGroup;
        this.jobStatus = jobStatus;
        this.cronExpression = cronExpression;
        this.desc = desc;
    }

    public String getJobId() {
        return jobId;
    }

    public void setJobId(String jobId) {
        this.jobId = jobId;
    }

    public String getJobName() {
        return jobName;
    }

    public void setJobName(String jobName) {
        this.jobName = jobName;
    }

    public String getJobGroup() {
        return jobGroup;
    }

    public void setJobGroup(String jobGroup) {
        this.jobGroup = jobGroup;
    }

    public String getJobStatus() {
        return jobStatus;
    }

    public void setJobStatus(String jobStatus) {
        this.jobStatus = jobStatus;
    }

    public String getCronExpression() {
        return cronExpression;
    }

    public void setCronExpression(String cronExpression) {
        this.cronExpression = cronExpression;
    }

    public String getDesc() {
        return desc;
    }

    public void setDesc(String desc) {
        this.desc = desc;
    }
    
    
}

    接下来这个类是最为主要的,请求地址跳往相对应的方法:

package com.wxapi.action;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.quartz.CronScheduleBuilder;
import org.quartz.CronTrigger;
import org.quartz.JobBuilder;
import org.quartz.JobDetail;
import org.quartz.JobKey;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.TriggerBuilder;
import org.quartz.TriggerKey;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.quartz.SchedulerFactoryBean;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

import com.quartz.QuartzJobFactory;
import com.quartz.model.ScheduleJob;
import com.wxapi.bs.IQuartzBS;

@Controller
@RequestMapping("/quartz")
public class QuartzAction {

    @Autowired
    private SchedulerFactoryBean schedulerFactoryBean;
    
    @Autowired
    private IQuartzBS quartzBS;
    
    /**
     * 任务创建与更新(未存在的就创建,已存在的则更新)
     * @param request
     * @param response
     * @param scheduleJob
     * @param model
     * @return
     */
    @RequestMapping(value="http://www.mamicode.com/update", method={RequestMethod.POST,RequestMethod.GET})
    public String updateQuartz(HttpServletRequest request,HttpServletResponse response,
            @ModelAttribute("scheduleJob") ScheduleJob job,ModelMap model){
        
        try {
            
            Scheduler scheduler = schedulerFactoryBean.getScheduler();
            
            if(null!=job){
                
                //获取触发器标识
                TriggerKey triggerKey = TriggerKey.triggerKey(job.getJobName(), job.getJobGroup());
                //获取触发器trigger
                CronTrigger trigger = (CronTrigger) scheduler.getTrigger(triggerKey);
                
                if(null==trigger){//不存在任务
                    
                    //创建任务
                    JobDetail jobDetail = JobBuilder.newJob(QuartzJobFactory.class)
                            .withIdentity(job.getJobName(), job.getJobGroup())
                            .build();
                    
                    jobDetail.getJobDataMap().put("scheduleJob", job);
                    
                    //表达式调度构建器
                    CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(job
                            .getCronExpression());
                    
                    //按新的cronExpression表达式构建一个新的trigger
                    trigger = TriggerBuilder.newTrigger()
                            .withIdentity(job.getJobName(), job.getJobGroup())
                            .withSchedule(scheduleBuilder)
                            .build();
                    
                    scheduler.scheduleJob(jobDetail, trigger);
                    
                    //把任务插入数据库
                    int result = quartzBS.add(job);
                    if(result!=0){
                        model.addAttribute("msg", "您的任务创建成功!");
                    }else{
                        model.addAttribute("msg", "您的任务创建失败!");
                    }
                    
                }else{//存在任务
                    
                    // Trigger已存在,那么更新相应的定时设置
                    //表达式调度构建器
                    CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(job
                            .getCronExpression());
                    
                    //按新的cronExpression表达式重新构建trigger
                    trigger = trigger.getTriggerBuilder()
                            .withIdentity(triggerKey)
                            .withSchedule(scheduleBuilder)
                            .build();
                    
                    //按新的trigger重新设置job执行
                    scheduler.rescheduleJob(triggerKey, trigger);
                    
                    //更新数据库中的任务
                    int result = quartzBS.update(job);
                    if(result==1){
                        model.addAttribute("msg", "您的任务更新成功!");
                    }else{
                        model.addAttribute("msg", "您的任务更新失败!");
                    }
                }
                
            }
            
        } catch (SchedulerException e) {
            e.printStackTrace();
        }
        
        return "/warn.jsp";
    }
    
    
    /**
     * 暂停任务
     * @param request
     * @param response
     * @param job
     * @param model
     * @return
     */
    @RequestMapping(value="http://www.mamicode.com/pause", method={RequestMethod.POST,RequestMethod.GET})
    public String pauseQuartz(HttpServletRequest request,HttpServletResponse response,
            @ModelAttribute("scheduleJob") ScheduleJob scheduleJob,ModelMap model){

        Scheduler scheduler = schedulerFactoryBean.getScheduler();
        JobKey jobKey = JobKey.jobKey(scheduleJob.getJobName(), scheduleJob.getJobGroup());
        try {
            scheduler.pauseJob(jobKey);
        } catch (SchedulerException e) {
            e.printStackTrace();
        }

        return "/warn.jsp";
    }
    
    
    /**
     * 恢复任务
     * @param request
     * @param response
     * @param scheduleJob
     * @param model
     * @return
     */
    @RequestMapping(value="http://www.mamicode.com/resume", method={RequestMethod.POST,RequestMethod.GET})
    public String resumeQuartz(HttpServletRequest request,HttpServletResponse response,
            @ModelAttribute("scheduleJob") ScheduleJob scheduleJob,ModelMap model){

        Scheduler scheduler = schedulerFactoryBean.getScheduler();
        JobKey jobKey = JobKey.jobKey(scheduleJob.getJobName(), scheduleJob.getJobGroup());
        try {
            scheduler.resumeJob(jobKey);
        } catch (SchedulerException e) {
            e.printStackTrace();
        }

        return "/warn.jsp";
    }
    
    
    /**
     * 删除任务
     * @param request
     * @param response
     * @param scheduleJob
     * @param model
     * @return
     */
    @RequestMapping(value="http://www.mamicode.com/delete", method={RequestMethod.POST,RequestMethod.GET})
    public String deleteQuartz(HttpServletRequest request,HttpServletResponse response,
            @ModelAttribute("scheduleJob") ScheduleJob scheduleJob,ModelMap model){

        Scheduler scheduler = schedulerFactoryBean.getScheduler();
        JobKey jobKey = JobKey.jobKey(scheduleJob.getJobName(), scheduleJob.getJobGroup());
        try {
            scheduler.deleteJob(jobKey);
        } catch (SchedulerException e) {
            e.printStackTrace();
        }

        return "/warn.jsp";
    }

}

    在运行工厂类中:

ScheduleJob scheduleJob = (ScheduleJob)context.getMergedJobDataMap().get("scheduleJob");

    可获取任务分组和任务名称来确定任务的唯一性,然后在execute方法中通过判断任务分组和任务名来实现你具体的操作。


四、推荐:

    推荐博客:《Quartz深入浅出》

    官网:http://www.quartz-scheduler.org/

    下载:http://www.quartz-scheduler.org/downloads


五、结语:

    嗯,quartz与spring的动态整合在此粗略的讲述完,特别感谢:每日有客网站的文章《Spring 3整合Quartz 2实现定时任务》,文中大部分内容也是从中学习借鉴。如有不足之处,还望指出。

    坚持是一种精神,分享是一种快乐!

本文出自 “学而思” 博客,请务必保留此出处http://linhongyu.blog.51cto.com/6373370/1530148