简介

定时任务是后端开发中常见的需求,主要应用场景有定期数据报表、定时消息通知、异步的后台业务逻辑处理、日志分析处理、垃圾数据清理、定时更新缓存等等。

Spring Boot 集成了一整套的定时任务工具,让我们专注于完成逻辑,剩下的基础调度工作将自动完成。

通用实现方式

实现方式 描述
java.util.Timer Timer 提供了一个 java.util.TimerTask 任务支持任务调度。该方式只能按指定频率执行,不能在指定时间运行。由于功能过于单一,使用较少。
Quartz Quartz 是一个功能比较强大的调度器,支持在指定时间运行,也可以按照指定频率执行。缺点是使用起来相对麻烦。
Spring 框架自带的 Schedule 模块 可以看做是轻量级的 Quartz

静态定时任务

@Component
@EnableScheduling
public class StaticScheduleJob { /**
* 上次开始执行时间点后5秒再次执行
*/
@Scheduled(fixedRate = 5000)
public void job3() {
System.out.println("执行任务job3:"+DateUtil.formatDateTime(new Date()));
} /**
* 上次执行完毕时间点后3秒再次执行
*/
@Scheduled(fixedDelay = 3000)
public void job2() {
System.out.println("执行任务job2:"+DateUtil.formatDateTime(new Date()));
} /**
* 每隔10秒执行一次(按照 corn 表达式规则执行)
*/
@Scheduled(cron = "0/10 * * * * ?")
public void job1() {
System.out.println("执行任务job1:"+DateUtil.formatDateTime(new Date()));
} }

@EnableScheduling 注解启用定时调动功能

@Scheduled 参数说明:

  • @Scheduled(fixedRate = 5000):上次开始执行时间点后5秒再次执行
  • @Scheduled(fixedDelay = 3000):上次执行完毕时间点后3秒再次执行
  • @Scheduled(cron = "0/10 * * * * ?"):每隔10秒执行一次(按照 corn 表达式规则执行)

Cron 表达式

1.Cron表达式格式

{秒} {分} {时} {日} {月} {周} {年(可选)}

2.Cron 表达式字段取值范围及说明

字段 取值范围 允许的特殊字符
Seconds(秒) 0 ~ 59 , - * /
Minutes(分) 0 ~ 59 , - * /
Hours(时) 0 ~ 23 , - * /
Day-of-Month(天) 可以用数字 1 ~ 31 中的任意一个值,但要注意特殊月份 , - * ? / L W C
Month(月) 可以用 0 ~ 11 或者字符串 “JAN、FEB、MAR、APR、MAY、JUN、JUL、AUG、SEP、OCT、NOV、DEC” 表示 , - * /
Day-of-Week(每周) 可以用数字 1 ~ 7 表示(1=星期日)或者用字符串 “SUN、MON、TUE、WED、THU、FRI、SAT” 表示 , - * ? / L C #
Year(年) 取值范围(1970-2099),允许为空值 , - * /

3.Cron 表达式中特殊字符的意义

特殊字符 说明
* 表示可以匹配该域的所有值
主要用于日和星期,可以匹配域的任意值,但实际不会。当2个子表达式其中之一被指定了值以后,为了避免冲突,需要将另一个子表达式的值设为?
/ 表示为每隔一段时间。如 0 0/10 * * * ? 其中的 0/10表示从0分钟开始,每隔10分钟执行一次
- 表示范围。如 0 0-5 14 * * ? 表示在每天14:00到14:05期间每1分钟执行一次
, 表示枚举多个值,这些值之间是"或"的关系。如 0 10,30 14 * 3 ? 表示每个星期二14点10分或者14点30分执行一次
L 表示每月或者每周的最后一天。如 0 0 0 L * ? * 表示每月的最后一天执行
W 表示最近工作日。如 0 0 0 15W * ?* 表示每月15号最近的那个工作日执行
# 用来指定具体的周数,"#"前面代表星期,"#"后面代表本月的第几周。如"2#1"表示本月第二周的星期日

4.Cron 在线生成工具

http://www.bejson.com/othertools/cron/

动态定时任务

1.实现 SchedulingConfigurer 接口

