Quartz与SpringMVC的整合

前言:

     要将quartz的任务持久化,能动态添加任务、修改任务、暂停任务、动态修改触发时间.

    调度器:Scheduler;工作:JobDetail ;触发器:Trigger


一、在pom文件中引入jar包

这里只定义quartz需要的jar包,其它和spring相关的省略


    org.quartz-scheduler
    quartz
    2.2.1
  

  
    org.quartz-scheduler
    quartz-jobs
    2.2.1
     

二、配置相应的xml文件

 



	
        
        
        
        
            
                
                fwoneScheduler
                
                AUTO
                
                org.quartz.simpl.SimpleThreadPool
                20
                5
                
                
                org.quartz.impl.jdbcjobstore.JobStoreTX
                
                
                true

                150000
                1
                
                12000
				
                QRTZ_
            
        

        

        
        

        

        
        

        
        
        
        

    
       
	   


    
    
        
        
        
        
        
        
        
        
        
        
            
        
    

三、建立相关的数据表

因为要持久化到数据库中,所以要建立相关的数据表

1.1.qrtz_blob_triggers : 以Blob 类型存储的触发器

1.2.qrtz_calendars:存放日历信息, quartz可配置一个日历来指定一个时间范围。

1.3.qrtz_cron_triggers:存放cron类型的触发器。

1.4.qrtz_fired_triggers:存放已触发的触发器。

1.5.qrtz_job_details:存放一个jobDetail信息。

1.6.qrtz_job_listeners:job**监听器**。

1.7.qrtz_locks: 存储程序的悲观锁的信息(假如使用了悲观锁)。

1.8.qrtz_paused_trigger_graps:存放暂停掉的触发器。

1.9.qrtz_scheduler_state:调度器状态。

1.10.qrtz_simple_triggers简单触发器的信息。

1.11.qrtz_trigger_listeners触发器监听器1.12.qrtz_triggers触发器基本信息

DROP TABLE IF EXISTS QRTZ_FIRED_TRIGGERS;
DROP TABLE IF EXISTS QRTZ_PAUSED_TRIGGER_GRPS;
DROP TABLE IF EXISTS QRTZ_SCHEDULER_STATE;
DROP TABLE IF EXISTS QRTZ_LOCKS;
DROP TABLE IF EXISTS QRTZ_SIMPLE_TRIGGERS;
DROP TABLE IF EXISTS QRTZ_SIMPROP_TRIGGERS;
DROP TABLE IF EXISTS QRTZ_CRON_TRIGGERS;
DROP TABLE IF EXISTS QRTZ_BLOB_TRIGGERS;
DROP TABLE IF EXISTS QRTZ_TRIGGERS;
DROP TABLE IF EXISTS QRTZ_JOB_DETAILS;
DROP TABLE IF EXISTS QRTZ_CALENDARS;


CREATE TABLE QRTZ_JOB_DETAILS
  (
    SCHED_NAME VARCHAR(120) NOT NULL,
    JOB_NAME  VARCHAR(200) NOT NULL,
    JOB_GROUP VARCHAR(200) NOT NULL,
    DESCRIPTION VARCHAR(250) NULL,
    JOB_CLASS_NAME   VARCHAR(250) NOT NULL,
    IS_DURABLE VARCHAR(1) NOT NULL,
    IS_NONCONCURRENT VARCHAR(1) NOT NULL,
    IS_UPDATE_DATA VARCHAR(1) NOT NULL,
    REQUESTS_RECOVERY VARCHAR(1) NOT NULL,
    JOB_DATA BLOB NULL,
    PRIMARY KEY (SCHED_NAME,JOB_NAME,JOB_GROUP)
);

CREATE TABLE QRTZ_TRIGGERS
  (
    SCHED_NAME VARCHAR(120) NOT NULL,
    TRIGGER_NAME VARCHAR(200) NOT NULL,
    TRIGGER_GROUP VARCHAR(200) NOT NULL,
    JOB_NAME  VARCHAR(200) NOT NULL,
    JOB_GROUP VARCHAR(200) NOT NULL,
    DESCRIPTION VARCHAR(250) NULL,
    NEXT_FIRE_TIME BIGINT(13) NULL,
    PREV_FIRE_TIME BIGINT(13) NULL,
    PRIORITY INTEGER NULL,
    TRIGGER_STATE VARCHAR(16) NOT NULL,
    TRIGGER_TYPE VARCHAR(8) NOT NULL,
    START_TIME BIGINT(13) NOT NULL,
    END_TIME BIGINT(13) NULL,
    CALENDAR_NAME VARCHAR(200) NULL,
    MISFIRE_INSTR SMALLINT(2) NULL,
    JOB_DATA BLOB NULL,
    PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
    FOREIGN KEY (SCHED_NAME,JOB_NAME,JOB_GROUP)
        REFERENCES QRTZ_JOB_DETAILS(SCHED_NAME,JOB_NAME,JOB_GROUP)
);

