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

时间:2021-10-03 11:10:08

原文地址:https://blog.csdn.net/ljqwstc/article/details/78257091

首先添加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>

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: DefaultQuartzScheduler
org.quartz.scheduler.rmi.export: false
org.quartz.scheduler.rmi.proxy: false
org.quartz.scheduler.wrapJobExecutionInUserTransaction: false org.quartz.threadPool.class: org.quartz.simpl.SimpleThreadPool
org.quartz.threadPool.threadCount: 10
org.quartz.threadPool.threadPriority: 5
org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread: true org.quartz.jobStore.misfireThreshold: 60000 #============================================================================
# Configure JobStore
#============================================================================ #默认配置,数据保存到内存
#org.quartz.jobStore.class: org.quartz.simpl.RAMJobStore
#持久化配置
org.quartz.jobStore.class:org.quartz.impl.jdbcjobstore.JobStoreTX
org.quartz.jobStore.driverDelegateClass:org.quartz.impl.jdbcjobstore.StdJDBCDelegate
org.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的统一入口:
@PersistJobDataAfterExecution
public 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工具类:
@Component
public 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);
} @Override
public 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
好了,大概的思路就是这样子,如果有什么不对的地方请拍砖,有什么不太清楚的地方也请拍砖!