-
在SpringBoot中使用定时任务非常简单,使用定时任务最常用的三种方式为:静态(基于注解)、动态(基于接口,查询数据库)和多线程定时任务(异步的方式执行定时任务)
-
给定时任务设置执行时间的方式最主要的是通过cron表达式的方式设置,比如:0 47 15 * * ?
-
Cron表达式参数每一位的具体含义如下
- 秒(0~59) 例如0/5表示每5秒
- 分(0~59)
- 时(0~23)
- 日(0~31)的某天,需计算
- 月(0~11)
- 周几( 可填1-7 或 SUN/MON/TUE/WED/THU/FRI/SAT)
-
0 47 15 * * ? 表示每天的15点47分执行
-
接下来介绍SpringBoot中定时人任务的使用
-
新建一个SpringBoot项目
-
引入相关的依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.1.0</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.1.10</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>org.mybatis.generator</groupId> <artifactId>mybatis-generator-core</artifactId> <version>1.3.5</version> <scope>provided</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency>
-
在SpringBoot启动类上面添加如下的注解
import org.mybatis.spring.annotation.MapperScan; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.scheduling.annotation.EnableAsync; import org.springframework.scheduling.annotation.EnableScheduling; @SpringBootApplication @EnableScheduling //定时任务注解 @EnableAsync //异步任务注解 //Mybatis的Mapper接口扫描注解 @MapperScan(basePackages ="com.kangswx.springboottaskscheduled.mapper") public class SpringbootTaskscheduledApplication { public static void main(String[] args) { SpringApplication.run(SpringbootTaskscheduledApplication.class, args); } }
-
静态定时任务的使用,新建一个静态定时任务的测试类(需要在测试类上面添加@Component注解)
import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; import java.text.SimpleDateFormat; import java.util.Date; @Component public class ScheduledTest { private static final Logger logger = LoggerFactory.getLogger(ScheduledTest.class); private static final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); @Scheduled(cron = "0/5 * * * * ?") public void testTask(){ logger.info("执行静态定时任务时间: " + sdf.format(new Date() +", 线程名称"+Thread.currentThread().getName())); } }
-
启动SpringBoot项目,会发现打出如下的日志,表示每次执行的定时任务都是在同一个线程下执行的
seconds (JVM running for 7.144) 2019-08-12 09:51:20.004 INFO 15056 --- [ scheduling-1] c.k.s.taskscheduled.ScheduledTest : 执行静态定时任务时间: 2019-08-12 09:51:20, 线程名称scheduling-1 2019-08-12 09:51:25.001 INFO 15056 --- [ scheduling-1] c.k.s.taskscheduled.ScheduledTest : 执行静态定时任务时间: 2019-08-12 09:51:25, 线程名称scheduling-1 2019-08-12 09:51:30.001 INFO 15056 --- [ scheduling-1] c.k.s.taskscheduled.ScheduledTest : 执行静态定时任务时间: 2019-08-12 09:51:30, 线程名称scheduling-1 2019-08-12 09:51:35.001 INFO 15056 --- [ scheduling-1] c.k.s.taskscheduled.ScheduledTest : 执行静态定时任务时间: 2019-08-12 09:51:35, 线程名称scheduling-1
-
动态定时任务(执行的时间通过查询数据库获取),每次执行结束后,会查询数据库中下次执行的时间
-
在数据库中新建表t_cron并添加一条数据
DROP TABLE IF EXISTS `t_cron`; CREATE TABLE `t_cron` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT 'id', `cron` varchar(20) NOT NULL COMMENT 'cron表达式', PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4; INSERT INTO `t_cron` VALUES ('1', '0/5 * * * * ?');
-
添加表对应的实体类Cron
public class Cron { private Integer id; private String cron; public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getCron() { return cron; } public void setCron(String cron) { this.cron = cron == null ? null : cron.trim(); } }
-
添加Cron对应的Mapper接口CronMapper
import com.kangswx.springboottaskscheduled.entity.Cron; import org.apache.ibatis.annotations.Mapper; import org.springframework.stereotype.Repository; @Mapper @Repository public interface CronMapper { Cron selectByPrimaryKey(Integer id); }
-
添加CronMapper对应的Mybatis配置文件CronMapper.xml
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" > <mapper namespace="com.kangswx.springboottaskscheduled.mapper.CronMapper" > <resultMap id="BaseResultMap" type="com.kangswx.springboottaskscheduled.entity.Cron" > <id column="id" property="id" jdbcType="INTEGER" /> <result column="cron" property="cron" jdbcType="VARCHAR" /> </resultMap> <sql id="Base_Column_List" > id, cron </sql> <select id="selectByPrimaryKey" resultMap="BaseResultMap" parameterType="java.lang.Integer" > select <include refid="Base_Column_List" /> from t_cron where id = #{id,jdbcType=INTEGER} </select> </mapper>
-
编写动态定时任务测试类,在定时任务执行一段时间之后,将数据库中的cron表达式修改为0/10 * * * * ?
@Component public class DynamicScheduleTask implements SchedulingConfigurer { private static final Logger logger = LoggerFactory.getLogger(DynamicScheduleTask.class); @Autowired private CronMapper cronMapper; @Override public void configureTasks(ScheduledTaskRegistrar scheduledTaskRegistrar) { SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); scheduledTaskRegistrar.addTriggerTask( //1.添加任务内容(Runnable) () -> logger.info("执行动态定时任务"+ sdf.format(new Date())), //2.设置执行周期(Trigger) triggerContext -> { //2.1 从数据库获取执行周期 Cron cron = cronMapper.selectByPrimaryKey(1); String cron_str = cron.getCron(); //2.2 合法性校验. if(StringUtils.isEmpty(cron_str)){ logger.info("获取定时任务执行时间出错"); } //2.3 返回执行周期(Date) return new CronTrigger(cron_str).nextExecutionTime(triggerContext); } ); } }
-
启动SpringBoot项目,会发现如下的日志,表示动态定时任务每次执行的时候依然在同一个线程下,但是执行的时间会随着数据库中的数据的改变而改变
2019-08-12 10:05:30.002 INFO 304 --- [pool-1-thread-1] c.k.s.taskscheduled.DynamicScheduleTask : 执行动态定时任务2019-08-12 10:05:30, 线程名为: pool-1-thread-1 2019-08-12 10:05:35.001 INFO 304 --- [pool-1-thread-1] c.k.s.taskscheduled.DynamicScheduleTask : 执行动态定时任务2019-08-12 10:05:35, 线程名为: pool-1-thread-1 2019-08-12 10:05:40.001 INFO 304 --- [pool-1-thread-1] c.k.s.taskscheduled.DynamicScheduleTask : 执行动态定时任务2019-08-12 10:05:40, 线程名为: pool-1-thread-1 2019-08-12 10:05:50.000 INFO 304 --- [pool-1-thread-1] c.k.s.taskscheduled.DynamicScheduleTask : 执行动态定时任务2019-08-12 10:05:50, 线程名为: pool-1-thread-1 2019-08-12 10:06:00.002 INFO 304 --- [pool-1-thread-1] c.k.s.taskscheduled.DynamicScheduleTask : 执行动态定时任务2019-08-12 10:06:00, 线程名为: pool-1-thread-1
-
多线程定时任务,新增多线程定时任务测试类
import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.scheduling.annotation.Async; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; import java.text.SimpleDateFormat; import java.util.Date; @Component public class MultithreadScheduleTask { private static final Logger logger = LoggerFactory.getLogger(MultithreadScheduleTask.class); private static final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); @Scheduled(cron = "0/2 * * * * ?") @Async public void first() throws InterruptedException { logger.info("第一个定时任务执行时间: " + sdf.format(new Date()) + "线程名为: " + Thread.currentThread().getName()); Thread.sleep(1000*10L); } }
-
启动SpringBoot项目,会看到如下的日志,这表示多线程定时任务字每次执行的时候会新开一个线程
2019-08-12 10:12:18.015 INFO 16160 --- [ task-1] c.k.s.t.MultithreadScheduleTask : 第一个定时任务执行时间: 2019-08-12 10:12:18线程名为: task-1 2019-08-12 10:12:20.001 INFO 16160 --- [ task-2] c.k.s.t.MultithreadScheduleTask : 第一个定时任务执行时间: 2019-08-12 10:12:20线程名为: task-2 2019-08-12 10:12:22.001 INFO 16160 --- [ task-3] c.k.s.t.MultithreadScheduleTask : 第一个定时任务执行时间: 2019-08-12 10:12:22线程名为: task-3 2019-08-12 10:12:24.001 INFO 16160 --- [ task-4] c.k.s.t.MultithreadScheduleTask : 第一个定时任务执行时间: 2019-08-12 10:12:24线程名为: task-4 2019-08-12 10:12:26.004 INFO 16160 --- [ task-5] c.k.s.t.MultithreadScheduleTask : 第一个定时任务执行时间: 2019-08-12 10:12:26线程名为: task-5
-
异步任务:异步任务一般用于执行费核心任务,用于提高响应速度,比如向用户发通知邮件,短信等,在不影响主线程响应的情况下,异步执行相关的方法
-
在使用异步任务的时候要注意,在某些情况下,异步任务会失效
-
新增异步任务测试的Service接口
public interface AsyncService { void firstTack() throws InterruptedException; void secondTask() throws InterruptedException; void thirdTask() throws InterruptedException; }
-
新增异步任务测试的Service实现类
import com.kangswx.springboottaskscheduled.service.AsyncService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Service; import java.text.SimpleDateFormat; import java.util.Date; @Service public class AsyncServiceImpl implements AsyncService { private static final Logger logger = LoggerFactory.getLogger(AsyncServiceImpl.class); private static final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); @Async @Override public void firstTack() throws InterruptedException { logger.info("第一个异步任务开始执行: " + sdf.format(new Date()) + " 线程名为: " + Thread.currentThread().getName()); Thread.sleep(1000 * 3L); logger.info("第一个异步任务执行结束: " + sdf.format(new Date()) + " 线程名为: " + Thread.currentThread().getName()); } @Async @Override public void secondTask() throws InterruptedException { logger.info("第二个异步任务开始执行: " + sdf.format(new Date()) + " 线程名为: " + Thread.currentThread().getName()); Thread.sleep(1000 * 4L); logger.info("第二个异步任务执行结束: " + sdf.format(new Date()) + " 线程名为: " + Thread.currentThread().getName()); } @Async @Override public void thirdTask() throws InterruptedException { logger.info("第三个异步任务开始执行: " + sdf.format(new Date()) + " 线程名为: " + Thread.currentThread().getName()); Thread.sleep(1000 * 5L); logger.info("第三个异步任务执行结束: " + sdf.format(new Date()) + " 线程名为: " + Thread.currentThread().getName()); } }
-
新增异步任务测试的AsyncController
import com.kangswx.springboottaskscheduled.service.AsyncService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.text.SimpleDateFormat; import java.util.Date; @RestController @RequestMapping("/api/v1/asynctask") public class AsyncController { private static final Logger logger = LoggerFactory.getLogger(AsyncController.class); @Autowired private AsyncService asyncService; /** * 调用异步任务接口 * @throws InterruptedException */ @GetMapping("excute") public void excuteAsync() throws InterruptedException { SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); Date start = new Date(); logger.info("调用异步任务开始"+sdf.format(start) +", 线程名称为"+Thread.currentThread().getName()); asyncService.firstTack(); asyncService.secondTask(); asyncService.thirdTask(); Date end = new Date(); logger.info("调用异步任务结束"+sdf.format(end)); logger.info("执行总时间为: " + (end.getTime() - start.getTime()) +", 线程名称为"+Thread.currentThread().getName()); } }
-
在浏览器上面访问http://localhost:8081/api/v1/asynctask/excute ,会看到如下的日志,从日志中可以看出主线程早就执行结束,但异步任务仍然在新开的线程下继续执行
2019-08-12 10:42:32.359 INFO 15160 --- [nio-8081-exec-6] c.k.s.controller.AsyncController : 调用异步任务开始2019-08-12 10:42:32, 线程名称为http-nio-8081-exec-6 2019-08-12 10:42:32.360 INFO 15160 --- [ task-1] c.k.s.service.impl.AsyncServiceImpl : 第二个异步任务开始执行: 2019-08-12 10:42:32 线程名为: task-1 2019-08-12 10:42:32.360 INFO 15160 --- [nio-8081-exec-6] c.k.s.controller.AsyncController : 调用异步任务结束2019-08-12 10:42:32 2019-08-12 10:42:32.360 INFO 15160 --- [ task-2] c.k.s.service.impl.AsyncServiceImpl : 第三个异步任务开始执行: 2019-08-12 10:42:32 线程名为: task-2 2019-08-12 10:42:32.360 INFO 15160 --- [nio-8081-exec-6] c.k.s.controller.AsyncController : 执行总时间为: 1, 线程名称为http-nio-8081-exec-6 2019-08-12 10:42:32.360 INFO 15160 --- [ task-8] c.k.s.service.impl.AsyncServiceImpl : 第一个异步任务开始执行: 2019-08-12 10:42:32 线程名为: task-8 2019-08-12 10:42:35.361 INFO 15160 --- [ task-8] c.k.s.service.impl.AsyncServiceImpl : 第一个异步任务执行结束: 2019-08-12 10:42:35 线程名为: task-8 2019-08-12 10:42:36.361 INFO 15160 --- [ task-1] c.k.s.service.impl.AsyncServiceImpl : 第二个异步任务执行结束: 2019-08-12 10:42:36 线程名为: task-1 2019-08-12 10:42:37.361 INFO 15160 --- [ task-2] c.k.s.service.impl.AsyncServiceImpl : 第三个异步任务执行结束: 2019-08-12 10:42:37 线程名为: task-2
-
具体的代码实现可见 SpringBoot下实现定时任务和异步任务
SpringBoot下定时任务和异步任务的实现
最新推荐文章于 2024-04-26 21:45:17 发布