Spring+Quartz实现定时任务自动化动态配置

背景:

一个系统定时任务是不可避免的,现有的spring Task也可以很好的解决定时任务的配置;但其中的缺点也是很显而易见的,即不能够做到可插拔式,每次改动都需要重启服务;所以在这里想做一套可自动化配置的定时任务处理机制:

环境:

spring:3.2.0.RELEASE

quartz:2.2.1

原理:

本套机制还是使用Quartz的核心原理进行封装配置。

Quartz的优点有:

1)Quartz将定时任务分为任务和触发器

2)Quartz通过一个TreeSet对所有的触发器进行管理

3)Quartzl能够非常方便的删除定时任务

4)Quartz有一个专门的调度线程对任务进行管理,任务执行有另外专门的线程池

5)Quartz能够通过序列化,将定时任务保存在数据库

先介绍下Quartz使用过程中的一些名词:

SchedulerFactory:调度器工厂

Scheduler:调度器,通过调度器工厂getScheduler()方法生成,主要起到注册管理作用,即将JobDetailTrigger组成一对对进行注册管理

Trigger:触发器,定义任务启动时机,主要有SimpleTriggerCronTirggerDateIntervalTrigger,和NthIncludedDayTrigger四种类型,本文中主要用到的是CronTirgger,通过corn表达式来规定任务执行时间;

Job:表示一个工作,要执行的具体内容。此接口中只有一个方法void execute(JobExecutionContext context)同时job主要有两种类型的job:无状态的(stateless)和有状态的(stateful)。对于同一个trigger来说,有状态的job不能被并行执行,只有上一次触发的任务被执行完之后,才能触发下一次执行。一个job可以被多个trigger关联,但是一个trigger只能关联一个job。

JobDetail:表示一个具体的可执行的调度程序,Job是这个可执行程调度程序所要执行的内容,另外JobDetail还包含了这个任务调度的方案和策略。可使用JobBuilder进行创建并传参;

JobExecutionContextjob上下文,即job执行时的参数,其中的JobDataMap为我们提供了很多的便利。它是JobDetail中的JobDataMapTrigger中的JobDataMap的并集,但是如果存在相同的数据,则后者会覆盖前者的值。

主要流程:

1.定时批跑取出数据库中的批跑任务数据进行组装JobDetailTrigger构建Scheduler

2.循环任务数据:

a.Scheduler不存在且为有效状态的任务在则新建

b.Scheduler存在且为有效状态的任务在则修改成最新的Trigger

c.Scheduler存在但为无效状态则删除

3.重写job执行接口,分别重新无状态和有状态两种

4.反射执行任务

实践:

1.工具类

a.ScheduleJob:任务实体类用以封装任务属性,与数据库相对应,可自行拓展;下面get,set方法已去除:

import java.util.Date;

/**
 * 
 * @模块名称:批跑自动化
 * @Description 任务实体类
 * @version:1.0
 * @author:cc
 * @date:2018年5月15日 上午10:43:27
 */
public class ScheduleJob {
    public static final String STATUS_RUNNING = "1"; // 正在运行

    public static final String STATUS_NOT_RUNNING = "0"; // 已停止

    public static final String CONCURRENT_IS = "1";// 异步,可多个同时执行

    public static final String CONCURRENT_NOT = "0";// 同步,需排队执行

    public static final String STATUS_JOB = "1"; // 有效运行

    public static final String STATUS_NOT_JOB = "0"; // 无效不运行

    /**
     * 任务唯一标识
     */
    private Long jobId;

    /**
     * 任务名称
     * 
     * 任务名称和任务分组组合要求唯一,且新增后不可修改
     */
    private String jobName;

    /**
     * 任务分组
     * 
     * 任务名称和任务分组组合要求唯一,且新增后不可修改
     */
    private String jobGroup;

    /**
     * cron表达式
     */
    private String cronExpression;

    /**
     * 描述
     */
    private String description;

    /**
     * 任务是否有状态即同一任务是否可以多个同时进行
     */
    private String isConcurrent;

    /**
     * spring beanId 相对于beanClass优先级更高
     */
    private String springId;

    /**
     * 任务执行时调用哪个类的方法 包名+类名
     */
    private String beanClass;

    /**
     * 任务调用的方法名
     */
    private String methodName;

    /**
     * 任务参数
     */
    private String jobData;

    /**
     * 任务状态 任务是否正在执行中
     */
    private String runingStatus;

    /**
     * 任务状态 是否启动任务
     */
    private String jobStatus;

    /**
     * 创建时间
     */
    private Date createTime;

    /**
     * 更新时间
     */
    private Date updateTime;
}

b.SpringUtils:spring 类加载工具类:

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;

/**
 * 
 * @模块名称:批跑自动化
 * @Description spring 类加载工具类
 * @version:1.0
 * @author:cc
 * @date:2018年5月15日 上午10:40:26
 */
public final class SpringUtils implements BeanFactoryPostProcessor {