CREATE TABLE QRTZ_SIMPLE_TRIGGERS
  (
    SCHED_NAME VARCHAR(120) NOT NULL,
    TRIGGER_NAME VARCHAR(200) NOT NULL,
    TRIGGER_GROUP VARCHAR(200) NOT NULL,
    REPEAT_COUNT BIGINT(7) NOT NULL,
    REPEAT_INTERVAL BIGINT(12) NOT NULL,
    TIMES_TRIGGERED BIGINT(10) NOT NULL,
    PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
    FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
        REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
);

CREATE TABLE QRTZ_CRON_TRIGGERS
  (
    SCHED_NAME VARCHAR(120) NOT NULL,
    TRIGGER_NAME VARCHAR(200) NOT NULL,
    TRIGGER_GROUP VARCHAR(200) NOT NULL,
    CRON_EXPRESSION VARCHAR(200) NOT NULL,
    TIME_ZONE_ID VARCHAR(80),
    PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
    FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
        REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
);

CREATE TABLE QRTZ_SIMPROP_TRIGGERS
  (          
    SCHED_NAME VARCHAR(120) NOT NULL,
    TRIGGER_NAME VARCHAR(200) NOT NULL,
    TRIGGER_GROUP VARCHAR(200) NOT NULL,
    STR_PROP_1 VARCHAR(512) NULL,
    STR_PROP_2 VARCHAR(512) NULL,
    STR_PROP_3 VARCHAR(512) NULL,
    INT_PROP_1 INT NULL,
    INT_PROP_2 INT NULL,
    LONG_PROP_1 BIGINT NULL,
    LONG_PROP_2 BIGINT NULL,
    DEC_PROP_1 NUMERIC(13,4) NULL,
    DEC_PROP_2 NUMERIC(13,4) NULL,
    BOOL_PROP_1 VARCHAR(1) NULL,
    BOOL_PROP_2 VARCHAR(1) NULL,
    PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
    FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP) 
    REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
);

CREATE TABLE QRTZ_BLOB_TRIGGERS
  (
    SCHED_NAME VARCHAR(120) NOT NULL,
    TRIGGER_NAME VARCHAR(200) NOT NULL,
    TRIGGER_GROUP VARCHAR(200) NOT NULL,
    BLOB_DATA BLOB NULL,
    PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
    FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
        REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
);

CREATE TABLE QRTZ_CALENDARS
  (
    SCHED_NAME VARCHAR(120) NOT NULL,
    CALENDAR_NAME  VARCHAR(200) NOT NULL,
    CALENDAR BLOB NOT NULL,
    PRIMARY KEY (SCHED_NAME,CALENDAR_NAME)
);

CREATE TABLE QRTZ_PAUSED_TRIGGER_GRPS
  (
    SCHED_NAME VARCHAR(120) NOT NULL,
    TRIGGER_GROUP  VARCHAR(200) NOT NULL, 
    PRIMARY KEY (SCHED_NAME,TRIGGER_GROUP)
);

CREATE TABLE QRTZ_FIRED_TRIGGERS
  (
    SCHED_NAME VARCHAR(120) NOT NULL,
    ENTRY_ID VARCHAR(95) NOT NULL,
    TRIGGER_NAME VARCHAR(200) NOT NULL,
    TRIGGER_GROUP VARCHAR(200) NOT NULL,
    INSTANCE_NAME VARCHAR(200) NOT NULL,
    FIRED_TIME BIGINT(13) NOT NULL,
    SCHED_TIME BIGINT(13) NOT NULL,
    PRIORITY INTEGER NOT NULL,
    STATE VARCHAR(16) NOT NULL,
    JOB_NAME VARCHAR(200) NULL,
    JOB_GROUP VARCHAR(200) NULL,
    IS_NONCONCURRENT VARCHAR(1) NULL,
    REQUESTS_RECOVERY VARCHAR(1) NULL,
    PRIMARY KEY (SCHED_NAME,ENTRY_ID)
);

