Quartz-Spring定时任务器持久化,通过Service动态添加,删除,启动暂停任务

来源:互联网 发布:淘宝蛋糕店排名 编辑:程序博客网 时间:2024/05/18 03:59

转载请注:http://blog.csdn.net/ljqwstc/article/details/78257091


最近接了一个活,需要在项目中添加定时任务。定时任务可以用jdk自带的timer,但是考虑到业务需求,这里可以用小巧灵活非常好用到的Quartz定时管理框架。

写这篇博客主要有两个原因。

第一:是希望自己日后如果有遗忘翻看的时候能想起来怎么操作,所以可能记录的东西不会很全,望谅解,若有写的不对的地方,欢迎拍砖留言。

第二:自己在开始做之前也看了非常多的文章,始终感觉模棱两可,不知怎么下手,在这,我希望有quartz业务需求的朋友看了我的文章后能知道怎么下手,完成业务需求。


废话不多说,下面就简单介绍了如何quartz如何整合spring框架,以及quartz的持久化还有动态添加删除、启动等操作。


首先添加maven的依赖:

        <!--quartz定时任务-->        <dependency>            <groupId>org.quartz-scheduler</groupId>            <artifactId>quartz</artifactId>            <version>2.2.1</version>        </dependency>        <dependency>            <groupId>org.quartz-scheduler</groupId>            <artifactId>quartz-jobs</artifactId>            <version>2.2.1</version>        </dependency>

spring配置文件中添加如下bean

    <!--    自动装配类    @Autowired    private Scheduler scheduler;    -->    <!-- quartz持久化存储  -->    <!--实现动态配置只需定义一下schedulerbean就可以了-->    <bean name="schedulerFactoryBean"          class="org.springframework.scheduling.quartz.SchedulerFactoryBean" lazy-init="true">        <property name="dataSource">            <ref bean="dataSource"/>        </property>        <property name="applicationContextSchedulerContextKey" value="applicationContext"/>        <property name="quartzProperties">            <props>                <prop key="org.quartz.scheduler.instanceId">AUTO</prop>                <!-- 线程池配置 -->                <prop key="org.quartz.threadPool.class">${org.quartz.threadPool.class}</prop>                <prop key="org.quartz.threadPool.threadCount">${org.quartz.threadPool.threadCount}</prop>                <prop key="org.quartz.threadPool.threadPriority">${org.quartz.threadPool.threadPriority}</prop>                <prop key="org.quartz.jobStore.misfireThreshold">${org.quartz.jobStore.misfireThreshold}</prop>                <!-- JobStore 配置 -->                <prop key="org.quartz.jobStore.class">${org.quartz.jobStore.class}</prop>                <!-- 集群配置 -->                <prop key="org.quartz.jobStore.isClustered">true</prop>                <prop key="org.quartz.jobStore.clusterCheckinInterval">15000</prop>                <prop key="org.quartz.jobStore.maxMisfiresToHandleAtATime">1</prop>                <!-- 数据表设置 -->                <prop key="org.quartz.jobStore.tablePrefix">${org.quartz.jobStore.tablePrefix}</prop>                <prop key="org.quartz.jobStore.dataSource">${org.quartz.jobStore.dataSource}</prop>            </props>        </property>    </bean>
解释:
1.一般公司项目中都已经定义好了dataSource,比如我们公司是用DruidDataSource,所以直接拿来用就可以了。如果没有的话,需要配置一个dataSource,这里就不阐述了。
2.因为是要持久化的,所以org.quartz.jobStore.class:org.quartz.impl.jdbcjobstore.JobStoreTX  
这样设置以后就是持久化,会将任务的一些相关信息和配置存入到数据库中。具体需要用到什么数据表,下面会有。
3.Scheduler简单说相当于一个容器一样,里面放着jobDetail和Trriger
4.其他的一些配置可以根据自己的业务需求,到quartz的官网查看配置文档进行添加。

这个是quartz.properties文件