@Configuration
public class CustomScheduleConfig implements SchedulingConfigurer { @Autowired
private CronTriggerDao cronTriggerDao; @Override
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
// 上次开始执行时间点后5秒再执行
taskRegistrar.addFixedRateTask(() -> System.out.println("CustomScheduleConfig执行任务job1=>"
+ DateUtil.formatDateTime(new Date()) + ",线程:" + Thread.currentThread().getName()), 5000);
// 上次执行完毕时间点后3秒再执行
taskRegistrar.addFixedDelayTask(() -> System.out.println("CustomScheduleConfig执行任务job2=>"
+ DateUtil.formatDateTime(new Date()) + ",线程:" + Thread.currentThread().getName()), 3000);
// 添加一个配合数据库动态执行的定时任务
TriggerTask triggerTask = new TriggerTask(
// 任务内容.拉姆达表达式
() -> {
// 1)添加任务 Runnable
System.out.println("CustomScheduleConfig执行动态任务job3=>" + DateUtil.formatDateTime(new Date()) + ",线程:"
+ Thread.currentThread().getName());
// 2)设置执行周期
}, triggerContext -> {
// 3)从数据库获取执行周期
String cron = cronTriggerDao.getCronTriggerById(1L);
if (cron != null) {
// 4)返回执行周期(Date)
return new CronTrigger(cron).nextExecutionTime(triggerContext);
} else {
return null;
} });
taskRegistrar.addTriggerTask(triggerTask); } }

2.数据库中初始化数据

这边为了测试简单初始化两行数据:

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0; -- ----------------------------
-- Table structure for t_cron_trigger
-- ----------------------------
DROP TABLE IF EXISTS `t_cron_trigger`;
CREATE TABLE `t_cron_trigger` (
`id` int(8) NOT NULL AUTO_INCREMENT COMMENT '任务id',
`cron` varchar(20) COLLATE utf8_bin DEFAULT NULL COMMENT 'cron表达式',
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
`update_time` datetime DEFAULT NULL COMMENT '更新时间',
`is_deleted` int(1) DEFAULT '0' COMMENT '作废状态 0-否 1-是',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8 COLLATE=utf8_bin; -- ----------------------------
-- Records of t_cron_trigger
-- ----------------------------
BEGIN;
INSERT INTO `t_cron_trigger` VALUES (1, '0/10 * * * * ?', '2019-10-30 13:40:14', NULL, 0);
INSERT INTO `t_cron_trigger` VALUES (2, '0/7 * * * * ?', '2019-10-30 13:40:34', NULL, 0);
COMMIT; SET FOREIGN_KEY_CHECKS = 1;

3.从数据库中读取 cron 表达式值

CronTrigger 实体类

public class CronTrigger implements Serializable{

	/**
*
*/
private static final long serialVersionUID = 880141459783509786L; private Long id;//任务id
private String cron;//cron表达式
private String createTime;//创建时间
private String updateTime;//更新时间
private boolean isDeleted=false;//删除状态 public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getCron() {
return cron;
}
public void setCron(String cron) {
this.cron = cron;
}
public String getCreateTime() {
return createTime;
}
public void setCreateTime(String createTime) {
this.createTime = createTime;
}
public String getUpdateTime() {
return updateTime;
}
public void setUpdateTime(String updateTime) {
this.updateTime = updateTime;
}
public boolean isDeleted() {
return isDeleted;
}
public void setDeleted(boolean isDeleted) {
this.isDeleted = isDeleted;
}
}

CronTriggerDao

public interface CronTriggerDao {
/**
* 根据id获取cron表达式
* @param id
* @return
*/
String getCronTriggerById(Long id); }

CronTriggerMapper.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.4//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.zwqh.springboot.dao.CronTriggerDao">
<resultMap type="cn.zwqh.springboot.model.CronTrigger" id="cronTrigger">
<id property="id" column="id"/>
<result property="cron" column="cron"/>
<result property="createTime" column="create_time"/>
<result property="updateTime" column="update_time"/>
<result property="isDeleted" column="is_deleted"/>
</resultMap>
<!-- 根据id获取cron表达式 -->
<select id="getCronTriggerById" resultType="String">
select cron from t_cron_trigger where is_deleted=0 and id=#{id}
</select>
</mapper>

4.测试

启动前记得在启动类上加上 @EnableScheduling

