Spring 和Quartz2 整合实现动态定时任务

时间:2022-04-17 21:32:44


问题起始:

        最近要做一个定时任务,使用Spring的定时任务配置也可以实现。但是很多时候,我们常常会遇到需要动态的添加或修改任务,而spring中所提供的定时任务组件却只能够通过修改xml中trigger的配置才能控制定时任务的时间以及任务的启用或停止,这在带给我们方便的同时也失去了动态配置任务的灵活性。我搜索了一些网上的解决方法,都没有很好的解决这个问题,而且大多数提到的解决方案都停留在Quartz 1.x系列版本上,所用到的代码和API已经不能适用于新版本的Spring和Quartz。那么让我们来解决它吧、

知识点补充:

      1、quartz任务调度快速入门:

          任务调度快速入门

          该资料使用 的是旧版本的quarzt,里面的实例化 jobdetail  和 Trigger的方式都不适用了。但是,对很多基础的概念解释的相当清晰。推荐只看概念,加深理解任务调度的工作机制。

      2、带参数执行 任务调度

          在job中,不可能不需要参数,这时候参数的传递就显得尤为重要了。quartz2提供参数传递方法是:

           1).jobDetail.getJobDataMap().put("timerconfig", timerConfig);   将 timerConfig 以map集合的形式传递给  任务执行时的上下文。

           2). (TimerConfig) context.getJobDetail().getJobDataMap().get("timerconfig");     context是job接口中execute(JobExecutionContext context);将刚刚传递的timerconfig取出

如果timerconfig是对象,则用get();其他的则使用对应的get方法即可。

      3、如何实例化JobDetail

       在quartz2中,实例化任务的方式变化较大,是使用builder进行实例化。 JobDetail jobDetail = newJob(MyTask.class) .withIdentity(name, Groupname).build();

      4、如何实例化Trigger

       Trigger trigger = newTrigger()
                        .withIdentity(name, Groupname)
                        .startNow()
                        .withSchedule(
                                CronScheduleBuilder
                                        .cronSchedule(new CronExpression(
                                                expression))).build();

       此版本不再采用1版本的 SimpleTrigger /cronTrigger.而是在使用调度器(schedule)的时候选择是用 SimpleScheduleBuilder还是 CronScheduleBuilder。

       例如: CronScheduleBuilder .cronSchedule(new CronExpression( expression))//采用cronScheduleBuilder生产 cronTrigger定时器

                    SimpleScheduleBuilder .SimpleSchedule()//采用SimpleScheduleBuilder生产 SimpleTrigger定时器

     

问题解决:

       bean.xml文件:

       

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
xmlns:tx="http://www.springframework.org/schema/tx" xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.0.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-4.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-4.0.xsd
">

<!-- 调度器 -->
<bean id="schedulerFactoryBean" class="org.springframework.scheduling.quartz.SchedulerFactoryBean" lazy-init="false">
</bean>

</beans>

    Mytask,java

   

/*
* @(#)MyTask.javaV0.0.1 2015-1-28, 下午8:34:14
*
*/
package com.jpgk.system.timer.config;

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

import com.jpgk.mq.model.TimerConfig;

public class MyTask implements Job {
private String id;
private String category;
private String destination;
private String uri;
private String clientid;
private String content;
private String expression;
private String method;
private TimerConfig timerConfig;// 一组timerconfig数据对象

// 任务执行覆写
public void execute(JobExecutionContext context)
throws JobExecutionException {
TimerConfig timerConfig = (TimerConfig) context.getJobDetail()
.getJobDataMap().get("timerconfig");
// 属性赋值
initPrivate(timerConfig);
System.out.println(timerConfig.getContent());
}

// 初始化私有属性,这个方法的存在是为了解决 线程池在每次反射实例化MyTask的时候使用无参构造函数,但任务需要这些私有属性作为任务执行的参数
public void initPrivate(TimerConfig timerconfig) {
timerConfig = timerconfig;
category = timerConfig.getCategory().toUpperCase();// 取出任务类型
uri = timerConfig.getUri();// 取出请求路径
destination = timerConfig.getDestination();// 取出目的地
clientid = timerConfig.getClientid();// 客户ID
expression = timerConfig.getExpression();// 表达式
content = timerConfig.getContent();// 请求参数,例如 a=1&b=2
method = timerConfig.getMethod().toUpperCase();
}

}

 调度页面:Testabc.java

/*
 * @(#)Testabc.java    V0.0.1 2015-1-28, 下午8:06:03
 *
 * Copyright 2015 www.ifood517.com. All rights reserved.
 * www.ifood517.com PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 */
package com.jpgk.mq.temp;

import static org.quartz.JobBuilder.newJob;
import static org.quartz.TriggerBuilder.newTrigger;

import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.quartz.CronExpression;
import org.quartz.CronScheduleBuilder;
import org.quartz.CronTrigger;
import org.quartz.JobDetail;
import org.quartz.ScheduleBuilder;
import org.quartz.SchedulerException;
import org.quartz.Trigger;
import org.quartz.TriggerKey;
import org.quartz.impl.StdScheduler;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.jpgk.mq.model.TimerConfig;
import com.jpgk.mq.service.TimerConfigService;
import com.jpgk.system.timer.config.MyTask;

public class Testabc {

    private static StdScheduler stdScheduler;