#============================================================================# Configure Datasources#============================================================================#JDBC驱动#org.quartz.dataSource.myDS.driver = com.mysql.jdbc.Driver#org.quartz.dataSource.myDS.URL = jdbc:mysql://localhost:3306/quartz?characterEncoding=utf-8#org.quartz.dataSource.myDS.user = root#org.quartz.dataSource.myDS.password = root#org.quartz.dataSource.myDS.maxConnections =5#集群配置org.quartz.scheduler.instanceName: DefaultQuartzSchedulerorg.quartz.scheduler.rmi.export: falseorg.quartz.scheduler.rmi.proxy: falseorg.quartz.scheduler.wrapJobExecutionInUserTransaction: falseorg.quartz.threadPool.class: org.quartz.simpl.SimpleThreadPoolorg.quartz.threadPool.threadCount: 10org.quartz.threadPool.threadPriority: 5org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread: trueorg.quartz.jobStore.misfireThreshold: 60000#============================================================================# Configure JobStore#============================================================================#默认配置,数据保存到内存#org.quartz.jobStore.class: org.quartz.simpl.RAMJobStore#持久化配置org.quartz.jobStore.class:org.quartz.impl.jdbcjobstore.JobStoreTXorg.quartz.jobStore.driverDelegateClass:org.quartz.impl.jdbcjobstore.StdJDBCDelegateorg.quartz.jobStore.useProperties:true#数据库表前缀org.quartz.jobStore.tablePrefix:qrtz_org.quartz.jobStore.dataSource:myDS

这个是quartz官方的表,运行就可以了,就建表成功了。

## Quartz seems to work best with the driver mm.mysql-2.0.7-bin.jar## PLEASE consider using mysql with innodb tables to avoid locking issues## In your Quartz properties file, you'll need to set # org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.StdJDBCDelegate#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))ENGINE=MyISAM DEFAULT CHARSET=utf8;  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))ENGINE=MyISAM DEFAULT CHARSET=utf8;  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))ENGINE=MyISAM DEFAULT CHARSET=utf8;  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))ENGINE=MyISAM DEFAULT CHARSET=utf8;  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))ENGINE=MyISAM DEFAULT CHARSET=utf8;  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))ENGINE=MyISAM DEFAULT CHARSET=utf8;  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))ENGINE=MyISAM DEFAULT CHARSET=utf8;  CREATE TABLE QRTZ_PAUSED_TRIGGER_GRPS  (    SCHED_NAME VARCHAR(120) NOT NULL,    TRIGGER_GROUP  VARCHAR(200) NOT NULL,     PRIMARY KEY (SCHED_NAME,TRIGGER_GROUP))ENGINE=MyISAM DEFAULT CHARSET=utf8;  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))ENGINE=MyISAM DEFAULT CHARSET=utf8;  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))ENGINE=MyISAM DEFAULT CHARSET=utf8;  CREATE TABLE QRTZ_LOCKS  (    SCHED_NAME VARCHAR(120) NOT NULL,    LOCK_NAME  VARCHAR(40) NOT NULL,     PRIMARY KEY (SCHED_NAME,LOCK_NAME))ENGINE=MyISAM DEFAULT CHARSET=utf8;  commit;

ok,到这里,我们需要的配置基本完成,接下来,就是怎么样去创建任务,实现任务的动态添加,修改。


首先,我们回顾一下,如果要执行一个任务,是不是要自定义一个类,比如SendEmailJob,然后这个类实现Job接口,再重写excute方法?然后再定义jobDetail,Trigger,然后再Scheduler.schdulerJob(jobDetail,trigger),安排任务?

那如果业务需求改了,现在需要通知用户的任务,抓取新闻的任务,这样的话就需要写非常非常多的job实现类,对吧?

所以,考虑到这一点,我们可以写一个QuartzJobFactory类实现Job接口,作为统一的Job入口,并且在job的上下文中加入一个我们POJO类(保存着一些job的相关信息),那么,在QuartzJobFactory类的excute方法中,我们可以根据我们传进去的POJO类对象获取当前job的相关信息,从而实现动态的去具体执行什么任务(方法)。

下面详细解释下实现方法:

这个是我自己定义的POJO类对象,用于保存当前任务的详细信息。
public class ScheduleJob implements Serializable {    private static final long serialVersionUID = -5115028108119830917L;    /**     * 任务名称     */    private String jobName;    /**     * 任务分组     */    private String jobGroup;    /**     * 触发器名称(默认和任务名称相同)     */    private String triggerName;    /**     * 触发器分组(默认和任务分组相同)     */    private String triggerGroup;    /**     * 任务需要调用的是哪个类的类名     */    private String className;    /**     * 任务需要调用的是哪个类的方法名     */    private String methodName;    /**     * 方法所需参数数组     */    private ArrayList paramArray;    /**     * 任务运行时间表达式     */    private String cron;    /**     * 任务运行时间(特指只运行一次的任务)     */    private String runDate;    /**     * 任务描述     */    private String desc;    getter and setter.....}

这个POJO类主要的作用的,保存任务的一些相关信息。当我们在创建jobDetail的时候,可以获取他的dataMap,set进去。这样我们可以在QuartzJobFactory的excute方法中的参数中获取到dataMap,并从中拿到这个POJO类对象,从而获取了该job具体要怎么执行,执行哪个方法,方法参数是什么,就都有了!

接下来,我们继续看:

这个是我定义的QuartzJobFactory,Job的统一入口:
@PersistJobDataAfterExecutionpublic class QuartzJobFactory implements Job {    /**     * The constant logger.     */    private static final Logger logger = LoggerFactory.getLogger(Constants.LOG_SYSTEM_RUN);    public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {        ScheduleJob scheduleJob = (ScheduleJob) jobExecutionContext.getMergedJobDataMap().get("scheduleJob");        //获取service层接口名和接口方法名        String springBeanName = scheduleJob.getClassName();        String methodName = scheduleJob.getMethodName();        //获取参数数组和参数类型数组        ArrayList paramArray = scheduleJob.getParamArray();        Class[] paramType = new Class[paramArray.size()];        for (int i = 0; i < paramArray.size(); i++) {            paramType[i] = paramArray.get(i).getClass();        }        /**         * 反射运行service层的方法         */        Object bean = SpringContextsUtil.getBean(springBeanName);        Method method = ReflectionUtils.findMethod(bean.getClass(), methodName, paramType);        ReflectionUtils.invokeMethod(method, bean, paramArray.toArray());        logger.info("任务名称 = [" + scheduleJob.getJobName() + "]," +                "任务调用的类=[" + scheduleJob.getClassName() + "]," +                "任务调用的方法=[" + scheduleJob.getMethodName() + "]---->>成功启动运行");    }}
有状态的两个注解
支持并发
@PersistJobDataAfterExecution
如果不支持并发
@disallowconcurrentexecution

通过SpringContextsUtil工具类调用getBean去获取bean,这里的参数是String 类型的beanName,也就是我们配置文件配置bean是name那个属性。或者是我们在写Service层的时候,通过注@Service("beanName")   ,当然是推荐后者啦!简单方便。
获得了bean以后,在通过方法名获取Method,再利用反射就可以实现对Service层方法的调用了。

下面是我的SpringContextUtil工具类:
@Componentpublic class SpringContextsUtil implements ApplicationContextAware {private static ApplicationContext applicationContext;public static Object getBean(String beanName) throws BeansException {return applicationContext.getBean(beanName);}public static <T> T getBean(String beanName, Class<T> clazs) {return applicationContext.getBean(beanName, clazs);}@Overridepublic void setApplicationContext(ApplicationContext applicationContext)throws BeansException {SpringContextsUtil.applicationContext = applicationContext;}}

ok,现在就只剩下对任务的添加,删除,启动,暂停等动态操作了。

为了方便管理,在我的quartz项目中,我的jobName和triggerName相同,jobGroup和triggerGroup相同。

既然是对任务的一些动态操作。我创了一个QuartzProducer接口,里面有对job的动态操作的一些方法。如下:
public interface QuartzProducer {    /**     * 添加简单任务,只运行一次的任务     *     * @param jobName     * @param jobGroup     * @param className     * @param methodName     * @param paramArray     * @param runDateTime 格式:yyyyMMddHHmmss       * @throws SchedulerException     * @throws ParseException     */    public void addSimpleJob(String jobName, String jobGroup, String className, String methodName, ArrayList paramArray, String runDateTime) throws SchedulerException, ParseException;    /**     * 添加循环任务,特定时间循环运行,例如每个星期3,12点运行等     *     * @param jobName     * @param jobGroup     * @param className     * @param methodName     * @param paramArray     * @param cron     * @throws SchedulerException     */    public void addCronJob(String jobName, String jobGroup, String className, String methodName, ArrayList paramArray, String cron) throws SchedulerException;    /**     * 修改简单任务,一般指修改运行时间     *     * @param jobName     * @param jobGroup     * @param runDateTime 格式:yyyyMMddHHmmss       */    public void updateSimpleJob(String jobName, String jobGroup, String runDateTime) throws SchedulerException, ParseException;    /**     * 修改cron任务,一般指修改循环运行时间     *     * @param jobName     * @param jobGroup     * @param cron     */    public void updateCronJob(String jobName, String jobGroup, String cron) throws SchedulerException;    /**     * 移除任务     *     * @param jobName     * @param jobGroup     */    public void deleteJob(String jobName, String jobGroup) throws SchedulerException;    /**     * 移除所有任务     */    public void deleteAll() throws SchedulerException;    /**     * 暂停任务     *     * @param jobName     * @param jobGroup     */    public void pauseJob(String jobName, String jobGroup) throws SchedulerException;    /**     * 暂停所有任务     */    public void pauseAll() throws SchedulerException;    /**     * 恢复某个任务     *     * @param jobName     * @param jobGroup     */    public void resumeJob(String jobName, String jobGroup) throws SchedulerException;    /**     * 恢复所有     */    public void resumeAll() throws SchedulerException;    /**     * 关闭任务调度器     */    public void shutDown() throws SchedulerException;    /**     * 开启任务调度器     */    public void startScheduler() throws SchedulerException;}

实现类:
//定义该bean的name为"quartzProducer"@Service("quartzProducer")public class QuartzProducerImpl implements QuartzProducer {//虽然在Spring配置中配置的是SchedulerFactoryBean这个类,但是我们自动转配就写这样,一样是可以使用的    @Autowired    private Scheduler scheduler;    @Override    public void addSimpleJob(String jobName, String jobGroup, String className, String methodName, ArrayList paramArray, String runDateTime) throws SchedulerException, ParseException {        //判断是否已存在相同jobName,jobGroup,若存在则删除        if (scheduler.getJobDetail(JobKey.jobKey(jobName, jobGroup)) != null) {            deleteJob(jobName, jobGroup);        }        JobDetail jobDetail = JobBuilder.newJob(QuartzJobFactory.class).withIdentity(jobName, jobGroup).build();        //任务具体执行的内容封装,返回给job统一入口        ScheduleJob job = new ScheduleJob(jobName, jobGroup, jobName, jobGroup, className, methodName, paramArray, null, runDateTime, null);        //将我自定义的POJO类存放到DataMap中jobDetail.getJobDataMap().put("scheduleJob", job);        //创建SimpleTrigger,在特定时间仅运行一次        Date runDate = DateUtils.parseDate(runDateTime, DateStyle.YYYYMMDDHHMMSS.getValue());        SimpleTrigger trigger = (SimpleTrigger) TriggerBuilder.newTrigger().withIdentity(jobName, jobGroup).                startAt(runDate).build();        scheduler.scheduleJob(jobDetail, trigger);    }}

只贴了一个添加的方法,其他方法可以自行实现。注释应该比较清楚了,这里不过多赘述。

ok,到这里,我们已经完全了quartz跟spring的整合,以及如何调用service层的业务逻辑,任务的动态处理。


接下来就非常非常简单了,一般来说就是前端调用controller层,给它传参,比如要调用什么接口,接口的方法,jobname,jobgroup,方法参数,什么时候运行,或者是cron表达式。然后controller层再调用我们刚刚写的quartzProducer就可以了。


其他方法,比如定时任务修改,停用,删除,都是类似的,controller-->service

好了,大概的思路就是这样子,如果有什么不对的地方请拍砖,有什么不太清楚的地方也请拍砖!

转载请注:http://blog.csdn.net/ljqwstc/article/details/78257091


阅读全文
0 0