目录
吃水不忘挖井人系列
1.认识了解各种定时任务实现方式:
2.本文主要参考
3.其他参考
一.业务需求
这里提一下我对@Scheduled和Quartz的一点小看法(如有误解还请指正)
二.软件环境
java版本
SpringBoot版本
Quartz版本(maven的dependency)
三.操作流程
1.加入maven依赖(第二步已写,忽略)
2-1.创建一个测试类自己去理解一下quartz的流程
2-2.实现任务代码
在业务流程中,加入task的方法代码,并在主要逻辑流程里,调用此方法
重点是Task.class应该怎么写 (注意FIXME的 那几行)
3.注意事项
@Component
ApplicationContext的使用
如何从JobDataMap里面获取数据
没有返回值的void方法如何停止代码,既不能break也不是exit?
吃水不忘挖井人系列
1.认识了解各种定时任务实现方式:
Java定时任务调度详解
Java定时任务的三种实现方式
Java定时任务 (spring整合quartz,用xml方式配置)
SpringBoot 使用@Scheduled注解配置定时任务
2.本文主要参考
定时任务框架Quartz-(一)Quartz入门与Demo搭建 小试身手,入门demo
Quartz学习笔记(二)Job、JobDetail、JobDataMap 传参重点,JobDataMap怎么获取?
SpringBoot集成Quartz动态定时任务 进阶提炼,Quartz的Controller分层
ApplicationContextAware使用理解 最后点睛之笔@Component注解
3.其他参考
在线Cron表达式生成器
通过这个生成器,您可以在线生成任务调度比如Quartz的Cron表达式,对Quartz Cron 表达式的可视化双向解析和生成
SpringBoot+Quartz定时任务:Job类对象注入(Demo)
这篇文章倒是用IOC的另一种注入解决方案,不过我没用
一.业务需求
最近遇到几个项目都需要定时任务这个功能,而且是不能用@Scheduled的复杂定时任务,所以研究一下Quartz
这里提一下我对@Scheduled和Quartz的一点小看法(如有误解还请指正)
1.@Scheduled 只能指定固定时间(cron语法)或者延迟x时间后,从项目启动(或延迟x时间后)启动任务,然后一直循环执行
2. Quartz 不仅可以支持以上方式,而主要符合我这次需求的是"指定多久后启动任务,执行几次后结束"
项目有涉及调用银行支付,支付成功后有回调我的方法,查询是否支付成功,成功的话我会写入数据库保存支付交易信息
但是银行的回调方法只有一次,如果这一次遇到某些问题,很可能用户支付成功了,但是因为只有一次回调导致成功的订单没有写入数据库,
所以打算,在这次回调接收到的一开始,就启动Quartz执行业务流程,并每隔5分钟调用一次,一共调用3次,用来查看支付结果
二.软件环境
网上大多都是spring+quartz的文章,还用的是xml的注入方式,和我目前对接的项目不匹配,所以找了一圈之后自己总结一下
java版本
java version "1.8.0_192"
Java(TM) SE Runtime Environment (build 1.8.0_192-b12)
Java HotSpot(TM) 64-Bit Server VM (build 25.192-b12, mixed mode)
SpringBoot版本
<!-- spring boot -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.9.RELEASE</version>
<relativePath/>
</parent>
Quartz版本(maven的dependency)
<!-- quartz https://mvnrepository.com/artifact/org.quartz-scheduler/quartz -->
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz</artifactId>
<version>2.3.0</version>
</dependency>
<!--调度器核心包-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<version>4.3.4.RELEASE</version>
</dependency>
三.操作流程
1.加入maven依赖(第二步已写,忽略)
2-1.创建一个测试类自己去理解一下quartz的流程
这个类直接运行就可以了,里面的代码注释也很详细了
package com.test.scheduled.test;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Random;
import java.util.concurrent.TimeUnit;
import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;
public class ScheduledTasks implements Job {
public static void main(String[] args) {
ScheduledTasks eg = new ScheduledTasks();
try {
eg.simpleSchedule();
} catch (SchedulerException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
@Override
public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
String printTime = new SimpleDateFormat("yy-MM-dd HH-mm-ss").format(new Date());
System.out.println("开始job任务, PrintWordsJob start at:" + printTime + ", prints: Hello Job-" + new Random().nextInt(100));
}
public void simpleSchedule() throws SchedulerException, InterruptedException {
// 1、创建调度器Scheduler
SchedulerFactory schedulerFactory = new StdSchedulerFactory();
Scheduler scheduler = schedulerFactory.getScheduler();
// 2、 通过JobBuilder构建JobDetail实例,并与PrintWordsJob类绑定(Job执行内容)
JobDetail jobDetail = JobBuilder.newJob(ScheduledTasks.class)
.withIdentity("job1", "jobGroup1")//给job命名并分组
.build();
// 3、构建Trigger实例,每隔1s执行一次
Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity("trigger1", "triggerGroup1")//给trigger命名并分组
.startNow()//立即生效
// .startAt(new Date(System.currentTimeMillis() + 1000 * 60 * 5)) // 5分钟后生效
.withSchedule(
SimpleScheduleBuilder.simpleSchedule()
// .withIntervalInMilliseconds(200) // 每隔 200毫秒 执行一次
.withIntervalInSeconds(5) // 每隔5秒执行一次
// .withIntervalInMinutes(10) // 每隔10分钟执行一次
.withRepeatCount(3) // 重复3次
// .repeatForever()//一直执行
)
.build();
//4、执行
scheduler.scheduleJob(jobDetail, trigger);
System.out.println("--------scheduler start ! ------------");
scheduler.start();
//睡眠
TimeUnit.MINUTES.sleep(1);
scheduler.shutdown();
System.out.println("--------scheduler shutdown ! ------------");
}
public void cronSchedule() throws SchedulerException, InterruptedException {
// 1、创建调度器Scheduler
SchedulerFactory schedulerFactory = new StdSchedulerFactory();
Scheduler scheduler = schedulerFactory.getScheduler();
// 2、通过JobBuilder构建JobDetail实例,并与PrintWordsJob类绑定(Job执行内容)
JobDetail jobDetail = JobBuilder.newJob(ScheduledTasks.class)
.usingJobData("jobDetail1", "这个Job用来测试的")
.withIdentity("job1", "group1")//给job命名并分组
.build();
// 3-1、基于表达式构建触发器
CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule("0/5 * * * * ?");
// 3-2、通过TriggerBuilder构建CronTrigger触发器实例(继承于Trigger)
Date startDate = new Date();
startDate.setTime(startDate.getTime() + 5000);
Date endDate = new Date();
endDate.setTime(startDate.getTime() + 5000);
CronTrigger cronTrigger = TriggerBuilder.newTrigger()
.usingJobData("trigger1", "这是jobDetail1的trigger")
.withIdentity("trigger1", "triggerGroup1") //给trigger命名并分组
// .startNow()//立即生效
.startAt(startDate) //startNow 和 startAt 有一个坑!这两个方法是对同一个成员变量进行修改的 也就是说startAt和startNow同时调用的时候任务开始的时间是按后面调用的方法为主的
.endAt(endDate)
.withSchedule(cronScheduleBuilder)
.build();
//4、执行
scheduler.scheduleJob(jobDetail, cronTrigger);
System.out.println("--------scheduler start ! ------------");
scheduler.start();
System.out.println("--------scheduler shutdown ! ------------");
}
}
2-2.实现任务代码
在业务流程中,加入task的方法代码,并在主要逻辑流程里,调用此方法
/**
* @Title: checkStatusSchedule
* @Description: 定时任务, 回调开始后, 调用这个任务
* @param in:
* @return void
* @Author: Tyler
* @Date: 2019/12/12
*/
public void checkStatusSchedule(QueryPaymentResultIn in) throws SchedulerException, InterruptedException {
// 1、创建调度器Scheduler
SchedulerFactory schedulerFactory = new StdSchedulerFactory();
Scheduler scheduler = schedulerFactory.getScheduler();
// 2、 通过JobBuilder构建JobDetail实例,并与Task类绑定(Job执行内容)
JobDetail jobDetail = JobBuilder.newJob(Task.class)
.usingJobData("in", JSON.toJSONString(in))
.withIdentity("job1", "jobGroup1")//给job命名并分组
.build();
// 3、构建Trigger实例
Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity("trigger1", "triggerGroup1")//给trigger命名并分组
.startAt(new Date(System.currentTimeMillis() + 1000 * 60 * 5)) // 5分钟后启动任务,调用Task.class
.withSchedule(SimpleScheduleBuilder.simpleSchedule()
.withIntervalInSeconds(5)// 每隔5秒执行一次
// .withIntervalInMinutes(5)//每隔5分钟执行一次
.withRepeatCount(3)// 重复3次
// .repeatForever()//一直执行
)
.build();
//4、执行
scheduler.scheduleJob(jobDetail, trigger);
System.out.println("--------scheduler start ! ------------");
scheduler.start();
// //睡眠
// TimeUnit.MINUTES.sleep(1);
// scheduler.shutdown();
// System.out.println("--------scheduler shutdown ! ------------");
}
调用checkStatusSchedule方法时,传入 查询交易状态的传入参数 QueryPaymentResultIn in
此QueryPaymentResultIn对象内属性无非是 订单号,订单日期,交易金额之类的,不展示了
重点是Task.class应该怎么写 (注意FIXME的 那几行)
package com.test.scheduled;
import java.text.SimpleDateFormat;
import java.util.List;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
import com.alibaba.fastjson.JSON;
/**
* @description: 支付回调后触发的定时任务:查询3次支付是否成功
* @author: Tyler
* @create: 2019-12-12 11:08
**/
@Component // FIXME 这个注解很重要,不要忘记,不加的话applicationContext是null
public class Task implements Job, ApplicationContextAware {
protected final Log logger = LogFactory.getLog(getClass());
private static ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
// FIXME 通过获取spring的上下文来getBean()就不需要另一种方式注入了
this.applicationContext = applicationContext;
}
@Override
public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
// FIXME 如何从JobDataMap里面获取数据
Object object = jobExecutionContext.getJobDetail().getJobDataMap().get("in");
QueryPaymentResultIn in = JSON.parseObject(object.toString(), QueryPaymentResultIn.class);
// FIXME 如何利用applicationContext来调用Service里的方法
PayService payService= applicationContext.getBean(PayService.class);
QueryPaymentResult payStatus = payService.queryPaymentResult(in);
if (payStatus == null || !"3".equals(payStatus.getOrder_status())) {
// 订单支付失败,不作处理,等待其它2次调用定时任务
return; // FIXME void的方法如何停止代码不往下运行
}
//订单支付成功: ..... 剩余的业务逻辑
}
3.注意事项
@Component
这个注解很重要,不要忘记,不加的话applicationContext是null
ApplicationContext的使用
1. 继承ApplicationContextAware----public class Task implements Job, ApplicationContextAware {}
2.必须是static的,否则还是null private static ApplicationContext applicationContext;
3.重写方法
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
// FIXME 通过获取spring的上下文来getBean()就不需要另一种方式注入了
this.applicationContext = applicationContext;
}
4.如何获取需要的service?
PayService payService= applicationContext.getBean(PayService.class);
QueryPaymentResult payStatus = payService.queryPaymentResult(in);
如何从JobDataMap里面获取数据
目前我没找到更好的代码表达方式,只能先序列化存到JobDataMap里,然后获取出来是Object对象再反序列化
尝试过 强制类型转换(QueryPaymentResultIn)object 可是会报错
Object object = jobExecutionContext.getJobDetail().getJobDataMap().get("in");
QueryPaymentResultIn in = JSON.parseObject(object.toString(), QueryPaymentResultIn.class);
没有返回值的void方法如何停止代码,既不能break也不是exit?
答案是,在需要停止代码的地方,直接 return; 就可以了
这个看着简单,不知道之前还真挺蛋疼
--------ALL BY MedusaSTears
--------2019.12.16