打印日志如下:

多线程定时任务

通过上面的日志我们可以看到任务执行都是单线程的。如果要实现多线程执行任务,我们可以通过在 SchedulingConfigurer 接口的 configureTasks方法中添加线程池即可。

1.CustomScheduleConfig

@Configuration
public class CustomScheduleConfig implements SchedulingConfigurer { @Autowired
private CronTriggerDao cronTriggerDao; @Override
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) { //设定一个长度为10的定时任务线程池
taskRegistrar.setScheduler(Executors.newScheduledThreadPool(10)); // 上次开始执行时间点后5秒再执行
taskRegistrar.addFixedRateTask(() -> System.out.println("CustomScheduleConfig执行任务job1=>"
+ DateUtil.formatDateTime(new Date()) + ",线程:" + Thread.currentThread().getName()), 5000);
// 上次执行完毕时间点后3秒再执行
taskRegistrar.addFixedDelayTask(() -> System.out.println("CustomScheduleConfig执行任务job2=>"
+ DateUtil.formatDateTime(new Date()) + ",线程:" + Thread.currentThread().getName()), 3000);
// 添加第一个配合数据库动态执行的定时任务
TriggerTask triggerTask = new TriggerTask(
// 任务内容.拉姆达表达式
() -> {
// 1)添加任务 Runnable
System.out.println("CustomScheduleConfig执行动态任务job3=>" + DateUtil.formatDateTime(new Date()) + ",线程:"
+ Thread.currentThread().getName());
// 2)设置执行周期
}, triggerContext -> {
// 3)从数据库获取执行周期
String cron = cronTriggerDao.getCronTriggerById(1L);
if (cron != null) {
// 4)返回执行周期(Date)
return new CronTrigger(cron).nextExecutionTime(triggerContext);
} else {
return null;
} });
taskRegistrar.addTriggerTask(triggerTask);
// 添加第二个配合数据库动态执行的定时任务
TriggerTask triggerTask2 = new TriggerTask(
// 任务内容.拉姆达表达式
() -> {
// 1)添加任务 Runnable
System.out.println("CustomScheduleConfig执行动态任务job4=>" + DateUtil.formatDateTime(new Date()) + ",线程:"
+ Thread.currentThread().getName());
// 2)设置执行周期
}, triggerContext -> {
// 3)从数据库获取执行周期
String cron = cronTriggerDao.getCronTriggerById(2L);
if (cron != null) {
// 4)返回执行周期(Date)
return new CronTrigger(cron).nextExecutionTime(triggerContext);
} else {
return null;
} });
taskRegistrar.addTriggerTask(triggerTask2); } }

2.测试

打印日志如下:

示例代码

github

码云

非特殊说明,本文版权归 朝雾轻寒 所有,转载请注明出处.

原文标题:Spring Boot 2.X(十二):定时任务

原文地址:https://www.zwqh.top/article/info/21

如果文章对您有帮助,请扫码关注下我的公众号,文章持续更新中...

