一、概述
Quartz技术是一个基于java实现的比较成熟的定时任务框架.
相关概念:
- 工作(Job):用于定义具体执行的工作,实现的任务类,每一个Job必须实现org.quartz.job接口,且只需实现接口定义的execute()方法。
- 工作明细(JobDetail):用于描述定时工作相关的信息
- 触发器(Trigger):描述了工作明细与调度器的对应关系
- 调度器(Scheduler):用于描述触发工作的执行规则,通常使用cron表达式定义规则
1.1 主要接口
- Scheduler 用于与调度程序交互的主程序接口。 Scheduler 调度程序-任务执行计划表,只有安排进执行计划的任务Job(通过scheduler.scheduleJob方法安排进执行计划),当它预先定义的执行时间到了的时候(任务触发trigger),该任务才会执行。
- Job 我们预先定义的希望在未来时间能被调度程序执行的任务类,我们可以自定义。
- JobDetail 使用JobDetail来定义定时任务的实例,JobDetail实例是通过JobBuilder类创建的。
- JobDataMap 可以包含不限量的(序列化的)数据对象,在job实例执行的时候,可以使用 其中的数据;JobDataMap是Java Map接口的一个实现,额外增加了一些便于存取基本类型的数 据的方法。
- Trigger 触发器,Trigger对象是用来触发执行Job的。当调度一个job时,我们实例一个触发器然后调整它的属性来满足job执行的条件。表明任务在什么时候会执行。定义了一个已经被安排的任务将会在什么时候执行的时间条件,比如每2秒就执行一次。
- JobBuilder -用于声明一个任务实例,也可以定义关于该任务的详情比如任务名、组名等,这个声明的实例将会作为一个实际执行的任务。
- TriggerBuilder 触发器创建器,用于创建触发器trigger实例。
- JobListener、TriggerListener、SchedulerListener监听器,用于对组件的监听。
1.2 数据库初始化脚本
二、使用示例
2.1 与springboot整合
依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-quartz</artifactId>
</dependency>定义任务
public class MyQuartz extends QuartzJobBean {
@Override
protected void executeInternal(JobExecutionContext context) throws JobExecutionException {
System.out.println("quartz task run...");
}
}配置类
@Configuration
public class QuartzConfig {
@Bean
public JobDetail printJobDetail(){
//绑定具体的工作
return JobBuilder.newJob(MyQuartz.class).storeDurably().build();
}
@Bean
public Trigger printJobTrigger(){
ScheduleBuilder schedBuilder = CronScheduleBuilder.cronSchedule("0/5 * * * * ?");
//绑定对应的工作明细
return TriggerBuilder.newTrigger().forJob(printJobDetail()).withSchedule(schedBuilder).build();
}
}触发器需要绑定任务,使用forJob()操作传入绑定的工作明细对象。此处可以为工作明细设置名称然后使用名称绑定,也可以直接调用对应方法绑定。触发器中最核心的规则是执行时间,此处使用调度器定义执行时间,执行时间描述方式使用的是cron表达式。
2.2 使用配置方式启动任务(ruoyi)
实现功能:在页面中维护定时任务的信息,指定类的路径名成,是否可并发执行,入参等参数,对定时任务进行增删改及查看执行的任务日志等功能。
配置类
指定定时任务参数信息
/**
* 定时任务配置
*
* @author ruoyi
*/
@Configuration
public class ScheduleConfig {
//spring继承quartz的配置类
@Bean
public SchedulerFactoryBean schedulerFactoryBean(DataSource dataSource) {
SchedulerFactoryBean factory = new SchedulerFactoryBean();
factory.setDataSource(dataSource);
// quartz参数
Properties prop = new Properties();
prop.put("org.quartz.scheduler.instanceName" , "RuoyiScheduler");
prop.put("org.quartz.scheduler.instanceId" , "AUTO");
// 线程池配置
prop.put("org.quartz.threadPool.class" , "org.quartz.simpl.SimpleThreadPool");
prop.put("org.quartz.threadPool.threadCount" , "20");
prop.put("org.quartz.threadPool.threadPriority" , "5");
// JobStore配置
prop.put("org.quartz.jobStore.class" , "org.quartz.impl.jdbcjobstore.JobStoreTX");
// 集群配置
prop.put("org.quartz.jobStore.isClustered" , "true");
prop.put("org.quartz.jobStore.clusterCheckinInterval" , "15000");
prop.put("org.quartz.jobStore.maxMisfiresToHandleAtATime" , "1");
prop.put("org.quartz.jobStore.txIsolationLevelSerializable" , "true");
// sqlserver 启用
// prop.put("org.quartz.jobStore.selectWithLockSQL", "SELECT * FROM {0}LOCKS UPDLOCK WHERE LOCK_NAME = ?");
prop.put("org.quartz.jobStore.misfireThreshold" , "12000");
prop.put("org.quartz.jobStore.tablePrefix" , "QRTZ_");
factory.setQuartzProperties(prop);
factory.setSchedulerName("RuoyiScheduler");
// 延时启动
factory.setStartupDelay(1);
factory.setApplicationContextSchedulerContextKey("applicationContextKey");
// 可选,QuartzScheduler
// 启动时更新己存在的Job,这样就不用每次修改targetObject后删除qrtz_job_details表对应记录了
factory.setOverwriteExistingJobs(true);
// 设置自动启动,默认为true
factory.setAutoStartup(true);
return factory;
}
}抽象定时任务执行类
任务执行时流程抽象类。实现对任务执行的日志进行记录。(是否成功、耗时)
实现类分为可并发执行和不可并发执行(只针对一个任务并发执行)
/**
* 抽象quartz调用
*
* @author ruoyi
*/
public abstract class AbstractQuartzJob implements Job {
private static final Logger log = LoggerFactory.getLogger(AbstractQuartzJob.class);
/**
* 线程本地变量
*/
private static ThreadLocal<Date> threadLocal = new ThreadLocal<>();
@Override
public void execute(JobExecutionContext context) throws JobExecutionException {
SysJob sysJob = new SysJob();
BeanUtils.copyBeanProp(sysJob, context.getMergedJobDataMap().get(ScheduleConstants.TASK_PROPERTIES));
try {
before(context, sysJob);
if (sysJob != null) {
doExecute(context, sysJob);
}
after(context, sysJob, null);
} catch (Exception e) {
log.error("任务执行异常 - :" , e);
after(context, sysJob, e);
}
}
/**
* 执行前
*
* @param context 工作执行上下文对象
* @param sysJob 系统计划任务
*/
protected void before(JobExecutionContext context, SysJob sysJob) {
threadLocal.set(new Date());
}
/**
* 执行后
*
* @param context 工作执行上下文对象
* @param sysJob 系统计划任务
*/
protected void after(JobExecutionContext context, SysJob sysJob, Exception e) {
Date startTime = threadLocal.get();
threadLocal.remove();
final SysJobLog sysJobLog = new SysJobLog();
sysJobLog.setJobName(sysJob.getJobName());
sysJobLog.setJobGroup(sysJob.getJobGroup());
sysJobLog.setInvokeTarget(sysJob.getInvokeTarget());
sysJobLog.setStartTime(startTime);
sysJobLog.setStopTime(new Date());
long runMs = sysJobLog.getStopTime().getTime() - sysJobLog.getStartTime().getTime();
sysJobLog.setJobMessage(sysJobLog.getJobName() + " 总共耗时:" + runMs + "毫秒");
if (e != null) {
sysJobLog.setStatus(Constants.FAIL);
String errorMsg = StringUtils.substring(ExceptionUtil.getExceptionMessage(e), 0, 2000);
sysJobLog.setExceptionInfo(errorMsg);
} else {
sysJobLog.setStatus(Constants.SUCCESS);
}
// 写入数据库当中
SpringUtils.getBean(ISysJobLogService.class).addJobLog(sysJobLog);
}
/**
* 执行方法,由子类重载
*
* @param context 工作执行上下文对象
* @param sysJob 系统计划任务
* @throws Exception 执行过程中的异常
*/
protected abstract void doExecute(JobExecutionContext context, SysJob sysJob) throws Exception;
}可并发执行任务实现类
/**
* 定时任务处理(允许并发执行)
*
* @author ruoyi
*/
public class QuartzJobExecution extends AbstractQuartzJob {
@Override
protected void doExecute(JobExecutionContext context, SysJob sysJob) throws Exception {
JobInvokeUtil.invokeMethod(sysJob);
}
}不可并发执行任务实现类
/**
* 定时任务处理(禁止并发执行) 多一个注解
*
* @author ruoyi
*/
@DisallowConcurrentExecution
public class QuartzDisallowConcurrentExecution extends AbstractQuartzJob {
@Override
protected void doExecute(JobExecutionContext context, SysJob sysJob) throws Exception {
JobInvokeUtil.invokeMethod(sysJob);
}
}对定时任务进行维护的方法
修改数据库数据及运行中的job的各种参数
/**
* 定时任务调度信息 服务层
*
* @author ruoyi
*/
@Service
public class SysJobServiceImpl implements ISysJobService {
@Autowired
private Scheduler scheduler;
@Autowired
private SysJobMapper jobMapper;
/**
* 项目启动时,初始化定时器 主要是防止手动修改数据库导致未同步到定时任务处理(注:不能手动修改数据库ID和任务组名,否则会导致脏数据)
*/
@PostConstruct
public void init() throws SchedulerException, TaskException {
scheduler.clear();
List<SysJob> jobList = jobMapper.selectJobAll();
for (SysJob job : jobList) {
ScheduleUtils.createScheduleJob(scheduler, job);
}
}
/**
* 获取quartz调度器的计划任务列表
*
* @param job 调度信息
* @return
*/
@Override
public List<SysJob> selectJobList(SysJob job) {
return jobMapper.selectJobList(job);
}
/**
* 通过调度任务ID查询调度信息
*
* @param jobId 调度任务ID
* @return 调度任务对象信息
*/
@Override
public SysJob selectJobById(Long jobId) {
return jobMapper.selectJobById(jobId);
}
/**
* 暂停任务
*
* @param job 调度信息
*/
@Override
@Transactional
public int pauseJob(SysJob job) throws SchedulerException {
Long jobId = job.getJobId();
String jobGroup = job.getJobGroup();
job.setStatus(ScheduleConstants.Status.PAUSE.getValue());
int rows = jobMapper.updateJob(job);
if (rows > 0) {
scheduler.pauseJob(ScheduleUtils.getJobKey(jobId, jobGroup));
}
return rows;
}
/**
* 恢复任务
*
* @param job 调度信息
*/
@Override
@Transactional
public int resumeJob(SysJob job) throws SchedulerException {
Long jobId = job.getJobId();
String jobGroup = job.getJobGroup();
job.setStatus(ScheduleConstants.Status.NORMAL.getValue());
int rows = jobMapper.updateJob(job);
if (rows > 0) {
scheduler.resumeJob(ScheduleUtils.getJobKey(jobId, jobGroup));
}
return rows;
}
/**
* 删除任务后,所对应的trigger也将被删除
*
* @param job 调度信息
*/
@Override
@Transactional
public int deleteJob(SysJob job) throws SchedulerException {
Long jobId = job.getJobId();
String jobGroup = job.getJobGroup();
int rows = jobMapper.deleteJobById(jobId);
if (rows > 0) {
scheduler.deleteJob(ScheduleUtils.getJobKey(jobId, jobGroup));
}
return rows;
}
/**
* 批量删除调度信息
*
* @param jobIds 需要删除的任务ID
* @return 结果
*/
@Override
@Transactional
public void deleteJobByIds(Long[] jobIds) throws SchedulerException {
for (Long jobId : jobIds) {
SysJob job = jobMapper.selectJobById(jobId);
deleteJob(job);
}
}
/**
* 任务调度状态修改
*
* @param job 调度信息
*/
@Override
@Transactional
public int changeStatus(SysJob job) throws SchedulerException {
int rows = 0;
String status = job.getStatus();
if (ScheduleConstants.Status.NORMAL.getValue().equals(status)) {
rows = resumeJob(job);
} else if (ScheduleConstants.Status.PAUSE.getValue().equals(status)) {
rows = pauseJob(job);
}
return rows;
}
/**
* 立即运行任务
*
* @param job 调度信息
*/
@Override
@Transactional
public void run(SysJob job) throws SchedulerException {
Long jobId = job.getJobId();
String jobGroup = job.getJobGroup();
SysJob properties = selectJobById(job.getJobId());
// 参数
JobDataMap dataMap = new JobDataMap();
dataMap.put(ScheduleConstants.TASK_PROPERTIES, properties);
scheduler.triggerJob(ScheduleUtils.getJobKey(jobId, jobGroup), dataMap);
}
/**
* 新增任务
*
* @param job 调度信息 调度信息
*/
@Override
@Transactional
public int insertJob(SysJob job) throws SchedulerException, TaskException {
job.setStatus(ScheduleConstants.Status.PAUSE.getValue());
int rows = jobMapper.insertJob(job);
if (rows > 0) {
ScheduleUtils.createScheduleJob(scheduler, job);
}
return rows;
}
/**
* 更新任务的时间表达式
*
* @param job 调度信息
*/
@Override
@Transactional
public int updateJob(SysJob job) throws SchedulerException, TaskException {
SysJob properties = selectJobById(job.getJobId());
int rows = jobMapper.updateJob(job);
if (rows > 0) {
updateSchedulerJob(job, properties.getJobGroup());
}
return rows;
}
/**
* 更新任务
*
* @param job 任务对象
* @param jobGroup 任务组名
*/
public void updateSchedulerJob(SysJob job, String jobGroup) throws SchedulerException, TaskException {
Long jobId = job.getJobId();
// 判断是否存在
JobKey jobKey = ScheduleUtils.getJobKey(jobId, jobGroup);
if (scheduler.checkExists(jobKey)) {
// 防止创建时存在数据问题 先移除,然后在执行创建操作
scheduler.deleteJob(jobKey);
}
ScheduleUtils.createScheduleJob(scheduler, job);
}
/**
* 校验cron表达式是否有效
*
* @param cronExpression 表达式
* @return 结果
*/
@Override
public boolean checkCronExpressionIsValid(String cronExpression) {
return CronUtils.isValid(cronExpression);
}
}项目启动时初始化定时任务数据
/**
* 项目启动时,初始化定时器 主要是防止手动修改数据库导致未同步到定时任务处理(注:不能手动修改数据库ID和任务组名,否则会导致脏数据)
*/
@PostConstruct
public void init() throws SchedulerException, TaskException {
scheduler.clear();
List<SysJob> jobList = jobMapper.selectJobAll();
for (SysJob job : jobList) {
ScheduleUtils.createScheduleJob(scheduler, job);
}
}使用demo
/**
* 定时任务调度测试
*
* @author ruoyi
*/
@Component("ryTask")
public class RyTask {
public void ryMultipleParams(String s, Boolean b, Long l, Double d, Integer i) {
System.out.println(StringUtils.format("执行多参方法: 字符串类型{},布尔类型{},长整型{},浮点型{},整形{}" , s, b, l, d, i));
}
public void ryParams(String params) {
System.out.println("执行有参方法:" + params);
}
public void ryNoParams() {
System.out.println("执行无参方法");
}
}相关工具类
表达式工具类
/**
* cron表达式工具类
*
* @author ruoyi
*/
public class CronUtils {
/**
* 返回一个布尔值代表一个给定的Cron表达式的有效性
*
* @param cronExpression Cron表达式
* @return boolean 表达式是否有效
*/
public static boolean isValid(String cronExpression) {
return CronExpression.isValidExpression(cronExpression);
}
/**
* 返回一个字符串值,表示该消息无效Cron表达式给出有效性
*
* @param cronExpression Cron表达式
* @return String 无效时返回表达式错误描述,如果有效返回null
*/
public static String getInvalidMessage(String cronExpression) {
try {
new CronExpression(cronExpression);
return null;
} catch (ParseException pe) {
return pe.getMessage();
}
}
/**
* 返回下一个执行时间根据给定的Cron表达式
*
* @param cronExpression Cron表达式
* @return Date 下次Cron表达式执行时间
*/
public static Date getNextExecution(String cronExpression) {
try {
CronExpression cron = new CronExpression(cronExpression);
return cron.getNextValidTimeAfter(new Date(System.currentTimeMillis()));
} catch (ParseException e) {
throw new IllegalArgumentException(e.getMessage());
}
}
}定时任务工具类(创建任务)
/**
* 定时任务工具类
*
* @author ruoyi
*/
public class ScheduleUtils {
/**
* 得到quartz任务类
*
* @param sysJob 执行计划
* @return 具体执行任务类
*/
private static Class<? extends Job> getQuartzJobClass(SysJob sysJob) {
boolean isConcurrent = "0".equals(sysJob.getConcurrent());
return isConcurrent ? QuartzJobExecution.class : QuartzDisallowConcurrentExecution.class;
}
/**
* 构建任务触发对象
*/
public static TriggerKey getTriggerKey(Long jobId, String jobGroup) {
return TriggerKey.triggerKey(ScheduleConstants.TASK_CLASS_NAME + jobId, jobGroup);
}
/**
* 构建任务键对象
*/
public static JobKey getJobKey(Long jobId, String jobGroup) {
return JobKey.jobKey(ScheduleConstants.TASK_CLASS_NAME + jobId, jobGroup);
}
/**
* 创建定时任务
*/
public static void createScheduleJob(Scheduler scheduler, SysJob job) throws SchedulerException, TaskException {
Class<? extends Job> jobClass = getQuartzJobClass(job);
// 构建job信息
Long jobId = job.getJobId();
String jobGroup = job.getJobGroup();
JobDetail jobDetail = JobBuilder.newJob(jobClass).withIdentity(getJobKey(jobId, jobGroup)).build();
// 表达式调度构建器
CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(job.getCronExpression());
cronScheduleBuilder = handleCronScheduleMisfirePolicy(job, cronScheduleBuilder);
// 按新的cronExpression表达式构建一个新的trigger
CronTrigger trigger = TriggerBuilder.newTrigger().withIdentity(getTriggerKey(jobId, jobGroup))
.withSchedule(cronScheduleBuilder).build();
// 放入参数,运行时的方法可以获取
jobDetail.getJobDataMap().put(ScheduleConstants.TASK_PROPERTIES, job);
// 判断是否存在
if (scheduler.checkExists(getJobKey(jobId, jobGroup))) {
// 防止创建时存在数据问题 先移除,然后在执行创建操作
scheduler.deleteJob(getJobKey(jobId, jobGroup));
}
scheduler.scheduleJob(jobDetail, trigger);
// 暂停任务
if (job.getStatus().equals(ScheduleConstants.Status.PAUSE.getValue())) {
scheduler.pauseJob(ScheduleUtils.getJobKey(jobId, jobGroup));
}
}
/**
* 设置定时任务策略
*/
public static CronScheduleBuilder handleCronScheduleMisfirePolicy(SysJob job, CronScheduleBuilder cb)
throws TaskException {
switch (job.getMisfirePolicy()) {
case ScheduleConstants.MISFIRE_DEFAULT:
return cb;
case ScheduleConstants.MISFIRE_IGNORE_MISFIRES:
return cb.withMisfireHandlingInstructionIgnoreMisfires();
case ScheduleConstants.MISFIRE_FIRE_AND_PROCEED:
return cb.withMisfireHandlingInstructionFireAndProceed();
case ScheduleConstants.MISFIRE_DO_NOTHING:
return cb.withMisfireHandlingInstructionDoNothing();
default:
throw new TaskException("The task misfire policy '" + job.getMisfirePolicy()
+ "' cannot be used in cron schedule tasks" , Code.CONFIG_ERROR);
}
}
}任务执行工具类
/**
* 任务执行工具
*
* @author ruoyi
*/
public class JobInvokeUtil {
/**
* 执行方法
*
* @param sysJob 系统任务
*/
public static void invokeMethod(SysJob sysJob) throws Exception {
String invokeTarget = sysJob.getInvokeTarget();
String beanName = getBeanName(invokeTarget);
String methodName = getMethodName(invokeTarget);
List<Object[]> methodParams = getMethodParams(invokeTarget);
if (!isValidClassName(beanName)) {
Object bean = SpringUtils.getBean(beanName);
invokeMethod(bean, methodName, methodParams);
} else {
Object bean = Class.forName(beanName).newInstance();
invokeMethod(bean, methodName, methodParams);
}
}
/**
* 调用任务方法
*
* @param bean 目标对象
* @param methodName 方法名称
* @param methodParams 方法参数
*/
private static void invokeMethod(Object bean, String methodName, List<Object[]> methodParams)
throws NoSuchMethodException, SecurityException, IllegalAccessException, IllegalArgumentException,
InvocationTargetException {
if (StringUtils.isNotNull(methodParams) && methodParams.size() > 0) {
Method method = bean.getClass().getDeclaredMethod(methodName, getMethodParamsType(methodParams));
method.invoke(bean, getMethodParamsValue(methodParams));
} else {
Method method = bean.getClass().getDeclaredMethod(methodName);
method.invoke(bean);
}
}
/**
* 校验是否为为class包名
*
* @param str 名称
* @return true是 false否
*/
public static boolean isValidClassName(String invokeTarget) {
return StringUtils.countMatches(invokeTarget, ".") > 1;
}
/**
* 获取bean名称
*
* @param invokeTarget 目标字符串
* @return bean名称
*/
public static String getBeanName(String invokeTarget) {
String beanName = StringUtils.substringBefore(invokeTarget, "(");
return StringUtils.substringBeforeLast(beanName, ".");
}
/**
* 获取bean方法
*
* @param invokeTarget 目标字符串
* @return method方法
*/
public static String getMethodName(String invokeTarget) {
String methodName = StringUtils.substringBefore(invokeTarget, "(");
return StringUtils.substringAfterLast(methodName, ".");
}
/**
* 获取method方法参数相关列表
*
* @param invokeTarget 目标字符串
* @return method方法相关参数列表
*/
public static List<Object[]> getMethodParams(String invokeTarget) {
String methodStr = StringUtils.substringBetween(invokeTarget, "(" , ")");
if (StringUtils.isEmpty(methodStr)) {
return null;
}
String[] methodParams = methodStr.split(",");
List<Object[]> classs = new LinkedList<>();
for (int i = 0; i < methodParams.length; i++) {
String str = StringUtils.trimToEmpty(methodParams[i]);
// String字符串类型,包含'
if (StringUtils.contains(str, "'")) {
classs.add(new Object[]{StringUtils.replace(str, "'" , ""), String.class});
}
// boolean布尔类型,等于true或者false
else if (StringUtils.equals(str, "true") || StringUtils.equalsIgnoreCase(str, "false")) {
classs.add(new Object[]{Boolean.valueOf(str), Boolean.class});
}
// long长整形,包含L
else if (StringUtils.containsIgnoreCase(str, "L")) {
classs.add(new Object[]{Long.valueOf(StringUtils.replaceIgnoreCase(str, "L" , "")), Long.class});
}
// double浮点类型,包含D
else if (StringUtils.containsIgnoreCase(str, "D")) {
classs.add(new Object[]{Double.valueOf(StringUtils.replaceIgnoreCase(str, "D" , "")), Double.class});
}
// 其他类型归类为整形
else {
classs.add(new Object[]{Integer.valueOf(str), Integer.class});
}
}
return classs;
}
/**
* 获取参数类型
*
* @param methodParams 参数相关列表
* @return 参数类型列表
*/
public static Class<?>[] getMethodParamsType(List<Object[]> methodParams) {
Class<?>[] classs = new Class<?>[methodParams.size()];
int index = 0;
for (Object[] os : methodParams) {
classs[index] = (Class<?>) os[1];
index++;
}
return classs;
}
/**
* 获取参数值
*
* @param methodParams 参数相关列表
* @return 参数值列表
*/
public static Object[] getMethodParamsValue(List<Object[]> methodParams) {
Object[] classs = new Object[methodParams.size()];
int index = 0;
for (Object[] os : methodParams) {
classs[index] = (Object) os[0];
index++;
}
return classs;
}
}2.3 使用Demo
依赖
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz</artifactId>
<version>2.3.0</version>
</dependency>任务类
Job实例在Quartz中的生命周期:每次调度器执行Job时,它在调用execute方法前会创建一个新的Job实例,当调用完成后,关联的Job对象实例会被释放,释放的实例会被垃圾回收机制回收。
// 定义任务类
public class HelloJob implements Job {
@Override
// 通过JobExecutionContext对象访问到Quartz运行时候的环境以及Job本身的明细数据。
public void execute(JobExecutionContext arg0) throws JobExecutionException {
// 定义时间
Date date = new Date();
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String dateString = dateFormat.format(date);
// 定义工作任务内容
System.out.println("进行数据库备份操作。当前任务执行的时间:"+dateString);
}
}任务调度demo
JobDetail为Job实例提供了许多设置属性,以及JobDetaMap成员变量属性,它用来存储特定Job实例的状态信息,调度器需要借助JobDetail对象来添加Job实例。
JobDetail重要属性:name、group、jobClass、jobDataMap
public class HelloSchedulerDemo {
public static void main(String[] args) throws Exception {
// 1:从工厂中获取任务调度的实例
Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
// 2:定义一个任务调度实例,将该实例与HelloJob绑定,任务类需要实现Job接口
JobDetail job = JobBuilder.newJob(HelloJob.class)
.withIdentity("job1", "group1") // 定义该实例唯一标识
.build();
// 3:定义触发器 ,马上执行, 然后每5秒重复执行一次
Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity("trigger1", "group1") // 定义该实例唯一标识
.startNow() // 马上执行
.withSchedule(SimpleScheduleBuilder.simpleSchedule()
.repeatSecondlyForever(5)) // 每5秒执行一次
.build();
// 4:使用触发器调度任务的执行
scheduler.scheduleJob(job, trigger);
// 5:开启
scheduler.start();
// 关闭
// scheduler.shutdown();
}
}三、核心知识
3.1 JobDataMap
JobDataMap可以用来装载任何可序列化的数据对象,当job实例对象被执行时这些参数对象会传递给它。实现了JDK的Map接口,并且添加了非常方便的方法用来存取基本数据类型。
数据获取方式
在进行任务调度时,JobDataMap存储在JobExecutionContext中
java// 2:定义一个任务调度实例,将该实例与HelloJob绑定,任务类需要实现Job接口 JobDetail job = JobBuilder.newJob(HelloJob.class) .withIdentity("job1", "group1") // 定义该实例唯一标识 .usingJobData("message", "打印日志") .build(); // 3:定义触发器 ,马上执行, 然后每5秒重复执行一次 // 如果遇到同名的key,Trigger中的.usingJobData("message", "simple触发器")会覆盖 // JobDetail中的.usingJobData("message", "打印日志")。 Trigger trigger = TriggerBuilder.newTrigger() .withIdentity("trigger1", "group1") // 定义该实例唯一标识 .startNow() // 马上执行 //.startAt(triggerStartTime) // 针对某个时刻执行 .withSchedule(SimpleScheduleBuilder.simpleSchedule() .repeatSecondlyForever(5)) // 每5秒执行一次 .usingJobData("message", "simple触发器") .build(); // 获取值 JobDataMap jobDataMap = context.getJobDetail().getJobDataMap(); String jobMessage = jobDataMap.getString("message"); System.out.println("任务参数消息值:"+jobMessage); JobDataMap triggerDataMap = context.getTrigger().getJobDataMap(); String triggerMessage = triggerDataMap.getString("message"); System.out.println("触发器参数消息值:"+triggerMessage);Job实现类中添加setter方法对应JobDataMap的键值
javaprivate String message; public void setMessage(String message) { this.message = message; }
3.2 有无状态的Job
@PersistJobDataAfterExecution
有状态的Job可以理解为多次Job调用期间可以持有一些状态信息,这些状态信息存储在JobDataMap中,而默认的无状态job每次调用时都会创建一个新的JobDataMap。
// 构建时,JobDataMap中设置参数
JobDetail job = JobBuilder.newJob(HelloJob.class)
.withIdentity("job1", "group1") // 定义该实例唯一标识
.usingJobData("message", "打印日志")
.usingJobData("count", 0)
.build();
// 任务类中添加getset方法
private Integer count;
public void setCount(Integer count) {
this.count = count;
}
// execute方法中对参数操作
++count;
System.out.println("count数量:"+count);
context.getJobDetail().getJobDataMap().put("count", count);
// job类上需添加 @PersistJobDataAfterExecution 注解3.3 Trigger
Quartz有一些不同的触发器类型,不过,用得最多的是SimpleTrigger和CronTrigger。
(1)jobKey
表示job实例的标识,触发器被触发时,该指定的job实例会被执行。
(2)startTime
表示触发器的时间表,第一次开始被触发的时间,它的数据类型是java.util.Date。
(3)endTime
指定触发器终止被触发的时间,它的数据类型是java.util.Date。
// 获取jobKey、startTime、endTime
Trigger trigger = context.getTrigger();
System.out.println("jobKey的标识:"+trigger.getJobKey().getName()+";jobKey的组名称:"+trigger.getJobKey().getGroup());
System.out.println("任务开始时间:"+dateFormat.format(trigger.getStartTime())+";任务结束时间:"+dateFormat.format(trigger.getEndTime()));
public class HelloSchedulerDemoTrigger {
public static void main(String[] args) throws Exception {
// 1:从工厂中获取任务调度的实例
Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
// 定义日期
Date startDate = new Date();
// 启动任务,任务在当前时间3秒后执行
startDate.setTime(startDate.getTime()+3000);
// 定义日期
Date endDate = new Date();
// 结束任务,任务在当前时间10秒后停止
endDate.setTime(endDate.getTime()+10000);
// 2:定义一个任务调度实例,将该实例与HelloJob绑定,任务类需要实现Job接口
JobDetail job = JobBuilder.newJob(HelloJobTrigger.class)
.withIdentity("job1", "group1") // 定义该实例唯一标识
.usingJobData("message", "打印日志")
.build();
// 3:定义触发器 ,马上执行, 然后每5秒重复执行一次
Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity("trigger1", "group1") // 定义该实例唯一标识
.startAt(startDate)
.endAt(endDate)
.withSchedule(SimpleScheduleBuilder.simpleSchedule()
.repeatSecondlyForever(5)) // 每5秒执行一次
.usingJobData("message", "simple触发器")
.build();
// 4:使用触发器调度任务的执行
scheduler.scheduleJob(job, trigger);
// 5:开启
scheduler.start();
// 关闭
// scheduler.shutdown();
}
}SimpleTrigger触发器
SimpleTrigger对于设置和使用是最为简单的一种 QuartzTrigger。
它是为那种需要在特定的日期/时间启动,且以一个可能的间隔时间重复执行 n 次的 Job 所设计的。
- SimpleTrigger的属性有:开始时间、结束时间、重复次数和重复的时间间隔。
- 重复次数属性的值可以为0、正整数、或常量 SimpleTrigger.REPEAT_INDEFINITELY。
- 重复的时间间隔属性值必须为大于0或长整型的正整数,以毫秒作为时间单位,当重复的时间间隔为0时,意味着与Trigger同时触发执行。
- 如果有指定结束时间属性值,则结束时间属性优先于重复次数属性,这样的好处在于:当我们需要创建一个每间隔10秒钟触发一次直到指定的结束时间的 Trigger,而无需去计算从开始到结束的所重复的次数,我们只需简单的指定结束时间和使用REPEAT_INDEFINITELY作为重复次数的属性 值即可。
public class HelloSchedulerDemoSimpleTrigger {
public static void main(String[] args) throws Exception {
// 1:从工厂中获取任务调度的实例
Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
// 定义日期
Date startDate = new Date();
// 启动任务,任务在当前时间3秒后执行
startDate.setTime(startDate.getTime()+3000);
// 2:定义一个任务调度实例,将该实例与HelloJob绑定,任务类需要实现Job接口
JobDetail job = JobBuilder.newJob(HelloJobSimpleTrigger.class)
.withIdentity("job1", "group1") // 定义该实例唯一标识
.build();
// 3:定义触发器 ,马上执行, 然后每5秒重复执行一次
Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity("trigger1", "group1") // 定义该实例唯一标识
.startAt(startDate) // 指定开始时间
.endAt(endDate) // 指定结束时间
//.withSchedule(SimpleScheduleBuilder.simpleSchedule().repeatSecondlyForever(5)
// .withRepeatCount(2)) // 每5秒执行一次,连续执行3次后停止,默认值是0
.build();
// 4:使用触发器调度任务的执行
scheduler.scheduleJob(job, trigger);
// 5:开启
scheduler.start();
// 关闭
// scheduler.shutdown();
}
}CronTrigger触发器
CronTrigger是使用Corn表达式来确定何时执行任务。相关参考Corn表达式。
public class HelloSchedulerDemoCronTrigger {
public static void main(String[] args) throws Exception {
// 1:从工厂中获取任务调度的实例
Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
// 2:定义一个任务调度实例,将该实例与HelloJob绑定,任务类需要实现Job接口
JobDetail job = JobBuilder.newJob(HelloJobCronTrigger.class)
.withIdentity("job1", "group1") // 定义该实例唯一标识
.build();
// 3:定义触发器 ,马上执行, 然后每5秒重复执行一次
Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity("trigger1", "group1") // 定义该实例唯一标识
.withSchedule(CronScheduleBuilder.cronSchedule("0/5 * * 6 4 ?"))// 定义表达式
.build();
// 4:使用触发器调度任务的执行
scheduler.scheduleJob(job, trigger);
// 5:开启
scheduler.start();
// 关闭
// scheduler.shutdown();
}
}3.4 SchedulerFactory
所有的Scheduler实例由SchedulerFactory创建,可以使用一组参数(java.util.Properties)来创建和初始化Quartz调度器,配置参数一般存储在quartz.properties文件中。可以调用getScheduler方法就能创建和初始化调度器对象
常用方法
// 初始化对象
// 通过properties获取factory对象
Properties props = new Properties();
new StdSchedulerFactory().initialize(props);
SchedulerFactory schedulerFactory = new StdSchedulerFactory();
// 获取调度器对象
Scheduler scheduler = schedulerFactory.getScheduler();
// 获取调度器开始的时间
Date date = scheduler.scheduleJob(job, trigger);
// 启动调度任务
scheduler.start();
// 任务调度挂起 暂停
scheduler.standby();
// 关闭任务调度
// true 表示等待所有正在执行的job执行完毕之后,再关闭Scheduler;
// false 直接关闭
scheduler.shutdown(false);
// DirectSchedulerFactory是对SchedulerFactory的直接实现,通过它可以直接构建Scheduler、threadpool 等
DirectSchedulerFactory directSchedulerFactory = DirectSchedulerFactory.getInstance();
Scheduler scheduler = directSchedulerFactory.getScheduler();3.5 Quartz.properties 配置
默认路径:quartz-2.3.0中的org.quartz中的quartz.properties,在项目中添加配置文件可以替换底层的配置文件。
#===============================================================
#Configure Main Scheduler Properties 调度器属性
#===============================================================
#调度器的实例名
org.quartz.scheduler.instanceName = QuartzScheduler
#调度器的实例ID,大多数情况设置为auto即可
# 这个值必须在所有调度器实例中是唯一的,尤其是在一个集群环境中,作为集群的唯一key。假如你想Quartz帮你生成这个值的话,可以设置为AUTO。
<NolebasePageProperties />
org.quartz.scheduler.instanceId = AUTO
#===============================================================
#Configure ThreadPool 线程池属性
#===============================================================
#处理Job的线程个数,至少为1,但最多的话最好不要超过100,在多数机器上设置该值超过100的话就会显得相当不实用了,特别是在你的 Job 执行时间较长的情况下
org.quartz.threadPool.threadCount = 5
#线程的优先级,优先级别高的线程比级别低的线程优先得到执行。最小为1,最大为10,默认为5
org.quartz.threadPool.threadPriority = 5
#一个实现了 org.quartz.spi.ThreadPool 接口的类,Quartz 自带的线程池实现类是 org.quartz.smpl.SimpleThreadPool
org.quartz.threadPool.class = org.quartz.simpl.SimpleThreadPool
#===============================================================
#Configure JobStore 作业存储设置
#===============================================================
#要使 Job 存储在内存中需通过设置 org.quartz.jobStrore.class 属性为 org.quartz.simpl.RAMJobStore
org.quartz.jobStore.class = org.quartz.simpl.RAMJobStore
#===============================================================
#Configure Plugins 插件配置
#===============================================================
org.quartz.plugin.jobInitializer.class =
org.quartz.plugins.xml.JobInitializationPlugin
org.quartz.plugin.jobInitializer.overWriteExistingJobs = true
org.quartz.plugin.jobInitializer.failOnFileNotFound = true
org.quartz.plugin.jobInitializer.validating=false3.6 监听器
Quartz的监听器用于当任务调度中你所关注事件发生时,能够及时获取这一事件的通知。
主要有JobListener、TriggerListener、SchedulerListener三种,分别表示任务、触发器、调度器对应的监听器
全局监听器与非全局监听器
全局监听器能够接收到所有的Job/Trigger的事件通知,
而非全局监听器只能接收到在其上注册的Job或Trigger的事件,不在其上注册的Job或Trigger则不会进行监听。
// 从工厂中获取任务调度的实例
Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
// 创建并注册一个全局的Job Listener
scheduler.getListenerManager().addJobListener(new MyJobListener(), EverythingMatcher.allJobs());
// 创建并注册一个指定任务的Job Listener
scheduler.getListenerManager().addJobListener(new MyJobListener(),KeyMatcher.keyEquals(JobKey.jobKey("job1", "group1")));JobListener
public class MyJobListener implements JobListener{
@Override
// 用于获取该JobListener的名称。
public String getName() {
String name = getClass().getSimpleName();
System.out.println("监听器的名称是:"+name);
return name;
}
@Override
// Scheduler在JobDetail将要被执行时调用这个方法。
public void jobToBeExecuted(JobExecutionContext context) {
String jobName = context.getJobDetail().getKey().getName();
System.out.println("Job的名称是:"+jobName+" Scheduler在JobDetail将要被执行时调用这个方法");
}
@Override
// Scheduler在JobDetail即将被执行,但又被TriggerListerner否决时会调用该方法
public void jobExecutionVetoed(JobExecutionContext context) {
String jobName = context.getJobDetail().getKey().getName();
System.out.println("Job的名称是:"+jobName+" Scheduler在JobDetail即将被执行,但又被TriggerListerner否决时会调用该方法");
}
@Override
// Scheduler在JobDetail被执行之后调用这个方法
public void jobWasExecuted(JobExecutionContext context, JobExecutionException jobException) {
String jobName = context.getJobDetail().getKey().getName();
System.out.println("Job的名称是:"+jobName+" Scheduler在JobDetail被执行之后调用这个方法");
}
}TriggerListener
public class MyTriggerListener implements TriggerListener{
private String name;
public MyTriggerListener(String name) {
this.name = name;
}
@Override
// 用于获取触发器的名称
public String getName() {
return name;
}
@Override
// 当与监听器相关联的Trigger被触发,Job上的execute()方法将被执行时,Scheduler就调用该方法。
public void triggerFired(Trigger trigger, JobExecutionContext context) {
String triggerName = trigger.getKey().getName();
System.out.println(triggerName + " 被触发");
}
@Override
// 在 Trigger 触发后,Job 将要被执行时由 Scheduler 调用这个方法。TriggerListener 给了一个选择去否决 Job 的执行。假如这个方法返回 true,这个 Job 将不会为此次 Trigger 触发而得到执行。
public boolean vetoJobExecution(Trigger trigger, JobExecutionContext context) {
String triggerName = trigger.getKey().getName();
System.out.println(triggerName + " 没有被触发");
return true; // true:表示不会执行Job的方法
}
@Override
// Scheduler 调用这个方法是在 Trigger 错过触发时。你应该关注此方法中持续时间长的逻辑:在出现许多错过触发的 Trigger 时,长逻辑会导致骨牌效应。你应当保持这上方法尽量的小。
public void triggerMisfired(Trigger trigger) {
String triggerName = trigger.getKey().getName();
System.out.println(triggerName + " 错过触发");
}
@Override
// Trigger 被触发并且完成了 Job 的执行时,Scheduler 调用这个方法。
public void triggerComplete(Trigger trigger, JobExecutionContext context,
CompletedExecutionInstruction triggerInstructionCode) {
String triggerName = trigger.getKey().getName();
System.out.println(triggerName + " 完成之后触发");
}
}SchedulerListener
SchedulerListener会在Scheduler的生命周期中关键事件发生时被调用。与Scheduler有关的事件包括:增加一个job/trigger,删除一个job/trigger,scheduler发生严重错误,关闭scheduler等。
public class MySchedulerListener implements SchedulerListener{
@Override
// 用于部署JobDetail时调用
public void jobScheduled(Trigger trigger) {
String jobName = trigger.getJobKey().getName();
System.out.println(jobName + " 完成部署");
}
@Override
// 用于卸载JobDetail时调用
public void jobUnscheduled(TriggerKey triggerKey) {
System.out.println(triggerKey + " 完成卸载");
}
@Override
// 当一个 Trigger 来到了再也不会触发的状态时调用这个方法。除非这个 Job 已设置成了持久性,否则它就会从 Scheduler 中移除。
public void triggerFinalized(Trigger trigger) {
System.out.println("触发器被移除 " + trigger.getJobKey().getName());
}
@Override
public void triggerPaused(TriggerKey triggerKey) {
System.out.println(triggerKey + " 正在被暂停");
}
@Override
// Scheduler 调用这个方法是发生在一个 Trigger 或 Trigger 组被暂停时。假如是 Trigger 组的话,triggerName 参数将为 null。
public void triggersPaused(String triggerGroup) {
System.out.println("触发器组 "+triggerGroup + " 正在被暂停");
}
@Override
public void triggerResumed(TriggerKey triggerKey) {
System.out.println(triggerKey + " 正在从暂停中恢复");
}
@Override
// Scheduler 调用这个方法是发生成一个 Trigger 或 Trigger 组从暂停中恢复时。假如是 Trigger 组的话,假如是 Trigger 组的话,triggerName 参数将为 null。参数将为 null。
public void triggersResumed(String triggerGroup) {
System.out.println("触发器组 "+triggerGroup + " 正在从暂停中恢复");
}
@Override
// 任务添加时
public void jobAdded(JobDetail jobDetail) {
System.out.println(jobDetail.getKey()+" 添加工作任务");
}
@Override
public void jobDeleted(JobKey jobKey) {
System.out.println(jobKey+" 删除工作任务");
}
@Override
public void jobPaused(JobKey jobKey) {
System.out.println(jobKey+" 工作任务正在被暂停");
}
@Override
// 当一个或一组 JobDetail 暂停时调用这个方法。
public void jobsPaused(String jobGroup) {
System.out.println("工作任务组 "+jobGroup+" 正在被暂停");
}
@Override
public void jobResumed(JobKey jobKey) {
System.out.println(jobKey+" 正在从暂停中恢复");
}
@Override
// 当一个或一组 Job 从暂停上恢复时调用这个方法。假如是一个 Job 组,jobName 参数将为 null。
public void jobsResumed(String jobGroup) {
System.out.println("工作任务组 "+jobGroup+" 正在从暂停中恢复");
}
@Override
// 在 Scheduler 的正常运行期间产生一个严重错误时调用这个方法。
public void schedulerError(String msg, SchedulerException cause) {
System.out.println("产生严重错误时调用: "+msg+" "+cause.getUnderlyingException());
}
@Override
// 当Scheduler处于StandBy模式时,调用该方法
public void schedulerInStandbyMode() {
System.out.println("调度器在挂起模式下调用");
}
@Override
// 当Scheduler 开启时,调用该方法
public void schedulerStarted() {
System.out.println("调度器 开启时调用");
}
@Override
//
public void schedulerStarting() {
System.out.println("调度器 正在开启时调用");
}
@Override
// 当Scheduler停止时,调用该方法
public void schedulerShutdown() {
System.out.println("调度器 已经被关闭 时调用");
}
@Override
public void schedulerShuttingdown() {
System.out.println("调度器 正在被关闭 时调用");
}
@Override
// 当Scheduler中的数据被清除时,调用该方法。
public void schedulingDataCleared() {
System.out.println("调度器的数据被清除时调用");
}
}