    private static ConfigurableListableBeanFactory beanFactory; // Spring应用上下文环境

    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        SpringUtils.beanFactory = beanFactory;
    }

    /**
     * 获取对象
     * 
     * @param name
     * @return Object 一个以所给名字注册的bean的实例
     * @throws org.springframework.beans.BeansException
     * 
     */
    @SuppressWarnings("unchecked")
    public static < T > T getBean(String name) throws BeansException {
        return (T) beanFactory.getBean(name);
    }

    /**
     * 获取类型为requiredType的对象
     * 
     * @param clz
     * @return
     * @throws org.springframework.beans.BeansException
     * 
     */
    public static < T > T getBean(Class < T > clz) throws BeansException {
        @SuppressWarnings("unchecked")
        T result = (T) beanFactory.getBean(clz);
        return result;
    }

    /**
     * 如果BeanFactory包含一个与所给名称匹配的bean定义,则返回true
     * 
     * @param name
     * @return boolean
     */
    public static boolean containsBean(String name) {
        return beanFactory.containsBean(name);
    }

    /**
     * 判断以给定名字注册的bean定义是一个singleton还是一个prototype。 如果与给定名字相应的bean定义没有被找到,将会抛出一个异常(NoSuchBeanDefinitionException)
     * 
     * @param name
     * @return boolean
     * @throws org.springframework.beans.factory.NoSuchBeanDefinitionException
     * 
     */
    public static boolean isSingleton(String name) throws NoSuchBeanDefinitionException {
        return beanFactory.isSingleton(name);
    }

    /**
     * @param name
     * @return Class 注册对象的类型
     * @throws org.springframework.beans.factory.NoSuchBeanDefinitionException
     * 
     */
    public static Class < ? > getType(String name) throws NoSuchBeanDefinitionException {
        return beanFactory.getType(name);
    }

    /**
     * 如果给定的bean名字在bean定义中有别名,则返回这些别名
     * 
     * @param name
     * @return
     * @throws org.springframework.beans.factory.NoSuchBeanDefinitionException
     * 
     */
    public static String[] getAliases(String name) throws NoSuchBeanDefinitionException {
        return beanFactory.getAliases(name);
    }

}

c.TaskUtils:task工具类反射调用方法:

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

import org.apache.commons.lang.StringUtils;

import pers.cc.quartz.pojo.ScheduleJob;
import pers.cc.taf.log.TAFLog;

/**
 * 
 * @模块名称:批跑自动化
 * @Description task工具类反射调用
 * @version:1.0
 * @author:cc
 * @date:2018年5月15日 上午10:42:04
 */
public class TaskUtils {

    /**
     * 通过反射调用scheduleJob中定义的方法
     * 
     * @param scheduleJob
     */
    @SuppressWarnings("unchecked")
    public static void invokMethod(ScheduleJob scheduleJob) {
        Object object = null;
        Class clazz = null;
        boolean flag = true;
        // 1.获取bean
        // beanId不为空则通过SpringUtils获取
        if (StringUtils.isNotBlank(scheduleJob.getSpringId())) {
            object = SpringUtils.getBean(scheduleJob.getSpringId());
        }
        // beanId为空则通过包名+类名完整路径反射获得
        else if (StringUtils.isNotBlank(scheduleJob.getBeanClass())) {
            try {
                clazz = Class.forName(scheduleJob.getBeanClass());
                object = clazz.newInstance();
            }
            catch (Exception e) {
                flag = false;
                TAFLog.error("未找到" + scheduleJob.getBeanClass() + "对应的class", e);
            }

        }
        if (object == null) {
            flag = false;
            TAFLog.error("任务名称 = [" + scheduleJob.getJobName() + "]---------------未启动成功,请检查是否配置正确!!!");
            return;
        }
        clazz = object.getClass();
        Method method = null;
        // 2.获取方法
        try {
            // 判断参数是否为空
            if (StringUtils.isBlank(scheduleJob.getJobData())) {
                method = clazz.getDeclaredMethod(scheduleJob.getMethodName());
            }
            else {
                method = clazz.getDeclaredMethod(scheduleJob.getMethodName(), new Class[] { String.class });
            }

        }
        catch (NoSuchMethodException e) {
            flag = false;
            TAFLog.error("任务名称 = [" + scheduleJob.getJobName() + "]---------------未启动成功,方法名设置错误!!!", e);
        }
        catch (SecurityException e) {
            TAFLog.error("任务名称 = [" + scheduleJob.getJobName() + "]---------------未启动成功,方法名设置错误!!!", e);
        }
        if (method != null) {
            // 3.执行方法
            try {
                TAFLog.info(scheduleJob.getJobData());
                // 根据参数执行方法
                if (StringUtils.isBlank(scheduleJob.getJobData())) {
                    method.invoke(object);
                }
                else {
                    method.invoke(object, scheduleJob.getJobData());
                }
            }
            catch (IllegalAccessException e) {
                flag = false;
                TAFLog.error("未找到" + scheduleJob.getBeanClass() + "类下" + scheduleJob.getMethodName() + "对应的方法参数设置错误", e);
            }
            catch (IllegalArgumentException e) {
                flag = false;
                TAFLog.error("未找到" + scheduleJob.getBeanClass() + "类下" + scheduleJob.getMethodName() + "对应的方法参数设置错误", e);
            }
            catch (InvocationTargetException e) {
                flag = false;
                TAFLog.error("未找到" + scheduleJob.getBeanClass() + "类下" + scheduleJob.getMethodName() + "对应的方法参数设置错误", e);
            }
        }
        if (flag) {
            TAFLog.info("任务名称 = [" + scheduleJob.getJobName() + "]----------启动成功");
        }

    }
}

d.QuartzJobFactory:重写Job的execute方法(无状态);

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

import pers.cc.quartz.pojo.ScheduleJob;

/**
 * 
 * @模块名称:批跑自动化
 * @Description 计划任务执行处 无状态
 * @version:1.0
 * @author:cc
 * @date:2018年5月15日 上午10:40:06
 */
public class QuartzJobFactory implements Job {

    public void execute(JobExecutionContext context) throws JobExecutionException {
        ScheduleJob scheduleJob = (ScheduleJob) context.getMergedJobDataMap().get("scheduleJob");
        TaskUtils.invokMethod(scheduleJob);
    }
}

e.QuartzJobFactoryDisallowConcurrentExecution:重写Job的execute方法(有状态);

 

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

import pers.cc.quartz.pojo.ScheduleJob;

/**
 * 
 * @模块名称:批跑自动化
 * @Description 若一个方法一次执行不完下次轮转时则等待改方法执行完后才执行下一次操作
 * @version:1.0
 * @author:cc
 * @date:2018年5月15日 上午10:39:13
 */