Spring Boot 2.X(十二):定时任务的更多相关文章

  1. spring boot实战(第十二篇)整合RabbitMQ

    前言 最近几篇文章将围绕消息中间件RabbitMQ展开,对于RabbitMQ基本概念这里不阐述,主要讲解RabbitMQ的基本用法.Java客户端API介绍.spring Boot与RabbitMQ整 ...

  2. 学习Spring Boot:(十二)Mybatis 中自定义枚举转换器

    前言 在 Spring Boot 中使用 Mybatis 中遇到了字段为枚举类型,数据库存储的是枚举的值,发现它不能自动装载. 解决 内置枚举转换器 MyBatis内置了两个枚举转换器分别是:org. ...

  3. Spring Boot系列教程十二:Spring boot集成Redis

    一.创建项目 项目名称为 "springboot_redis",创建过程中勾选 "Web","Redis",第一次创建Maven需要下载依赖 ...

  4. 【Spring Boot学习之十二】mybatis3 分页打印sql日志

    环境 eclipse 4.7 jdk 1.8 Spring Boot 1.5.2 参考: mybatis手册 Mybatis的插件 PageHelper 分页查询使用方法MyBatis中Like语句使 ...

  5. Spring Boot 入门(十二):报表导出,对比poi、jxl和esayExcel的效率

    本片博客是紧接着Spring Boot 入门(十一):集成 WebSocket, 实时显示系统日志写的 关于poi.jxl和esayExcel的介绍自行百度. jxl最多支持03版excel,所以单个 ...

  6. Spring Boot教程(十二)整合elk(1)

    elk 简介 Elasticsearch是个开源分布式搜索引擎,它的特点有:分布式,零配置,自动发现,索引自动分片,索引副本机制,restful风格接口,多数据源,自动搜索负载等. Logstash是 ...

  7. Java Spring Boot VS .NetCore (二)实现一个过滤器Filter

    Java Spring Boot VS .NetCore (一)来一个简单的 Hello World Java Spring Boot VS .NetCore (二)实现一个过滤器Filter Jav ...

  8. spring boot项目中处理Schedule定时任务

    项目中,因为使用了第三方支付(支付宝和微信支付),支付完毕后,第三方支付平台一般会采用异步回调通知的方式,通知商户支付结果,然后商户根据通知内容,变更商户项目支付订单的状态.一般来说,为了防止商户项目 ...

  9. Spring Boot 2.X(十):自定义注册 Servlet、Filter、Listener

    前言 在 Spring Boot 中已经移除了 web.xml 文件,如果需要注册添加 Servlet.Filter.Listener 为 Spring Bean,在 Spring Boot 中有两种 ...

随机推荐

  1. Django安装与简单事例-02

    Django的下载与基本命令 1.下载Django: 1 pip3 install django 2.创建一个django project 1 django-admin.py startproject ...

  2. [Vue warn]: Duplicate keys detected: 'area'. This may cause an update error.

    运行vue程序,浏览器报错: 原因:检测到重复的密钥:'area',因为在使用v-for循环绑定的时候,key的值是唯一的,不能相同,否则会出现意想不到的bug 解决办法:v-for时绑定的key唯一

  3. 建议收藏 - 专业的MySQL开发规范

    为了项目的稳定,代码的高效,管理的便捷,在开发团队内部会制定各种各样的规范 这里分享一份我们定义的MySQL开发规范,欢迎交流拍砖 数据库对象命名规范 数据库对象 命名规范的对象是指数据库SCHEMA ...

  4. 软件测试的分类&软件测试生命周期

    软件测试的分类: 按测试执行阶段:单元测试.集成测试.系统测试.验收测试.(正式验收测试,Alpha 测试-内侧,Beta 测试-公测) 按测试技术分类:黑盒测试.白盒测试.灰盒测试 按测试对象是否运 ...

  5. HTML5-常用正则表达式

    有关H5正则表达式的一些常用式子,希望热爱编程的同学们多多指教,还有也希望可以关注收藏本站哦!❤^_^❤ 一.校验数字的表达式 1. 数字:^[0-9]*$ 2. n位的数字:^\d{n}$ 3. 至 ...

  6. 一个有意思的js块作用域问题

    1.问题 首先把问题放出来,昨天看了一个掘友发的一个问题,然后跟我同事一起研究了一下,没找出来是为什么,然后我回来一直在想为什么,然后各种找资料研究,从各个方面找为什么,比如js上下文,作用域,js垃 ...

  7. 讨论c/c++计算小数的精度问题

    求出所有100以下整数与一位小数相乘等于相加的浮点数这个有Bug浮点数计算时精度会出现误差 除非使用非常精确的类型或限制浮点的位数 比如 #include <iostream> int m ...

  8. ThinkPHP5通过composer安装Workerman安装失败问题(避坑指南)

    $ composer require topthink/think-workerUsing version ^2.0 for topthink/think-worker./composer.json ...

  9. SpringBoot自定义异常,优雅解决业务逻辑中的错误

    概要 你是不是在为业务逻辑中出现的异常弄的焦头烂额,常常在后台报错,前端却无法提示错误内容,导致用户体验极差?比如下单失败,前端只能提示下单失败,但是却不知道为什么失败,是库存不足,还是余额不足,亦或 ...

  10. ELK 学习笔记之 elasticsearch elasticsearch.yml配置概述

    elasticsearch.yml配置概述: 设置集群名字 cluster.name 定义节点名称 node.name 节点作为master,但是不负责存储数据,只是协调. node.master: ...