定时任务是指预先设置好时间周期,程序按照设定的时间周期、固定频率或特定的时间点来执行某些操作。定时任务常见于后端项目,常应用于在数据跑批、定时任务执行和邮件通知类任务中。现阶段常见的定时任务有两种,暂且分为静态定时任务和动态定时任务。
- 静态:是指在指定时间或指定任务场景的情况下执行。
- 动态:执行条件方便修改,常用于业务逻辑和外部配置的灵活变动。
定时任务的常见应用场景:数据备份、日志清理、邮件通知、定时刷新缓存。
文章目录
一、静态定时:SpringBoot 使用 @Scheduled 示例
这部分是使用 cron
表达式完成定时任务。表达式中设定时间可以设定在 配置文件
中,使用 @value
注解进行获取。也可以存储到数据库中,通过固定接口对设定时间进行增删改查操作,可以达到动态使用定时的目的。
在Spring Boot中创建定时任务,通常使用@Scheduled
注解,这是Spring框架提供的一个功能,允许你按照固定的频率(如每天、每小时、每分钟等)执行某个方法。
网上有很多 icon表达式生成器
,可以直接搜索 定时任务icon表达式生成器
,这里就不放链接了。
1、在入口类开启定时任务支持
在 SpringBoot
应用的启动类上,添加 @EnableScheduling
注解来开启定时任务的支持。
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableScheduling;
@SpringBootApplication
@EnableScheduling
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
2、创建定时任务
创建一个包含定时任务方法的类,并在该方法上使用@Scheduled
注解来指定任务的执行频率。
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
@Component
public class ScheduledTasks {
// 每5秒执行一次
@Scheduled(fixedRate = 5000)
public void doTaskEveryFiveSeconds() {
System.out.println("任务每5秒执行一次:" + new Date());
}
// 每天的特定时间执行一次
@Scheduled(cron = "0 0 12 * * ?") // 每天中午12点执行
public void doTaskEveryDayAtNoon() {
System.out.println("任务每天中午12点执行一次:" + new Date());
}
}
fixedRate
属性用于指定任务执行的固定频率(以毫秒为单位)。cron
属性则允许你使用CRON表达式来指定任务的执行时间。
这两个属性在实际的项目开发中都会做到配置文件中,便于修改。
3、配置定时任务线程池(可选)
默认情况下,Spring Boot使用单线程来执行所有的定时任务。如果需要并发执行多个定时任务,或者某个任务执行时间较长不希望阻塞其他任务,可以自定义定时任务的线程池。
在 application.properties
中设置相关配置
spring.task.scheduling.pool.size=5 # 线程池大小,可以用 ${} 配置
spring.task.scheduling.thread-name-prefix=task-scheduler- # 线程名前缀
也可以在配置类中自定义 TaskScheduler
Bean
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
@Configuration
public class SchedulerConfig {
@Bean
public ThreadPoolTaskScheduler threadPoolTaskScheduler() {
ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
scheduler.setPoolSize(5);
scheduler.setThreadNamePrefix("task-scheduler-");
return scheduler;
}
}
启动SpringBoot应用,定时任务就会按照指定的频率执行。
请注意,
@Scheduled
注解的方法不应该有任何参数,并且返回类型应该是void
。此外,@Scheduled
注解的方法可以定义在配置类中,但最好将其定义在一个独立的Bean中,以便于测试和管理。
二、动态定时:数据库存放 cron 表达式,使用接口进行动态修改
本部分使用 SpringBoot
集成 MyBatis
示例来解释动态定时任务的使用,通过使用接口调用进行数据库存储,将定时任务的时间设定换成数据库获取。
虽然 @Scheduled
注解本身不支持直接从数据库读取 cron
表达式,但可以通过一些设计来间接实现。
关键步骤在下面第三步 TaskSchedule
中。
以下是个人写的一个从数据库获取 cron 表达式来进行实时更新的 demo,有的地方并不完善。
文中的 MyBatis
集成如果有不明白可以看 SpringBoot 项目整合 MyBatisPlus 框架,附带测试示例
1、TaskController
在这个地方,先调用 http://localhost:8080/api/update
进行修改 cron
表达式。
然后使用 http://localhost:8080/api/update-cron
接口,实现提醒定时任务更换 cron
表达式,重新运行。
这样就实现了,不重启任务就可以实现定时任务的动态变化。
package com.wen.controller;
import com.wen.TaskSchedule;
import com.wen.dto.CronList;
import com.wen.service.TaskService;
import org.apache.ibatis.annotations.Param;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/api")
public class TaskController {
@Autowired
private TaskService taskService;
@GetMapping("select")
public CronList selectById(@Param("id") int id) {
return taskService.selectById(id);
}
@PostMapping("add")
public void addCron(@RequestBody CronList cronList) {
taskService.addCron(cronList);
}
@GetMapping("update")
public void updateCron(@Param("id") int id, @Param("cron") String cron) {
taskService.updateCron(id, cron);
}
@GetMapping("delete")
public void deleteCron(@Param("id") int id) {
taskService.removeCron(id);
}
@Autowired
private TaskSchedule taskSchedule;
@GetMapping("update-cron")
public String updateScheduledTask() {
taskSchedule.startOrUpdateScheduledTask();
return "success";
}
}
2、SchedulerConfig
package com.wen.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
@Configuration
public class SchedulerConfig {
@Bean
public ThreadPoolTaskScheduler taskScheduler() {
ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
scheduler.setPoolSize(10);
scheduler.setThreadNamePrefix("wen-");
scheduler.setDaemon(true);
return scheduler;
}
}
3、TaskSchedule
在Java中,直接使用 @Scheduled
注解并不支持直接通过监听器动态修改 cron
表达式,因为 @Scheduled
注解是在 Spring
容器启动时解析的,并且 cron
表达式是在那时被解析和固定下来的。
可以通过一些方法间接实现这个需求,比如使用 TaskScheduler
接口动态地添加、更新或取消定时任务。但是这种方式也会有一定限制。
package com.wen;
import com.wen.service.TaskService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.scheduling.support.CronTrigger;
import org.springframework.stereotype.Service;
import java.util.concurrent.ScheduledFuture;
@Service
public class TaskSchedule {
// 这里需要定义 SchedulerConfig
@Autowired
private TaskScheduler taskScheduler;
private ScheduledFuture<?> scheduledFuture;
@Autowired
private TaskService taskService;
public String getCronByiD() {
return taskService.selectById(1).getCron();
}
public void startOrUpdateScheduledTask() {
Runnable task = () -> System.out.println("Task is running at " + System.currentTimeMillis());
String cronExpression = getCronByiD();
// 如果有任务运行,先取消
if( scheduledFuture != null) {
scheduledFuture.cancel(false);
}
// 使用新的cron表达式
scheduledFuture = taskScheduler.schedule(task, new CronTrigger(cronExpression));
}
}
4、CronList
package com.wen.dto;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class CronList {
private int id;
private String cron;
}
5、CronListMapper
package com.wen.mapper;
import com.wen.dto.CronList;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface CronListMapper {
CronList selectById(int id);
void addCron(CronList cronList);
void updateCron(int id, String cron);
void removeCron(int id);
}
6、TaskServiceImpl
package com.wen.service.impl;
import com.wen.dto.CronList;
import com.wen.mapper.CronListMapper;
import com.wen.service.TaskService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class TaskServiceImpl implements TaskService {
@Autowired
private CronListMapper cronListMapper;
@Override
public CronList selectById(int id) {
return cronListMapper.selectById(id);
}
@Override
public void addCron(CronList cronList) {
cronListMapper.addCron(cronList);
}
@Override
public void updateCron(int id, String cron) {
cronListMapper.updateCron(id, cron);
}
@Override
public void removeCron(int id) {
cronListMapper.removeCron(id);
}
}
7、TaskService
package com.wen.service;
import com.wen.dto.CronList;
public interface TaskService {
CronList selectById(int id);
void addCron(CronList cronList);
void updateCron(int id, String cron);
void removeCron(int id);
}
8、CronListMapper.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.wen.mapper.CronListMapper">
<select id="selectById" resultType="com.wen.dto.CronList">
SELECT id, cron
FROM cron_list
WHERE id = #{id}
</select>
<insert id="addCron" parameterType="com.wen.dto.CronList">
INSERT INTO cron_list
VALUES (#{id}, #{cron})
</insert>
<update id="updateCron" parameterType="com.wen.dto.CronList">
UPDATE cron_list
SET cron = #{cron}
WHERE id = #{id}
</update>
<delete id="removeCron" parameterType="com.wen.dto.CronList">
DELETE FROM cron_list
WHERE id = #{id}
</delete>
</mapper>
9、TaskTest
package wen;
import com.wen.TestApplication;
import com.wen.dto.CronList;
import com.wen.service.TaskService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
@RunWith(SpringRunner.class)
@SpringBootTest(classes = TestApplication.class)
public class TaskTest {
@Autowired
private TaskService taskService;
@Test
public void addCron() {
CronList cronList = new CronList();
cronList.setId(1);
cronList.setCron("0 0 2 * * ?");
taskService.addCron(cronList);
}
@Test
public void selectById(){
CronList cronList = taskService.selectById(1);
System.out.println(cronList);
}
}
这里的测试只是表明数据库修改,具体上方通过接口进行调用。