@DisallowConcurrentExecution
public class QuartzJobFactoryDisallowConcurrentExecution implements Job {

    public void execute(JobExecutionContext context) throws JobExecutionException {
        ScheduleJob scheduleJob = (ScheduleJob) context.getMergedJobDataMap().get("scheduleJob");
        TaskUtils.invokMethod(scheduleJob);

    }
}

2.调度中心

TestComponent:先创建一个测试bean;有两个方法,一个方法有参,一个方法无参;且方法都睡眠15秒达到测试任务有无状态设置的同步性问题;

/**
 * 
 * @模块名称:
 * @Description 批跑自动化测试bean
 * @version:1.0
 * @author:cc
 * @date:2018年5月29日 上午10:35:15
 */
public interface TestComponent {
    /**
     * 
     * 【方法功能描述】测试方法有参数
     * 
     * @param params 【修改描述】
     * @version:1.0
     * @author:cc
     * @date:2018年5月29日 上午10:35:48
     */
    void test1(String params);

    /**
     * 
     * 【方法功能描述】测试方法无参数
     * 
     * @param params 【修改描述】
     * @version:1.0
     * @author:cc
     * @date:2018年5月29日 上午10:35:56
     */
    void test2();
}
import org.springframework.stereotype.Component;

import pers.cc.quartz.component.TestComponent;
import pers.cc.taf.log.TAFLog;

@Component("test")
public class TestComponentImpl implements TestComponent {

