SpringTask介绍
SpringTask是Spring框架提供的一种任务调度和异步处理的解决方案。可以按照约定的时间自动执行某个代码逻辑,它可以帮助开发者在Spring应用中轻松实现定时任务、异步任务等功能,提高应用的效率和可维护性。
应用场景:
- 信用卡每月还款提醒
- 银行贷款每月还款提醒
- 火车票售票系统处理未支付订单
- 入职纪念日为用户发通知
使用步骤
- 在引导类上添加注解
@EnableScheduling,开启定时任务功能 - 哪个方法需要周期性执行,就在方法上加注解
@Scheduled()
注意:必须是bean对象里的方法,否则Spring不能管理
注解@Scheduled
注解@Scheduled:加在方法上,表示要周期性执行
执行的时机,是通过注解的一些属性参数配置,常用的有fixedDelay、fixedRate、cron
一些简单的定时任务,使用
fixedDelay:设置一个毫秒值,是上一次执行结束后,延迟多长时间开始下一个任务
fixedRate:设置一个毫秒值,是上一次任务执行开始之后,延迟多长时间开始下一个任务
一些复杂的定时任务,使用
cron:设置一个cron表达式,通过cron表达式可以非常方便的表示一些复杂的执行周期
cron表达式
语法: 由7个域组成,使用空格隔开,是 秒 分 时 日 月 周 年 但是“年”一般不写,所以使用时通常用前6个域
| 名称 | 是否必须 | 允许值 | 特殊字符 |
|---|---|---|---|
| 秒 | 是 | 0-59 | , - * / |
| 分 | 是 | 0-59 | , - * / |
| 时 | 是 | 0-23 | , - * / |
| 日 | 是 | 1-31 | , - * ? / L W C |
| 月 | 是 | 1-12 或 JAN-DEC | , - * / |
| 周 | 是 | 1-7(表示周一到周日) 或 SUN-SAT | , - * ? / L C # |
说明:
如果写精确值,就表示只有这个值才会触发
* 表示所有合法值都会触发
? 表示忽略这个值。通常 日和周两个域,一个有值,另外一个就必须是?。如果日和周两个域都有值,就会出现不可预测的结果
, 表示可以在一个域上设置多个值
- 表示设置一个范围值。在指定范围内每个合法值都会触发
/ 表示设置一个递增值。比如 1/5 从1开始,每递增5就触发一次
L 表示last,可以用在日和周域上
如果用在日域上,表示月的最后一天
如果用在周域上,前边要加一个数字X,表示最后一个星期X。 3L 最后一个星期三
W 表示Weekday,离指定日期最近的工作日(不跨月)触发
# 用在周域上,X#Y 表示第Y个星期X触发
示例:
0 0 0 1 1 ? 表示每年的1月1日 00:00:00
0 0 0 * * ? 表示每年、每月、每天的 00:00:00
0 0 0 1 * ? 表示每年、每月、1号的 00:00:00
* * * * * ? 表示每年、每月、每天的 每小时、每分钟、每一秒
5 * * * * ? 表示每年、每月、每天的 每小时、每分钟的 第5秒时
0,30 * * * * ? 表示每年、每月、每天的 每小时、每分钟的 第0秒和第30秒执行
0-30 * * * * ? 表示每年、每月、每天的 每小时、每分钟的 第0~30秒中每秒执行
0/5 * * * * ? 表示每年、每月、每天的 每小时、每分钟的 第0秒开始,5秒执行一次
0 0 0 1W * ? 表示每年、每月的1号(离1号最近的工作日)那天 00:00:00
0 0 8 ? 6 7#3 每年父亲节早上8点整触发执行一次(每年6月的第3个星期日)
示例代码:
//每秒钟打印一次当前时间
@Component
public class DemoTask {
//@Scheduled(fixedDelay = 1000)
@Scheduled(cron = "0/5 * * * * ?")
public void printTime(){
System.out.println(LocalTime.now());
}
}
运行结果
应用案例
需求分析
用户下单后可能存在的情况:
- 下单后未支付,订单一直处于 待支付 状态
- 用户收获后管理端未点击完成按钮,订单一直处于 派送中 状态
对于上面两种情况需要通过定时任务来修改订单状态:
-
自动取消 超时未支付 的订单:
通过定时任务,每分钟检查一次是否存在支付超时订单(下单后超过15分钟仍未支付则判定为支付超时订单),如果存在则修改订单状态为“已取消”
-
自动完成 派送中超时 的订单
通过定时任务,每天凌晨1点检查一次是否存在“派送中”的订单,如果存在则修改订单状态为“已完成”
代码及分析步骤:
@Component
@Slf4j
public class OrderTask {
/*
* 清理超时未支付的订单
* 执行时机:每分钟执行一次
* 执行任务:
* 1. 找到15分钟之前下的订单,并且状态是 未支付
* 2. 如果找到了,把这些订单的状态修改成 已取消 设置取消原因和取消时间
* */
@Autowired
private UserOrderMapper orderMapper;
@Scheduled(fixedDelay = 60*1000)
public void cancelTimeoutOrder(){
log.info("扫描超时未支付的订单");
//1. 找到15分钟之前下的订单,并且状态是 未支付
OrdersPageQueryDTO dto = new OrdersPageQueryDTO();
dto.setStatus(Orders.PENDING_PAYMENT);
//获取当前时间,并在当前时间的基础上减15分钟
LocalDateTime time = LocalDateTime.now().plusMinutes(-15);
dto.setEndTime(time);
List<Orders> orders = orderMapper.pageQuery(dto);
//2.如果找到了,把这些订单的状态修改成 已取消 设置取消原因和取消时间
if(orders != null && orders.size() > 0){
for (Orders order : orders) {
order.setStatus(Orders.CANCELLED);
order.setCancelReason("订单超时未支付,自动取消");
order.setCancelTime(LocalDateTime.now());
orderMapper.update(order);
log.info("清理超时未支付的订单:{}",order);
}
}
}
/**
* 定时完成 派送中的订单
* 执行时机:每天凌晨2点整
* 执行任务:
* 1. 查找所有派送中的订单
* 2. 如果找到了,遍历这些订单,直接把订单的状态修改成“已完成”
*/
@Scheduled(cron = "0 0 2 * * ?")
public void completeDeliveryOrder(){
log.info("开始扫描派送中超时的订单..........");
//1. 查询所有派送的订单
OrdersPageQueryDTO dto = new OrdersPageQueryDTO();
dto.setStatus(Orders.DELIVERY_IN_PROGRESS);
List<Orders> ordersList = orderMapper.pageQuery(dto);
//2. 如果找到了,遍历这些订单,直接把订单的状态修改成 ‘已完成’
if(ordersList != null && ordersList.size() > 0){
for (Orders orders : ordersList) {
orders.setStatus(Orders.COMPLETED);
orderMapper.update(orders);
log.info("派送超时自动完成订单:{}",orders);
}
}
}
}