专栏首页Spring Boot 2.X 系列Spring Boot 2.X(十二):定时任务
原创

Spring Boot 2.X(十二):定时任务

简介

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

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

原创声明,本文系作者授权云+社区发表,未经许可,不得转载。

如有侵权,请联系 yunjia_community@tencent.com 删除。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • MYSQL事务他快你慢,都是你自己惹的祸

    实际事务DB里面常见的东西,没有事务的DB 那就不是DB ,但同样完成一个功能,并且书写也相似的存储过程逻辑(事务),怎么就快慢不一。

    AustinDatabases
  • 事务的ACID属性我就是傻傻的分不清。。。

    对于大部分程序员来说,他们的任务就是把现实世界的业务场景映射到数据库世界。比如银行为了存储人们的账户信息会建立一个account表:

    用户5397975
  • 如何设计出一个比较合理的数据归档系统

    在任何的数据库系统中,大部分都会有一个需求,数据归档,业务数据库不应该是永无止境的进行数据存取的目的地。业务数据库主要的功能是满足业务的保留数据的需求,以及相关...

    AustinDatabases
  • 聊聊分布式锁

    对于锁大家肯定不会陌生,在Java中synchronized关键字和ReentrantLock可重入锁在我们的代码中是经常见的,一般我们用其在多线程环境中控制对...

    用户5397975
  • Laravel源码解析之QueryBuilder

    上文我们说到执行 DB::table('users')->get()是由Connection对象执行table方法返回了一个QueryBuilder对象,Que...

    KevinYan
  • Laravel源码解析之Eloquent Model

    上篇文章我们讲了Database的查询构建器Query Builder, 学习了Query Builder为构建生成SQL语句而提供的Fluent Api的代码...

    KevinYan
  • 谈谈数据一致性

    数据一致性这个单词在平常开发中,或者各种文章中都能经常看见,我们常常听见什么东西数据不一致了,造成了一定的损失,赶快修复一下。但是很多同学对一致性具体代表什么意...

    用户5397975
  • Laravel源码分析之模型关联

    上篇文章我们主要讲了Eloquent Model关于基础的CRUD方法的实现,Eloquent Model中除了基础的CRUD外还有一个很重要的部分叫模型关联,...

    KevinYan
  • Laravel源码解析之Database

    在我们学习和使用一个开发框架时,无论使用什么框架,如何连接数据库、对数据库进行增删改查都是学习的重点,在Laravel中我们可以通过两种方式与数据库进行交互:

    KevinYan
  • 记一次神奇的Mysql死锁排查

    说起Mysql死锁,之前写过一次有关Mysql加锁的基本介绍,对于一些基本的Mysql锁或者死锁都有一个简单的认识,可以看下这篇文章为什么开发人员需要了解分布式...

    用户5397975

扫码关注云+社区

领取腾讯云代金券