    @Override
    public void test1(String params) {
        TAFLog.info("TestComponent:test1开始执行:" + params);
        try {
            Thread.sleep(15 * 1000);
        }
        catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void test2() {
        TAFLog.info("TestComponent:test2开始执行");
        try {
            Thread.sleep(15 * 1000);
        }
        catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

}

InitQuartzJobComponent:调度中心,取任务列表,循环任务列表对调度器Scheduler的新增,修改,删除;

import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

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.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.scheduling.quartz.SchedulerFactoryBean;
import org.springframework.stereotype.Component;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import pers.cc.quartz.pojo.ScheduleJob;
import pers.cc.quartz.util.QuartzJobFactory;
import pers.cc.quartz.util.QuartzJobFactoryDisallowConcurrentExecution;
import pers.cc.taf.log.TAFLog;

/**
 * 
 * @模块名称:
 * @Description 加载job列表进行任务修改
 * @version:1.0
 * @author:cc
 * @date:2018年5月15日 上午10:39:34
 */
@Component("initQuartzJob")
public class InitQuartzJobComponent implements ApplicationContextAware {

    private static ApplicationContext appCtx;

    public static SchedulerFactoryBean schedulerFactoryBean = null;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        if (this.appCtx == null) {
            this.appCtx = applicationContext;
        }
    }

    public static void init() {
        schedulerFactoryBean = (SchedulerFactoryBean) appCtx.getBean(SchedulerFactoryBean.class);
        // 这里从数据库中获取任务信息数据(此案例模拟)
        List < ScheduleJob > jobList = new ArrayList < ScheduleJob >();
        ScheduleJob job1 = new ScheduleJob();
        job1.setJobId((long) 1);
        job1.setJobGroup("测试批跑"); // 任务组
        job1.setJobName("有参方法");// 任务名称
        job1.setIsConcurrent(ScheduleJob.CONCURRENT_IS); // 运行状态sTimetask.getConcurrent() ? "1" : "0"
        job1.setCronExpression("0/5 * * * * ?");// corn表达式
        job1.setSpringId("test");// beanId;相对beanClass优先级高
        job1.setBeanClass("pers.cc.quartz.component.impl.TestComponentImpl");// 完整路径
        job1.setMethodName("test1");// 方法名
        job1.setRuningStatus(ScheduleJob.STATUS_NOT_RUNNING);// 任务运行状态
        job1.setJobStatus(ScheduleJob.STATUS_JOB); // 任务有效状态
        Gson gson = new GsonBuilder().serializeNulls().setDateFormat("yyyy-MM-dd HH:mm:ss").create();
        Map < String, Object > params = new HashMap < String, Object >();
        params.put("time", new Date());
        params.put("name", "cc");
        params.put("age", 14);
        job1.setJobData(gson.toJson(params));// 设置参数
        jobList.add(job1);

        ScheduleJob job2 = new ScheduleJob();
        job2.setJobId((long) 2);
        job2.setJobGroup("测试批跑"); // 任务组
        job2.setJobName("无参方法");// 任务名称
        job2.setIsConcurrent(ScheduleJob.CONCURRENT_NOT); // 运行状态sTimetask.getConcurrent() ? "1" : "0"
        job2.setCronExpression("0/5 * * * * ?");
        job2.setSpringId(null);// beanId;相对beanClass优先级高
        job2.setBeanClass("pers.cc.quartz.component.impl.TestComponentImpl");// 完整路径
        job2.setMethodName("test2");// 方法名
        job2.setRuningStatus(ScheduleJob.STATUS_NOT_RUNNING);// 任务运行状态
        job2.setJobStatus(ScheduleJob.STATUS_JOB); // 任务有效状态
        job2.setJobData(null);// 设置参数
        jobList.add(job2);

        for (ScheduleJob job : jobList) {
            try {
                addJob(job);
            }
            catch (SchedulerException e) {
                TAFLog.error("添加job错误!", e);
            }
        }
    }

    /**
     * 添加任务
     * 
     * @param scheduleJob
     * @throws SchedulerException
     */
    public static void addJob(ScheduleJob job) throws SchedulerException {
        if (job == null) {
            return;
        }
        Scheduler scheduler = schedulerFactoryBean.getScheduler();
        try {
            TAFLog.info(scheduler.getSchedulerName());
            TAFLog.info(scheduler + ":" + job.getJobName() + ":" + job.getJobGroup()
                    + "...........................................add");
        }
        catch (SchedulerException e1) {
            TAFLog.error("初始化scheduler异常!", e1);
        }
        TriggerKey triggerKey = TriggerKey.triggerKey(job.getJobName(), job.getJobGroup());

        CronTrigger trigger = (CronTrigger) scheduler.getTrigger(triggerKey);
        // 通过触发器来判断改任务是否已加入
        // 1. 不存在,创建一个
        if (null == trigger && ScheduleJob.STATUS_JOB.equals(job.getJobStatus())) {
            // a.根据job.getIsConcurrent()字段决定job实现类
            Class clazz = ScheduleJob.CONCURRENT_IS.equals(job.getIsConcurrent()) ? QuartzJobFactory.class
                    : QuartzJobFactoryDisallowConcurrentExecution.class;
            // b.根据实现类通过JobBuilder创建JobDetail
            JobDetail jobDetail = JobBuilder.newJob(clazz).withIdentity(job.getJobName(), job.getJobGroup())
                    .usingJobData("data", job.getJobData()).build();
            // c.传输参数
            jobDetail.getJobDataMap().put("scheduleJob", job);
            // d.通过CronScheduleBuilder和TriggerBuilder创建cron类型的触发器
            CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(job.getCronExpression());

            trigger = TriggerBuilder.newTrigger().withDescription(job.getJobId().toString())
                    .withIdentity(job.getJobName(), job.getJobGroup()).withSchedule(scheduleBuilder).build();
            // e.由JobDetail和Trigger配对添加到调度器中
            scheduler.scheduleJob(jobDetail, trigger);
        }
        // 2.存在,且状态存在则更新Trigger
        else if (trigger != null && ScheduleJob.STATUS_JOB.equals(job.getJobStatus())) {
            // a.Trigger已存在,那么更新相应的定时设置
            CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(job.getCronExpression());

            // b.按新的cronExpression表达式重新构建trigger
            trigger = trigger.getTriggerBuilder().withIdentity(triggerKey).usingJobData("data", job.getJobData())
                    .withSchedule(scheduleBuilder).build();
            // c.设置参数
            trigger.getJobDataMap().put("scheduleJob", job);
            // d.按新的trigger重新设置job执行
            scheduler.rescheduleJob(triggerKey, trigger);
        }
        // 3. 存在,且状态存在则删除Trigger
        else if (trigger != null && ScheduleJob.STATUS_NOT_JOB.equals(job.getJobStatus())) {
            JobKey jobKey = new JobKey(job.getJobName(), job.getJobGroup());
            // 根据jobKey删除
            scheduler.deleteJob(jobKey);
        }
    }
}

spring.xml:通过spring-task来初始话调度器且规划每1分钟更新调度器;

<?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:task="http://www.springframework.org/schema/task"
	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/task
	http://www.springframework.org/schema/task/spring-task-4.0.xsd
	">
	<!-- 引入属性文件 -->
	<bean id="propertyPlaceholderConfigurer"
		class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
	</bean>
	<!-- AppContextFactory -->
	<bean class="pers.cc.taf.context.factory.ContextFactory"></bean>
	<!-- 自动扫描(自动注入) -->
	<context:component-scan base-package="pers.cc">
		<context:exclude-filter type="annotation"
			expression="org.springframework.stereotype.Controller" />
	</context:component-scan>
	<!-- Spring Task -->
	<task:scheduler id="taskScheduler" pool-size="20" />
	<task:scheduled-tasks scheduler="taskScheduler">
		<!-- 初始化系统配置 (每一分钟进行跑批 ) -->
		<task:scheduled ref="configInfo" method="loadResource"
			initial-delay="1000" fixed-delay="600000" />
		<!-- 定时任务系统配置 (每一分钟进行跑批 ) -->
		<task:scheduled ref="initQuartzJob" method="init"
			initial-delay="1000" fixed-delay="60000" />
	</task:scheduled-tasks>
	<!-- 初始化springUtils -->
	<bean id="springUtils" class="pers.cc.quartz.util.SpringUtils" />
	<!-- 初始化SchedulerFactory -->
	<!-- 初始化SchedulerFactory -->
	<bean id="schedulerFactoryBean"
		class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
		<property name="configLocation" value="classpath:quartz.properties" />
	</bean>
</beans>
quartz.properties可以对SchedulerFactory 进行详细配置:
# Default Properties file for use by StdSchedulerFactory
# to create a Quartz Scheduler Instance, if a different
# properties file is not explicitly specified.
#
# ===========================================================================
# Configure Main Scheduler Properties 调度器属性
# ===========================================================================
#org.quartz.scheduler.instanceName= DefaultQuartzScheduler
#org.quartz.scheduler.instanceid=AUTO
#org.quartz.scheduler.rmi.export= false
#org.quartz.scheduler.rmi.proxy= false
#org.quartz.scheduler.wrapJobExecutionInUserTransaction= false
# ===========================================================================  
# Configure ThreadPool 线程池属性  
# ===========================================================================
#线程池的实现类(一般使用SimpleThreadPool即可满足几乎所有用户的需求)
#org.quartz.threadPool.class= org.quartz.simpl.SimpleThreadPool
#指定线程数,至少为1(无默认值)(一般设置为1-100直接的整数合适)
org.quartz.threadPool.threadCount= 10
#设置线程的优先级(最大为java.lang.Thread.MAX_PRIORITY 10,最小为Thread.MIN_PRIORITY 1,默认为5)
#org.quartz.threadPool.threadPriority= 5
#设置SimpleThreadPool的一些属性
#设置是否为守护线程
#org.quartz.threadpool.makethreadsdaemons = false
#org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread= true
#org.quartz.threadpool.threadsinheritgroupofinitializingthread=false
#线程前缀默认值是:[Scheduler Name]_Worker
#org.quartz.threadpool.threadnameprefix=swhJobThead;
# 配置全局监听(TriggerListener,JobListener) 则应用程序可以接收和执行 预定的事件通知
# ===========================================================================
# Configuring a Global TriggerListener 配置全局的Trigger监听器
# MyTriggerListenerClass 类必须有一个无参数的构造函数,和 属性的set方法,目前2.2.x只支持原始数据类型的值(包括字符串)
# ===========================================================================
#org.quartz.triggerListener.NAME.class = com.swh.MyTriggerListenerClass
#org.quartz.triggerListener.NAME.propName = propValue
#org.quartz.triggerListener.NAME.prop2Name = prop2Value
# ===========================================================================
# Configuring a Global JobListener 配置全局的Job监听器
# MyJobListenerClass 类必须有一个无参数的构造函数,和 属性的set方法,目前2.2.x只支持原始数据类型的值(包括字符串)
# ===========================================================================
#org.quartz.jobListener.NAME.class = com.swh.MyJobListenerClass
#org.quartz.jobListener.NAME.propName = propValue
#org.quartz.jobListener.NAME.prop2Name = prop2Value
# ===========================================================================  
# Configure JobStore 存储调度信息(工作,触发器和日历等)
# ===========================================================================
# 信息保存时间 默认值60秒
#org.quartz.jobStore.misfireThreshold= 60000
#保存job和Trigger的状态信息到内存中的类
#org.quartz.jobStore.class= org.quartz.simpl.RAMJobStore
# ===========================================================================  
# Configure SchedulerPlugins 插件属性 配置
# ===========================================================================
# 自定义插件  
#org.quartz.plugin.NAME.class = com.swh.MyPluginClass
#org.quartz.plugin.NAME.propName = propValue
#org.quartz.plugin.NAME.prop2Name = prop2Value
#配置trigger执行历史日志(可以看到类的文档和参数列表)
#org.quartz.plugin.triggHistory.class = #org.quartz.plugins.history.LoggingTriggerHistoryPlugin  
#org.quartz.plugin.triggHistory.triggerFiredMessage = Trigger {1}.{0} fired job {6}.{5} at: {4, date, HH:mm:ss MM/dd/yyyy}  
#org.quartz.plugin.triggHistory.triggerCompleteMessage = Trigger {1}.{0} completed firing job {6}.{5} at {4, date, HH:mm:ss MM/dd/yyyy} with resulting trigger instruction code: {9}  
#配置job调度插件  quartz_jobs(jobs and triggers内容)的XML文档  
#加载 Job 和 Trigger 信息的类   (1.8之前用:#org.quartz.plugins.xml.JobInitializationPlugin
#org.quartz.plugin.jobInitializer.class = org.quartz.plugins.xml.XMLSchedulingDataProcessorPlugin
#指定存放调度器(Job 和 Trigger)信息的xml文件,默认是classpath下quartz_jobs.xml
#org.quartz.plugin.jobInitializer.fileNames = my_quartz_job2.xml  
##org.quartz.plugin.jobInitializer.overWriteExistingJobs = false  
#org.quartz.plugin.jobInitializer.failOnFileNotFound = true  
#自动扫描任务单并发现改动的时间间隔,单位为秒
#org.quartz.plugin.jobInitializer.scanInterval = 10
#覆盖任务调度器中同名的jobDetail,避免只修改了CronExpression所造成的不能重新生效情况
#org.quartz.plugin.jobInitializer.wrapInUserTransaction = false
# ===========================================================================  
# Sample configuration of ShutdownHookPlugin  ShutdownHookPlugin插件的配置样例
# ===========================================================================
#org.quartz.plugin.shutdownhook.class = \org.quartz.plugins.management.ShutdownHookPlugin
#org.quartz.plugin.shutdownhook.cleanShutdown = true
#
# Configure RMI Settings 远程服务调用配置
#
#如果你想quartz-scheduler出口本身通过RMI作为服务器,然后设置“出口”标志true(默认值为false)。
#org.quartz.scheduler.rmi.export = false
#主机上rmi注册表(默认值localhost)
#org.quartz.scheduler.rmi.registryhost = localhost
#注册监听端口号(默认值1099)
#org.quartz.scheduler.rmi.registryport = 1099
#创建rmi注册,false/never:如果你已经有一个在运行或不想进行创建注册
# true/as_needed:第一次尝试使用现有的注册,然后再回来进行创建
# always:先进行创建一个注册,然后再使用回来使用注册
#org.quartz.scheduler.rmi.createregistry = never
#Quartz Scheduler服务端端口,默认是随机分配RMI注册表
#org.quartz.scheduler.rmi.serverport = 1098
#true:链接远程服务调度(客户端),这个也要指定registryhost和registryport,默认为false
# 如果export和proxy同时指定为true,则export的设置将被忽略
#org.quartz.scheduler.rmi.proxy = false

3.实践结果

启动项目,通过下列日志我们可以看到,调度器正常初始化,且每分钟都更新;无状态每5秒执行一次,而有状态则每15秒执行一次

[16:22:05] [INFO] (TaskUtils.java:76) 
[16:22:05] [INFO] (TaskUtils.java:76) {"time":"2018-05-29 16:22:01","age":14,"name":"cc"}
[16:22:05] [INFO] (TestComponentImpl.java:24) TestComponent:test2开始执行
[16:22:05] [INFO] (TestComponentImpl.java:13) TestComponent:test1开始执行:{"time":"2018-05-29 16:22:01","age":14,"name":"cc"}
[16:22:10] [INFO] (TaskUtils.java:76) {"time":"2018-05-29 16:22:01","age":14,"name":"cc"}
[16:22:10] [INFO] (TestComponentImpl.java:13) TestComponent:test1开始执行:{"time":"2018-05-29 16:22:01","age":14,"name":"cc"}
[16:22:15] [INFO] (TaskUtils.java:76) {"time":"2018-05-29 16:22:01","age":14,"name":"cc"}
[16:22:15] [INFO] (TestComponentImpl.java:13) TestComponent:test1开始执行:{"time":"2018-05-29 16:22:01","age":14,"name":"cc"}
[16:22:20] [INFO] (TaskUtils.java:76) {"time":"2018-05-29 16:22:01","age":14,"name":"cc"}
[16:22:20] [INFO] (TestComponentImpl.java:13) TestComponent:test1开始执行:{"time":"2018-05-29 16:22:01","age":14,"name":"cc"}
[16:22:20] [INFO] (TaskUtils.java:99) 任务名称 = [无参方法]----------启动成功
[16:22:20] [INFO] (TaskUtils.java:99) 任务名称 = [有参方法]----------启动成功
[16:22:20] [INFO] (TaskUtils.java:76) 
[16:22:20] [INFO] (TestComponentImpl.java:24) TestComponent:test2开始执行
[16:22:25] [INFO] (TaskUtils.java:76) {"time":"2018-05-29 16:22:01","age":14,"name":"cc"}
[16:22:25] [INFO] (TestComponentImpl.java:13) TestComponent:test1开始执行:{"time":"2018-05-29 16:22:01","age":14,"name":"cc"}
[16:22:25] [INFO] (TaskUtils.java:99) 任务名称 = [有参方法]----------启动成功
[16:22:30] [INFO] (TaskUtils.java:76) {"time":"2018-05-29 16:22:01","age":14,"name":"cc"}
[16:22:30] [INFO] (TestComponentImpl.java:13) TestComponent:test1开始执行:{"time":"2018-05-29 16:22:01","age":14,"name":"cc"}
[16:22:30] [INFO] (TaskUtils.java:99) 任务名称 = [有参方法]----------启动成功
[16:22:35] [INFO] (TaskUtils.java:76) {"time":"2018-05-29 16:22:01","age":14,"name":"cc"}
[16:22:35] [INFO] (TestComponentImpl.java:13) TestComponent:test1开始执行:{"time":"2018-05-29 16:22:01","age":14,"name":"cc"}
[16:22:35] [INFO] (TaskUtils.java:99) 任务名称 = [有参方法]----------启动成功
[16:22:35] [INFO] (TaskUtils.java:99) 任务名称 = [无参方法]----------启动成功
[16:22:35] [INFO] (TaskUtils.java:76) 
[16:22:35] [INFO] (TestComponentImpl.java:24) TestComponent:test2开始执行
[16:22:40] [INFO] (TaskUtils.java:76) {"time":"2018-05-29 16:22:01","age":14,"name":"cc"}
[16:22:40] [INFO] (TestComponentImpl.java:13) TestComponent:test1开始执行:{"time":"2018-05-29 16:22:01","age":14,"name":"cc"}
[16:22:40] [INFO] (TaskUtils.java:99) 任务名称 = [有参方法]----------启动成功
[16:22:45] [INFO] (TaskUtils.java:76) {"time":"2018-05-29 16:22:01","age":14,"name":"cc"}
[16:22:45] [INFO] (TestComponentImpl.java:13) TestComponent:test1开始执行:{"time":"2018-05-29 16:22:01","age":14,"name":"cc"}
[16:22:45] [INFO] (TaskUtils.java:99) 任务名称 = [有参方法]----------启动成功
[16:22:50] [INFO] (TaskUtils.java:99) 任务名称 = [有参方法]----------启动成功
[16:22:50] [INFO] (TaskUtils.java:76) {"time":"2018-05-29 16:22:01","age":14,"name":"cc"}
[16:22:50] [INFO] (TestComponentImpl.java:13) TestComponent:test1开始执行:{"time":"2018-05-29 16:22:01","age":14,"name":"cc"}
[16:22:50] [INFO] (TaskUtils.java:99) 任务名称 = [无参方法]----------启动成功
[16:22:50] [INFO] (TaskUtils.java:76) 
[16:22:50] [INFO] (TestComponentImpl.java:24) TestComponent:test2开始执行
[16:22:55] [INFO] (TaskUtils.java:76) {"time":"2018-05-29 16:22:01","age":14,"name":"cc"}
[16:22:55] [INFO] (TaskUtils.java:99) 任务名称 = [有参方法]----------启动成功
[16:22:55] [INFO] (TestComponentImpl.java:13) TestComponent:test1开始执行:{"time":"2018-05-29 16:22:01","age":14,"name":"cc"}
[16:23:00] [INFO] (TaskUtils.java:76) {"time":"2018-05-29 16:22:01","age":14,"name":"cc"}
[16:23:00] [INFO] (TestComponentImpl.java:13) TestComponent:test1开始执行:{"time":"2018-05-29 16:22:01","age":14,"name":"cc"}
[16:23:00] [INFO] (TaskUtils.java:99) 任务名称 = [有参方法]----------启动成功
[16:23:01] [INFO] (InitQuartzJobComponent.java:112) schedulerFactoryBean
[16:23:01] [INFO] (InitQuartzJobComponent.java:113) org.quartz.impl.StdScheduler@70696c65:有参方法:测试批跑...........................................add
[16:23:01] [INFO] (InitQuartzJobComponent.java:112) schedulerFactoryBean
[16:23:01] [INFO] (InitQuartzJobComponent.java:113) org.quartz.impl.StdScheduler@70696c65:无参方法:测试批跑...........................................add
[16:23:01] [INFO] (TaskUtils.java:76) {"time":"2018-05-29 16:23:01","age":14,"name":"cc"}
[16:23:01] [INFO] (TestComponentImpl.java:13) TestComponent:test1开始执行:{"time":"2018-05-29 16:23:01","age":14,"name":"cc"}
[16:23:05] [INFO] (TaskUtils.java:76) {"time":"2018-05-29 16:23:01","age":14,"name":"cc"}
[16:23:05] [INFO] (TestComponentImpl.java:13) TestComponent:test1开始执行:{"time":"2018-05-29 16:23:01","age":14,"name":"cc"}
[16:23:05] [INFO] (TaskUtils.java:99) 任务名称 = [有参方法]----------启动成功
[16:23:05] [INFO] (TaskUtils.java:99) 任务名称 = [无参方法]----------启动成功
[16:23:05] [INFO] (TaskUtils.java:76) 
[16:23:05] [INFO] (TestComponentImpl.java:24) TestComponent:test2开始执行
[16:23:10] [INFO] (TaskUtils.java:76) {"time":"2018-05-29 16:23:01","age":14,"name":"cc"}
[16:23:10] [INFO] (TestComponentImpl.java:13) TestComponent:test1开始执行:{"time":"2018-05-29 16:23:01","age":14,"name":"cc"}
[16:23:10] [INFO] (TaskUtils.java:99) 任务名称 = [有参方法]----------启动成功
[16:23:15] [INFO] (TaskUtils.java:76) {"time":"2018-05-29 16:23:01","age":14,"name":"cc"}
[16:23:15] [INFO] (TaskUtils.java:99) 任务名称 = [有参方法]----------启动成功
[16:23:15] [INFO] (TestComponentImpl.java:13) TestComponent:test1开始执行:{"time":"2018-05-29 16:23:01","age":14,"name":"cc"}
[16:23:16] [INFO] (TaskUtils.java:99) 任务名称 = [有参方法]----------启动成功
[16:23:20] [INFO] (TaskUtils.java:76) {"time":"2018-05-29 16:23:01","age":14,"name":"cc"}
[16:23:20] [INFO] (TestComponentImpl.java:13) TestComponent:test1开始执行:{"time":"2018-05-29 16:23:01","age":14,"name":"cc"}
[16:23:20] [INFO] (TaskUtils.java:99) 任务名称 = [有参方法]----------启动成功
[16:23:20] [INFO] (TaskUtils.java:99) 任务名称 = [无参方法]----------启动成功
[16:23:20] [INFO] (TaskUtils.java:76) 
[16:23:20] [INFO] (TestComponentImpl.java:24) TestComponent:test2开始执行
[16:23:25] [INFO] (TaskUtils.java:99) 任务名称 = [有参方法]----------启动成功
[16:23:25] [INFO] (TaskUtils.java:76) {"time":"2018-05-29 16:23:01","age":14,"name":"cc"}
[16:23:25] [INFO] (TestComponentImpl.java:13) TestComponent:test1开始执行:{"time":"2018-05-29 16:23:01","age":14,"name":"cc"}
[16:23:30] [INFO] (TaskUtils.java:76) {"time":"2018-05-29 16:23:01","age":14,"name":"cc"}
[16:23:30] [INFO] (TestComponentImpl.java:13) TestComponent:test1开始执行:{"time":"2018-05-29 16:23:01","age":14,"name":"cc"}
[16:23:30] [INFO] (TaskUtils.java:99) 任务名称 = [有参方法]----------启动成功
[16:23:35] [INFO] (TaskUtils.java:99) 任务名称 = [有参方法]----------启动成功
[16:23:35] [INFO] (TaskUtils.java:76) {"time":"2018-05-29 16:23:01","age":14,"name":"cc"}
[16:23:35] [INFO] (TestComponentImpl.java:13) TestComponent:test1开始执行:{"time":"2018-05-29 16:23:01","age":14,"name":"cc"}
[16:23:35] [INFO] (TaskUtils.java:99) 任务名称 = [无参方法]----------启动成功
[16:23:35] [INFO] (TaskUtils.java:76) 
[16:23:35] [INFO] (TestComponentImpl.java:24) TestComponent:test2开始执行
[16:23:40] [INFO] (TaskUtils.java:99) 任务名称 = [有参方法]----------启动成功
[16:23:40] [INFO] (TaskUtils.java:76) {"time":"2018-05-29 16:23:01","age":14,"name":"cc"}
[16:23:40] [INFO] (TestComponentImpl.java:13) TestComponent:test1开始执行:{"time":"2018-05-29 16:23:01","age":14,"name":"cc"}
[16:23:45] [INFO] (TaskUtils.java:76) {"time":"2018-05-29 16:23:01","age":14,"name":"cc"}
[16:23:45] [INFO] (TestComponentImpl.java:13) TestComponent:test1开始执行:{"time":"2018-05-29 16:23:01","age":14,"name":"cc"}
[16:23:45] [INFO] (TaskUtils.java:99) 任务名称 = [有参方法]----------启动成功
[16:23:50] [INFO] (TaskUtils.java:76) {"time":"2018-05-29 16:23:01","age":14,"name":"cc"}
[16:23:50] [INFO] (TestComponentImpl.java:13) TestComponent:test1开始执行:{"time":"2018-05-29 16:23:01","age":14,"name":"cc"}
[16:23:50] [INFO] (TaskUtils.java:99) 任务名称 = [有参方法]----------启动成功
[16:23:50] [INFO] (TaskUtils.java:99) 任务名称 = [无参方法]----------启动成功
[16:23:50] [INFO] (TaskUtils.java:76) 
[16:23:50] [INFO] (TestComponentImpl.java:24) TestComponent:test2开始执行
[16:23:55] [INFO] (TaskUtils.java:76) {"time":"2018-05-29 16:23:01","age":14,"name":"cc"}
[16:23:55] [INFO] (TestComponentImpl.java:13) TestComponent:test1开始执行:{"time":"2018-05-29 16:23:01","age":14,"name":"cc"}
[16:23:55] [INFO] (TaskUtils.java:99) 任务名称 = [有参方法]----------启动成功
[16:24:00] [INFO] (TaskUtils.java:76) {"time":"2018-05-29 16:23:01","age":14,"name":"cc"}
[16:24:00] [INFO] (TestComponentImpl.java:13) TestComponent:test1开始执行:{"time":"2018-05-29 16:23:01","age":14,"name":"cc"}
[16:24:00] [INFO] (TaskUtils.java:99) 任务名称 = [有参方法]----------启动成功
[16:24:01] [INFO] (InitQuartzJobComponent.java:112) schedulerFactoryBean
[16:24:01] [INFO] (InitQuartzJobComponent.java:113) org.quartz.impl.StdScheduler@70696c65:有参方法:测试批跑...........................................add
[16:24:01] [INFO] (InitQuartzJobComponent.java:112) schedulerFactoryBean
[16:24:01] [INFO] (InitQuartzJobComponent.java:113) org.quartz.impl.StdScheduler@70696c65:无参方法:测试批跑...........................................add
[16:24:01] [INFO] (TaskUtils.java:76) {"time":"2018-05-29 16:24:01","age":14,"name":"cc"}
[16:24:01] [INFO] (TestComponentImpl.java:13) TestComponent:test1开始执行:{"time":"2018-05-29 16:24:01","age":14,"name":"cc"}
[16:24:05] [INFO] (TaskUtils.java:99) 任务名称 = [有参方法]----------启动成功
[16:24:05] [INFO] (TaskUtils.java:76) {"time":"2018-05-29 16:24:01","age":14,"name":"cc"}
[16:24:05] [INFO] (TestComponentImpl.java:13) TestComponent:test1开始执行:{"time":"2018-05-29 16:24:01","age":14,"name":"cc"}
[16:24:05] [INFO] (TaskUtils.java:99) 任务名称 = [无参方法]----------启动成功
[16:24:05] [INFO] (TaskUtils.java:76) 
[16:24:05] [INFO] (TestComponentImpl.java:24) TestComponent:test2开始执行

当然这里大家可以写个controller去实时更新调度器去更好的测试;相信结果是一样的;

总结:

a.实现任务自动化配置

b.实现任务有无状态区分,即任务同步异步问题

c.实现按时更新调度器

d.实现beanId和类两种反射机制

e.实现有无参数两种方法调用方式

注:补scheduler.rescheduleJob立即执行问题