CREATE TABLE QRTZ_SCHEDULER_STATE
  (
    SCHED_NAME VARCHAR(120) NOT NULL,
    INSTANCE_NAME VARCHAR(200) NOT NULL,
    LAST_CHECKIN_TIME BIGINT(13) NOT NULL,
    CHECKIN_INTERVAL BIGINT(13) NOT NULL,
    PRIMARY KEY (SCHED_NAME,INSTANCE_NAME)
);

CREATE TABLE QRTZ_LOCKS
  (
    SCHED_NAME VARCHAR(120) NOT NULL,
    LOCK_NAME  VARCHAR(40) NOT NULL, 
    PRIMARY KEY (SCHED_NAME,LOCK_NAME)
);


四、具体实现

1、我本地自己建立的一个保存任务的数据表

DROP TABLE IF EXISTS `schedule_job`;
CREATE TABLE `schedule_job` (
  `job_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '任务id',
  `bean_name` varchar(200) DEFAULT NULL COMMENT 'spring bean名称',
  `method_name` varchar(100) DEFAULT NULL COMMENT '方法名',
  `params` varchar(2000) DEFAULT NULL COMMENT '参数',
  `cron_expression` varchar(100) DEFAULT NULL COMMENT 'cron表达式',
  `status` tinyint(4) DEFAULT NULL COMMENT '任务状态',
  `remark` varchar(255) DEFAULT NULL COMMENT '备注',
  `create_time` datetime DEFAULT NULL COMMENT '创建时间',
  PRIMARY KEY (`job_id`)
) ENGINE=InnoDB AUTO_INCREMENT=10 DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC COMMENT='定时任务';

2、项目启动时先初始化

说明:我的项目集成了mybatis,像数据库调用的那些就不说了。

ScheduleJobEntity这个是对应表`schedule_job`建立的实体类,schedulerJobDao是数据调用接口。

	/**
	 * 项目启动时,初始化定时器
	 */
	@PostConstruct
	public void myInit(){
		//先查出数据库中有多少个任务,将其初始化
		List list=schedulerJobDao.queryList(null);
		if(list!=null)
		{
			list.forEach
				(
					scheduleJob->
					{
						//获取各个任务对应的触发器,如果不为空,则更新;为空则创建任务;
						Trigger trigger= MyScheduleUtils.getTrigger(scheduler,scheduleJob.getJobId());
						if(trigger==null)
                             MyScheduleUtils.createScheduleJob(scheduler,scheduleJob);
						else
							 MyScheduleUtils.updateScheduleJob(scheduler,scheduleJob);
					}
			    );
		}
	}

3、建立工具类MyScheduleUtils

 

package com.fwone.utils;

import com.fwone.entity.ScheduleJobEntity;
import org.quartz.*;

public class MyScheduleUtils {
    private static final String JOB_NAME="TASK_";
    /**
     * 获取触发器Key
     * @param jobId
     * @return
     */
    public static TriggerKey getTriggerKey(Long jobId){
        return TriggerKey.triggerKey(JOB_NAME+jobId);
    }

    public static JobKey getJobKey(Long jobId){
        return JobKey.jobKey(JOB_NAME+jobId);
    }
    /**
     * 获取触发器
     * @param scheduler
     * @param jobId
     * @return
     */
    public static CronTrigger getTrigger(Scheduler scheduler, Long jobId){
        try {
            JobDetail jobDetail=scheduler.getJobDetail(getJobKey(jobId));
            Class c=jobDetail.getJobClass();
            System.out.println(c);
            return (CronTrigger)scheduler.getTrigger(getTriggerKey(jobId));
        } catch (SchedulerException e) {
            throw new RRException("获取定时任务CronTrigger出现异常",e);
        }
    }

    /**
     * 创建任务
     * @param scheduler
     * @param scheduleJob
     */
    public static void createScheduleJob(Scheduler scheduler, ScheduleJobEntity scheduleJob){
        try {
            /** 重要:
             *  定义一个job,并绑定到我们自定义的MyScheduleJob的class对象
             *  这里并不会马上创建一个MyScheduleJob实例,实例创建是在scheduler安排任务触发执行时创建的
             *  这种机制也为后面使用Spring集成提供了便利
             */
            JobDetail jobDetail = JobBuilder.newJob(MyScheduleJob.class)
                    .withIdentity(getJobKey(scheduleJob.getJobId()))
                    .build();
            /** 重要:
             *  定义一个表达式调度构建器:不触发立即执行,等待下次Cron触发频率到达时刻开始按照Cron频率依次执行
             *  如果任务失败,策略为:所有的misfire不管,执行下一个周期的任务
             */
            CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder
                    .cronSchedule(scheduleJob.getCronExpression())
                    .withMisfireHandlingInstructionDoNothing();

            /**重要:
             * 按新的cronExpression表达式构建一个新的trigger
             */
            Trigger trigger = TriggerBuilder
                    .newTrigger()
                    .withIdentity(getTriggerKey(scheduleJob.getJobId()))
                    .withSchedule(cronScheduleBuilder)
                    .build();
            //放入参数,运行时的方法可以获取
            jobDetail.getJobDataMap().put(ScheduleJobEntity.JOB_PARAM_KEY, scheduleJob);

            /**重要:
             * 把定义好的job放入调度器scheduler
             */
            scheduler.scheduleJob(jobDetail, trigger);

            //判断是否暂停任务
            if(scheduleJob.getStatus()==null||
                    scheduleJob.getStatus()== Constant.ScheduleStatus.PAUSE.getValue())
                pauseJob(scheduler,scheduleJob.getJobId());
        }catch (SchedulerException e){
            throw new RRException("创建定时任务失败", e);
        }

    }

    /**
     * 更新任务
     * @param scheduler
     * @param scheduleJob
     */
    public static void updateScheduleJob(Scheduler scheduler, ScheduleJobEntity scheduleJob){
        try {
        CronScheduleBuilder scheduleBuilder=CronScheduleBuilder
                .cronSchedule(scheduleJob.getCronExpression())
                .withMisfireHandlingInstructionDoNothing();

        CronTrigger trigger=getTrigger(scheduler,scheduleJob.getJobId());
        //按新的cronExpression表达式重新构建trigger
        trigger=trigger.getTriggerBuilder()
                .withIdentity(getTriggerKey(scheduleJob.getJobId()))
                .withSchedule(scheduleBuilder)
                .build();
        trigger.getJobDataMap().put(ScheduleJobEntity.JOB_PARAM_KEY,scheduleJob);
        scheduler.rescheduleJob(getTriggerKey(scheduleJob.getJobId()),trigger);

        //判断是否暂停任务
        if(scheduleJob.getStatus()==null||
                 scheduleJob.getStatus()== Constant.ScheduleStatus.PAUSE.getValue())
             pauseJob(scheduler,scheduleJob.getJobId());
        } catch (SchedulerException e) {
           throw new RRException("更新定时任务失败",e);
        }
    }

    /**
     * 暂停任务
     * @param scheduler
     * @param jobId
     */
    public static void pauseJob(Scheduler scheduler,Long jobId){
        try {
            scheduler.pauseJob(getJobKey(jobId));
        } catch (SchedulerException e) {
            throw new RRException("暂停定时任务失败", e);
        }
    }

    /**
     * 立即运行一次任务
     * @param scheduler
     * @param scheduleJob
     */
    public static void run(Scheduler scheduler,ScheduleJobEntity scheduleJob){
        try {
            JobDataMap jobDataMap=new JobDataMap();
            jobDataMap.put(ScheduleJobEntity.JOB_PARAM_KEY,scheduleJob);
            scheduler.triggerJob(getJobKey(scheduleJob.getJobId()),jobDataMap);
        } catch (SchedulerException e) {
            throw new RRException("立即执行任务失败",e);
        }
    }

    /**
     *恢复任务
     * @param scheduler
     * @param jobId
     */
    public static void resumeJob(Scheduler scheduler,Long jobId){
        try {
            scheduler.resumeJob(getJobKey(jobId));
        } catch (SchedulerException e) {
           throw new RRException("恢复任务失败",e);
        }
    }
}

自定义工作类,用于 执行任务

package com.fwone.utils;

import com.fwone.entity.ScheduleJobEntity;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.scheduling.quartz.QuartzJobBean;
import java.util.concurrent.Future;

/**
**另外Trigger中也可以设置JobDataMap属性,这是为了在多个Trigger中使用相同的Job。
**JobExecutionContext 将会合并JobDetail与Trigger的JobDataMap,如果其中属性名相同,后者将覆盖前者。
**可以使用JobExecutionContext.getMergedJobDataMap()方法来获取合并后的JobDataMap。
 */
public class MyScheduleJob extends QuartzJobBean {
    private Logger logger= LoggerFactory.getLogger(MyScheduleJob.class);
    @Override
    protected void executeInternal(JobExecutionContext context) throws JobExecutionException {
        ScheduleJobEntity scheduleJob=(ScheduleJobEntity)context
                .getMergedJobDataMap().get(ScheduleJobEntity.JOB_PARAM_KEY);
        ThreadPoolTaskExecutor taskExecutor=SpringContextUtil.getBean("taskExecutor");
        //任务开始时间
        long startTime = System.currentTimeMillis();
        logger.info("我的任务准备执行,任务ID:" + scheduleJob.getJobId());
        try {
            /**
             * ScheduleRunnable是一个任务类,通过反射执行真正的任务
             */
            ScheduleRunnable task=new ScheduleRunnable(scheduleJob.getBeanName(),
                                  scheduleJob.getMethodName(),scheduleJob.getParams());
            Future future=taskExecutor.submit(task);
            future.get();//线程结束之前下面语句不会执行
            long times=System.currentTimeMillis()-startTime;
            logger.info("我的任务执行完毕,任务ID:" + scheduleJob.getJobId() + "  总共耗时:" + times + "毫秒");

        } catch (Exception e) {
            logger.error("我的任务执行失败,任务ID:" + scheduleJob.getJobId(), e);
        }
    }
}

定义任务类ScheduleRunnable,通过反射执行真正的任务

package com.fwone.utils;

import org.apache.commons.lang.StringUtils;
import org.springframework.util.ReflectionUtils;

import java.lang.reflect.Method;

/**
 * 执行定时任务
 * 
 * @author chenshun
 * @email [email protected]
 * @date 2016年11月30日 下午12:49:33
 */
public class ScheduleRunnable implements Runnable {
	private Object target;//bean名称
	private Method method;//bean的方法名称
	private String params;//bean的参数

	/**
	 * 初始化
	 * @param beanName
	 * @param methodName
	 * @param params
	 * @throws NoSuchMethodException
	 * @throws SecurityException
     */
	public ScheduleRunnable(String beanName, String methodName, String params) throws NoSuchMethodException, SecurityException {
		this.target = SpringContextUtil.getBean(beanName);
		this.params = params;
		
		if(StringUtils.isNotBlank(params)){
			this.method = target.getClass().getDeclaredMethod(methodName, String.class);
		}else{
			this.method = target.getClass().getDeclaredMethod(methodName);
		}
	}

	@Override
	public void run() {
		try {
			//让方法跳过安全检查
			ReflectionUtils.makeAccessible(method);
			if(StringUtils.isNotBlank(params)){
				method.invoke(target, params);
			}else{
				method.invoke(target);
			}
		}catch (Exception e) {
			throw new RRException("执行定时任务失败", e);
		}
	}

}

最后,为了安全,还要在spring容器关闭时把调度器、线程池关闭。我试过如果不手动关闭,会发出警告说内存有可能溢出。

定义监听器,这样在容器关闭时能做一些处理工作.

package com.fwone.listener;


import com.fwone.utils.RRException;
import com.fwone.utils.SpringContextUtil;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;

import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.annotation.WebListener;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.TimeUnit;

@WebListener
public class MyContextLoaderListener implements ServletContextListener{
    @Override
    public void contextInitialized(ServletContextEvent sce) {
    }

    /**
     * 容器关闭时触发
     * @param sce
     */
    @Override
    public void contextDestroyed(ServletContextEvent sce) {
        /**
         * SpringContextUtil是我实现的一个获取bean的工具类,网上有很多例子,就不写出来了
         * 这里主要是把调度器Scheduler和线程池ExecutorService关闭
         */
        ExecutorService taskExecutor=SpringContextUtil.getBean("taskExecutor");
        Scheduler scheduler=(Scheduler)SpringContextUtil.getBean("scheduler");
        if(scheduler!=null){
            try {
                if(scheduler.isStarted()){
                    scheduler.shutdown();
                }
            } catch (SchedulerException e) {
                  throw new RRException("定时容器关闭失败");
            }
        }
        try {
            taskExecutor.shutdown();
            if(taskExecutor.awaitTermination(2000l, TimeUnit.MILLISECONDS))
                  taskExecutor.shutdownNow();
        }catch (InterruptedException e){
            e.printStackTrace();
            taskExecutor.shutdown();
        }
    }
}
至此大功告成,以后想添加定时任务,只需把写好的bean名称传进去就可以了。

你可能感兴趣的:(Quartz与SpringMVC的整合)