    public static void main(String[] args) {

        ApplicationContext context = new ClassPathXmlApplicationContext(
                "classpath:applicationContext.xml");
        // 实例化线程池
        stdScheduler = (StdScheduler) context.getBean("schedulerFactoryBean");
        // 取出数据库配置信息
        TimerConfigService timerConfigService = (TimerConfigService) context
                .getBean("timerstaskservice");
        List<TimerConfig> configs = timerConfigService.selectAll();

        for (int i = 0; i < configs.size(); i++) {
            // 进行任务的调度
            /*
             * String category = configs.get(i).getCategory();// 取出任务类型 String
             * uri = configs.get(i).getUri();// 取出请求路径 String destination =
             * configs.get(i).getDestination();// 取出目的地 String clientid =
             * configs.get(i).getClientid();// 客户ID String expression =
             * configs.get(i).getExpression();// 表达式 String content =
             * configs.get(i).getContent();// 请求参数,例如 a=1&b=2 String method =
             * configs.get(i).getMethod().toUpperCase();//请求方式
             */
            switch (configs.get(i).getCategory()) {
            case "HTTP":
                // 必须要请求路径
                if (configs.get(i).getUri() == ""
                        || configs.get(i).getUri() == null) {
                    try {
                        throw (new Exception("请求地址不能为空"));
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
                // 必须要请频率
                if (configs.get(i).getExpression() == ""
                        || configs.get(i).getExpression() == null) {
                    try {
                        throw (new Exception("执行频率不能为空"));
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
                if (configs.get(i).getMethod().toUpperCase().equals("GET")) {
                    // 重新构造uri
                    configs.get(i).setUri(
                            configs.get(i).getUri() + "?"
                                    + configs.get(i).getContent());
                } else {
                    // post须考虑其他方式
                    
                }
                // System.out.println("category->"+category+"uri->"+uri+"destination->"+destination+"clientid->"+clientid+"expression->"+expression+"content->"+content+"method->"+method);
                break;
            default:
                break;
            }
            
            //判断该任务是否存在
            TriggerKey triggerKey = TriggerKey.triggerKey(configs.get(i).getClientid(), configs.get(i).getClientid() + "Group");
            //获取trigger,即在spring配置文件中定义的 bean id="myTrigger"
            Trigger triggerforcheck = null;
            try {
                triggerforcheck = (CronTrigger) stdScheduler.getTrigger(triggerKey);
            } catch (SchedulerException e1) {
                e1.printStackTrace();
            }
            //如果已经存在
            if(null == triggerforcheck){
                //不存在新建一个
                // 构造任务
                JobDetail jobDetail = initJobdetail(configs.get(i).getClientid(),
                        configs.get(i).getClientid() + "Group", configs.get(i));
                // 构造定时器
                Trigger trigger = initTrigger(2, configs.get(i).getClientid(),
                        configs.get(i).getClientid() + "Group", configs.get(i)
                                .getExpression());
                // 注册定时器和任务
                try {
                    stdScheduler.scheduleJob(jobDetail, trigger);
                } catch (SchedulerException e) {
                    e.printStackTrace();
                }
            }else{
                //更改已有的设置
                Trigger trigger = updateTrigger(2,triggerKey,configs.get(i).getExpression());
                try {
                    //重新绑定定时器
                    stdScheduler.rescheduleJob(triggerKey, trigger);
                } catch (SchedulerException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    /**
     * 实例化一个Trigger,根据type返回simple/Cron Trigger
     */
    private static Trigger initTrigger(int type, String name, String Groupname,
            String expression) {
        try {
            if (type == 1) {
                // 1 simpleTrigger
                // Simp
            } else if (type == 2) {
                // conTrigger
                Trigger trigger = newTrigger()
                        .withIdentity(name, Groupname)
                        .startNow()
                        .withSchedule(
                                CronScheduleBuilder
                                        .cronSchedule(new CronExpression(
                                                expression))).build();
                return trigger;
            }
        } catch (Exception e) {
        }
        return null;
    }
    
    /**
     * 修改指定任务的Trigger
    */
    private static Trigger updateTrigger(int type,TriggerKey triggerKey , String expression){
        try {
            //重新构造表达式
            if (type == 1) {
                // 1 simpleTrigger
            } else if (type == 2) {
                Trigger trigger = newTrigger()
                        .withIdentity(triggerKey)
                        .startNow()
                        .withSchedule(
                                CronScheduleBuilder
                                        .cronSchedule(new CronExpression(expression))).build();
                return trigger;
            }
        }catch(Exception e) {
            
        }
        return null;
    }
    
    /**
     * 实例化一个JobDetail
    */
    private static JobDetail initJobdetail(String name, String Groupname,
            TimerConfig timerConfig) {
        JobDetail jobDetail = newJob(MyTask.class)
                .withIdentity(name, Groupname).build();
        // 在每次添加任务的时候要添加额外的参数,这里我传一个对象进行任务私有属性的初始化
        jobDetail.getJobDataMap().put("timerconfig", timerConfig);
        return jobDetail;
    }

}



以上代码涉及到数据库的数据,忽略即可。看看如何实例化JobDetail和Trigger的。留意下怎么在JobDetail中传递TimerConfig参数的。


但是只能说以上代码只是个解决思路,但是任务调度真正的知识不限于这么少。例如,在任务执行过程中强制终止,休眠,更改表达式等。

供参考:任务调度暂停等实现