  • 2
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 7
    评论
首先,需要在pom.xml中引入以下依赖: ```xml <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context-support</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.quartz-scheduler</groupId> <artifactId>quartz</artifactId> <version>${quartz.version}</version> </dependency> ``` 其中,`${spring.version}`和`${quartz.version}`是对应的版本号。 接着,在Spring配置文件中配置`SchedulerFactoryBean`和`JobDetailFactoryBean`: ```xml <bean id="scheduler" class="org.springframework.scheduling.quartz.SchedulerFactoryBean"> <!--自动启动 --> <property name="autoStartup" value="true" /> <!--配置数据源 --> <property name="dataSource" ref="dataSource" /> <!--配置Quartz的属性 --> <property name="quartzProperties"> <props> <prop key="org.quartz.scheduler.instanceName">Scheduler</prop> <prop key="org.quartz.scheduler.instanceId">AUTO</prop> <prop key="org.quartz.scheduler.skipUpdateCheck">true</prop> <prop key="org.quartz.scheduler.jmx.export">true</prop> <prop key="org.quartz.jobStore.class">org.quartz.impl.jdbcjobstore.JobStoreTX</prop> <prop key="org.quartz.jobStore.driverDelegateClass">org.quartz.impl.jdbcjobstore.StdJDBCDelegate</prop> <prop key="org.quartz.jobStore.tablePrefix">QRTZ_</prop> <prop key="org.quartz.jobStore.isClustered">false</prop> <prop key="org.quartz.threadPool.class">org.quartz.simpl.SimpleThreadPool</prop> <prop key="org.quartz.threadPool.threadCount">10</prop> <prop key="org.quartz.threadPool.threadPriority">5</prop> </props> </property> <!--配置JobDetail --> <property name="jobDetails"> <list> <bean id="jobDetailFactoryBean" class="org.springframework.scheduling.quartz.JobDetailFactoryBean"> <!--要执行的Job类 --> <property name="jobClass" value="com.example.job.MyJob" /> <!--Job的名称 --> <property name="name" value="MyJob" /> <!--Job的分组 --> <property name="group" value="MyJobGroup" /> <!--是否持久 --> <property name="durability" value="true" /> <!--是否可以被其他任务替代 --> <property name="requestsRecovery" value="true" /> </bean> </list> </property> <!--配置Trigger --> <property name="triggers"> <list> <bean id="cronTriggerFactoryBean" class="org.springframework.scheduling.quartz.CronTriggerFactoryBean"> <!--绑定JobDetail --> <property name="jobDetail" ref="jobDetailFactoryBean" /> <!--触发器的名称 --> <property name="name" value="MyTrigger" /> <!--触发器的分组 --> <property name="group" value="MyTriggerGroup" /> <!--Cron表达式 --> <property name="cronExpression" value="0/10 * * * * ?" /> </bean> </list> </property> </bean> ``` 其中,`SchedulerFactoryBean`是Quartz的核心类,用于创建和管理Quartz的`Scheduler`对象。`JobDetailFactoryBean`用于创建`JobDetail`对象,`CronTriggerFactoryBean`用于创建`Trigger`对象。 在上述配置中,我们配置了一个名为`MyJob`的Job,它由`com.example.job.MyJob`类实现,执行逻辑在该类的`executeInternal()`方法中。我们还配置了一个名为`MyTrigger`的Trigger,它使用Cron表达式`0/10 * * * * ?`,表示每隔10秒执行一次。 最后,在`com.example.job.MyJob`类中实现我们的业务逻辑: ```java public class MyJob extends QuartzJobBean { @Override protected void executeInternal(JobExecutionContext jobExecutionContext) throws JobExecutionException { // 执行业务逻辑 } } ``` 当配置完成后,启动Spring应用程序,Quartz就会按照我们的配置执行定时任务了。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值