目录
1.利用@Scheduled来实现传统的定时任务
2.两者的区别
3.Spring中的SchedulingConfigurer来拓展定时任务的灵活性
1)UrTaskConfig
2)TaskMain
3)BaseTask
4)效果
(1)插入配置定时任务的sql语句
(2)写一个实现类去继承BaseTask
代码示例:
pom.xml
enums枚举类
EnumTask
EnumTaskRule
数据库Dao操作
UrConfigAllDao
UrTaskTestDao
BaseTask
TaskMain
UrTaskConfig
TaskTextJob
数据库
ur_config_all
ur_task_test
1.利用@Scheduled来实现传统的定时任务
实现定时任务的方式有多种,其中最广为人知的一种是利用@Scheduled,来在代码里面实现代码的定时任务。但是这样简单粗暴的方式可以实现定时任务的功能,但是在我们之后的开发过程中会频繁的使用到定时任务,如果拓展定时任务实现方式的灵活性与拓展性就成了问题。
直接使用@Scheduled的方式来实现定时任务有是有效,但是每增加一个定时任务,或者要修改定时任务的规则,比如修改cron表达式为固定频率或者固定次数,每次修改都要重启一下服务,为我们的开发带来了许多不方便的地方。因为想了一下,可以利用SPring中的SchedulingConfigurer来实现定时任务的拓展性
@Component
@Slf4j
public class MonthEndDataSync {
@Autowired
private CarBrandService carBrandService;
@Autowired
private CarModelsService carModelsService;
@Autowired
private CarSeriesService carSeriesService;
@Scheduled(cron = "0 0 3 L * *")
public void MonthEndDataSync() {
carBrandService.syncCarBrand4Network();
carModelsService.syncCarModels4Network();
carSeriesService.syncCarSeries4Network();
log.debug("[MonthEndDataSync start] 月底同步数据完成");
}
2.两者的区别
1.@Scheduled不支持动态修改定时周期,只能停止服务器,修改cron表达式,再启动服务器;SchedulingConfigurer可以动态修改
2.@Scheduled只能是单线程,而SchedulingConfigurer默认是单线程,可以通过添加线程池,实现多线程下定时任务的运行
3.Spring中的SchedulingConfigurer来拓展定时任务的灵活性
总体思路:
1)UrTaskConfig
UrTaskConfig这一类用来获取定时任务的一些配置,定时任务的规则,定时任务的状态,以及定时任务的类型(cron表达式、固定频率、固定间隔)
2)TaskMain
实现SchedulingConfigurer配置Spring的定时任务调度器,在TaskMain类中实现configureTasks方法,根据不同的定时任务规则来实现不同的定时任务。
3)BaseTask
是一个抽象类实现Runnable接口,实现了Runnable接口。这意味着BaseTask的实例可以被当作线程来运行,即可以被提交给线程池或者由Thread对象直接启动。
在BaseTask中定义了三个抽象方法before(),excute(),after(),方法需要由具体的任务子类来实现,可以创建多个不同的任务类,它们都继承自BaseTask,并实现自己的before(),excute(),after()方法。
4)效果
设置一个每5秒执行的定时任务
最终的效果,在我们实际开发的过程中想要实现一个定时任务就只需要做两步
(1)插入配置定时任务的sql语句
例如
INSERT INTO `task_db`.`ur_config_all` (`ID`, `TABLE_NAME`, `COLUMN_NAME`, `S_ID`, `VALUEE`) VALUES (1, 'TASK_WORK', 'TaskTextJob', 'CORN_RULE', '*/5 * * * * *');
INSERT INTO `task_db`.`ur_config_all` (`ID`, `TABLE_NAME`, `COLUMN_NAME`, `S_ID`, `VALUEE`) VALUES (2, 'TASK_WORK', 'TaskTextJob', 'CORN_STATUS', '1');
INSERT INTO `task_db`.`ur_config_all` (`ID`, `TABLE_NAME`, `COLUMN_NAME`, `S_ID`, `VALUEE`) VALUES (3, 'TASK_WORK', 'TaskTextJob', 'CORN_TYPE', '1');
(2)写一个实现类去继承BaseTask
这样就会开启一个定时任务例如:TaskTextJob
代码示例:
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>untitled</artifactId>
<version>1.0-SNAPSHOT</version><build><plugins><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-compiler-plugin</artifactId><configuration><source>16</source><target>16</target></configuration></plugin></plugins></build>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<spring-boot-starter.version>3.1.1</spring-boot-starter.version>
<mybatis-plus-boot-starter.version>3.5.3.1</mybatis-plus-boot-starter.version>
<fastjson.version>2.0.32</fastjson.version>
<mysql-connector-j.version>8.0.33</mysql-connector-j.version>
<disruptor.version>3.4.2</disruptor.version>
<spring-boot-starter-web.version>3.1.1</spring-boot-starter-web.version>
</properties>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.1.1</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<dependencies>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>${mybatis-plus-boot-starter.version}</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>${fastjson.version}</version>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<version>${mysql-connector-j.version}</version>
</dependency>
<dependency>
<groupId>com.lmax</groupId>
<artifactId>disruptor</artifactId>
<version>${disruptor.version}</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.28</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<version>3.1.1</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>${spring-boot-starter-web.version}</version>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.18</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-log4j2</artifactId>
<version>3.1.1</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
<version>3.1.1</version>
</dependency>
</dependencies>
</project>
enums枚举类
EnumTask
public enum EnumTask {
corn_task("TASK_WORK", "类名", "CORN_RULE", "定时任务"),
corn_task_status("TASK_WORK", "类名", "CORN_STATUS", "定时任务开关"),
corn_task_type("TASK_WORK", "类名", "CORN_TYPE", "定时任务类型")
;
private final String tableName;
private final String columnName;
private final String sId;
private final String valuee;
EnumTask(String tableName, String columnName, String sId, String valuee) {
this.columnName = columnName;
this.sId = sId;
this.valuee = valuee;
this.tableName = tableName;
}
public String getTableName() {
return tableName;
}
public String getColumnName() {
return columnName;
}
public String getsId() {
return sId;
}
public String getValuee() {
return valuee;
}
}
EnumTaskRule
public enum EnumTaskRule {
ONE("1","corn表达式"),
TWO("2","固定频率"),
THREE("3","固定间隔"),
STATUS1("1","开启"),
STATUS0("0","禁用")
;
private final String code;
private final String msg;
EnumTaskRule(String code, String msg) {
this.code = code;
this.msg = msg;
}
public String getCode() {
return code;
}
public String getMsg() {
return msg;
}
}
数据库Dao操作
UrConfigAllDao
public interface UrConfigAllDao extends BaseMapper<UrConfigAll> {
@Select("""
select TABLE_NAME, COLUMN_NAME, S_ID, VALUEE,ID
from ur_config_all
where TABLE_NAME = #{tableName}
and COLUMN_NAME = #{columnName}
and S_ID = #{sId}
""")
UrConfigAll queryUrConfigAll(String tableName, String columnName, String sId);
}
UrTaskTestDao
public interface UrTaskTestDao extends BaseMapper<UrTaskTest> {
@Select("""
select ID, TASK_NAME, PROCESS_ID, CREATE_TIME, UPDATE_TIME,VERSION,STATUS
,CLASS_NAME,METHON,CRON,CRON_TYPE,NEXT_EXEC_TIME
from ur_task_test
where TASK_NAME = #{taskName}
""")
UrTaskTest selectByTaskName(String taskName);
@Update("""
update ur_task_test set
PROCESS_ID = #{processId},
UPDATE_TIME = #{updateTime},
VERSION = VERSION+1
where TASK_NAME = #{taskName} and VERSION=#{version}
""")
int updateProcessorIDandVersionByTaskName(UrTaskTest urTaskTest);
@Delete("""
DELETE FROM ur_task_test;
""")
int deleteAll();
@Insert("""
insert into ur_task_test (TASK_NAME, PROCESS_ID,
CREATE_TIME, UPDATE_TIME,VERSION,STATUS,CLASS_NAME,METHON,CRON,CRON_TYPE,NEXT_EXEC_TIME
)
values (#{taskName}, #{processId},
#{createTime}, #{updateTime},
#{version}, #{status}, #{className}
, #{methon} , #{cron}, #{cronType},#{nextExecTime}
)
""")
int insert2(UrTaskTest urTaskTest);
}
BaseTask
@Component
public abstract class BaseTask implements Runnable{
private final static Logger log = LoggerFactory.getLogger(BaseTask.class);
@Autowired
private UrConfigAllService urConfigAllService;
@Autowired
private UrTaskTestService taskTestService ;
private String taskName;
public String getTaskName() {
return taskName;
}
public void setTaskName(String taskName) {
this.taskName = taskName;
}
//获取数据库中关于定时任务的配置
UrTaskConfig getUrTaskConfig(){
UrTaskConfig urTaskConfig=new UrTaskConfig();
return urTaskConfig.getUrTaskConfig(taskName,urConfigAllService);
}
//执行前
public abstract void before();
//执行
public abstract void execute();
//执行后
public abstract void after();
@Override
public void run(){
//生成任务进程uuid
String uuid = UUID.randomUUID().toString();
//根据taskName在ur_task_test表中查询执行进程
UrTaskTest urTaskTest = taskTestService.selectByTaskName(taskName);
//获取任务进程的status
if (urTaskTest==null){
System.out.println("任务 :" + taskName + " 进程不存在");
return;
}
//判断status,如果是禁用就直接return
if (StringUtils.equals(String.valueOf(urTaskTest.getStatus()),EnumTaskRule.STATUS0.getCode()))return;
//uuid设置为进程id
urTaskTest.setProcessId(uuid);
//版本也是
urTaskTest.setVersion(urTaskTest.getVersion());
//更新ur_task_test中的字段
System.out.println("更新ur_task_test中的字段"+urTaskTest);
int i=taskTestService.updateProcessIDandVersionByTaskName(urTaskTest);
//更新返回为0,则该任务已经在其他节点执行
if (i==0){
System.out.println("任务 :" + taskName + " 已经在其他节点执行");
return;
}
//更新成功则任务进程开始
System.out.println("任务 :" + uuid + " 执行开始");
try {
//前置处理
before();
execute();
//后置处理
after();
}catch (Exception e){
throw new RuntimeException(e);
}finally {
urTaskTest.setTaskName(taskName);
urTaskTest.setProcessId(null);
urTaskTest.setUpdateTime(new Date());
System.out.println("任务进程查看urTaskTest的值 :" + urTaskTest + " 运行结束");
taskTestService.updateProcessIDandVersionByTaskName(urTaskTest);
System.out.println("任务进程 :" + uuid + " 运行结束");
}
}
}
TaskMain
@Component
public class TaskMain implements SchedulingConfigurer {
@Autowired
private ApplicationContext applicationContext;
@Autowired
private UrTaskTestService urTaskTestService;
private final static Logger log = LoggerFactory.getLogger(TaskMain.class);
//注册定时任务
@Override
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
//获取所有的定时任务,它们都是BaseTask类的实例或其子类
Map<String, BaseTask> map = applicationContext.getBeansOfType(BaseTask.class);
System.out.println("获取所有的定时任务"+map);
//开始线程数
taskRegistrar.setScheduler(Executors.newScheduledThreadPool(10));
//清除所有的进程
urTaskTestService.deleteAll();
System.out.println("获取所有的定时任务111"+map);
//遍历map(存储所有BaseTask bean的Map),针对每个bean执行相应的操作
for (String key:map.keySet()){
//为每个定时任务创建一个UrTaskTest对象,并设置其属性,如创建时间、任务名称、版本等
System.out.println("为每个定时任务创建一个UrTaskTest对象"+key);
UrTaskTest urTaskTest=new UrTaskTest()
.setCreateTime(new Date())
.setTaskName(key)
.setVersion(0L);
//从Map中获取当前key对应的BaseTask对象,并设置其任务名称taskNmae
BaseTask baseTask = map.get(key);
baseTask.setTaskName(key);
//从BaseTask对象中获取TaskConfigAll对象,并获取其配置信息
UrTaskConfig urTaskConfig = baseTask.getUrTaskConfig();
String cornStatus = urTaskConfig.getCornStatus();
String taskName = urTaskConfig.getTaskName();
//如果定时任务的cronStatus为TaskEnum.status0.getCode(),表示任务已停止,则跳过当前循环。
if (StringUtils.equals(cornStatus, EnumTaskRule.STATUS0.getCode())){
System.out.println("{} 定时任务配置状态为禁用"+urTaskConfig);
continue;
}
//设置urTaskControl的状态为TaskEnum.status1.getCode()
urTaskTest.setStatus(EnumTaskRule.STATUS1.getCode());
//如果cron(定时规则)为空,则记录日志并跳过当前循环
String corn = urTaskConfig.getCorn();
if (StringUtils.isEmpty(corn)){
System.out.println("{} 定时任务没配置定时的规则规则"+taskName);
continue;
}
//设置urTaskControlAll的其他属性,如cronType、cron、methon和className
urTaskTest.setCronType(urTaskConfig.getCornModern());
urTaskTest.setCron(corn);
urTaskTest.setMethon("execute()");
urTaskTest.setClassName(baseTask.getClass().getName());
//根据urTaskConfig.getCornModern()的值判断注册何种类型的定时任务
try {
//如果urTaskConfig.getCornModern()的值为TaskEnum.ONE.getCode(),则注册一个基于CronTrigger的触发任务
if (EnumTaskRule.ONE.getCode().equals(urTaskConfig.getCornModern())){
taskRegistrar.addTriggerTask(baseTask,sheduledConfig->{
Date date = new CronTrigger(corn).nextExecutionTime(sheduledConfig);
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String formattedDate = sdf.format(date);
// 打印格式化后的日期
System.out.println(taskName + "执行定时任务的时间:" + formattedDate);
return date.toInstant();
});
//如果urTaskConfig.getCornModern()的值为TaskEnum.TWO.getCode(),则注册一个固定频率的定时任务
}else if (EnumTaskRule.TWO.getCode().equals(urTaskConfig.getCornModern())){
int parseInt = Integer.parseInt(corn);
taskRegistrar.addFixedRateTask(baseTask,parseInt);
System.out.println("已添加固定频率任务,频率为:" + parseInt);
//如果urTaskConfig.getCornModern()的值为TaskEnum.THREE.getCode(),则注册一个固定次数的定时任务
}else if (EnumTaskRule.THREE.getCode().equals(urTaskConfig.getCornModern())){
int fixedDelay = Integer.parseInt(corn);
taskRegistrar.addFixedDelayTask(
baseTask, fixedDelay
);
}else {
//最后判断是否有定时任务的规则
System.out.println("{} 定时任务未找到动态方法"+taskName);
continue;
}
//插入urTaskTest一条数据到定时任务的进程表中
urTaskTestService.insert2(urTaskTest);
}catch (Exception e){
throw new RuntimeException(e);
}
}
}
}
UrTaskConfig
@Data
@Accessors(chain = true)
public class UrTaskConfig {
private final static Logger log = LoggerFactory.getLogger(UrTaskConfig.class);
//定时任务类名
private String taskName;
//定时任务规则
private String corn;
//定时任务状态
private String cornStatus;
//定时任务的类型
private String cornModern;
public UrTaskConfig getUrTaskConfig(String taskName, UrConfigAllService urConfigAllService){
//获取定时任务规则
UrConfigAll urConfigAll = urConfigAllService.getUrConfigAll(EnumTask.corn_task.getTableName(), taskName, EnumTask.corn_task.getsId());
String rule = Optional.ofNullable(urConfigAll).map(UrConfigAll::getValuee).orElse(null);
//获取定时任务状态
UrConfigAll urConfigAll1 = urConfigAllService.getUrConfigAll(EnumTask.corn_task_status.getTableName(), taskName, EnumTask.corn_task_type.getsId());
String ruleState = Optional.ofNullable(urConfigAll1).map(UrConfigAll::getValuee).orElse(null);
//定时任务任务类型
UrConfigAll urConfigAll2 = urConfigAllService.getUrConfigAll( EnumTask.corn_task_type.getTableName(), taskName,EnumTask.corn_task_type.getsId());
String ruleType = Optional.ofNullable(urConfigAll2).map(UrConfigAll::getValuee).orElse(EnumTaskRule.ONE.getCode());
UrTaskConfig urTaskConfig=new UrTaskConfig()
.setTaskName(taskName)
.setCorn(rule)
.setCornStatus(ruleState)
.setCornModern(ruleType);
System.out.println("定时任务配置参数"+JSON.toJSONString(urTaskConfig) );
return urTaskConfig;
}
}
TaskTextJob
@Component
public class TaskTextJob extends BaseTask {
@Override
public void before() {
}
@Override
public void execute() {
// 获取当前时间
LocalDateTime currentTime = LocalDateTime.now();
// 定义格式化模板,精确到秒
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
// 格式化当前时间
String formattedTime = currentTime.format(formatter);
// 输出格式化后的时间
System.out.println("执行定时任务测试: " + formattedTime);
}
@Override
public void after() {
}
}
数据库
ur_config_all
CREATE TABLE `ur_config_all` (
`ID` int NOT NULL AUTO_INCREMENT COMMENT '主键',
`TABLE_NAME` varchar(255) NOT NULL,
`COLUMN_NAME` varchar(255) NOT NULL,
`S_ID` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL,
`VALUEE` varchar(255) NOT NULL,
PRIMARY KEY (`ID`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
ur_task_test
CREATE TABLE `ur_task_test` (
`ID` int NOT NULL AUTO_INCREMENT COMMENT '任务ID',
`TASK_NAME` varchar(255) NOT NULL COMMENT '任务名',
`PROCESS_ID` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '进程ID',
`CREATE_TIME` datetime NOT NULL COMMENT '创建时间',
`UPDATE_TIME` datetime DEFAULT NULL COMMENT '更新时间',
`VERSION` bigint NOT NULL COMMENT '版本',
`STATUS` varchar(50) NOT NULL COMMENT '状态',
`CLASS_NAME` varchar(255) NOT NULL COMMENT '类路径',
`METHON` varchar(255) NOT NULL COMMENT '方法',
`CRON` varchar(255) DEFAULT NULL COMMENT '定时规则 有2种值,表达式和毫秒',
`CRON_TYPE` varchar(50) DEFAULT NULL COMMENT '定时任务类型 1:cron表达式,2:固定频率,3:固定间隔',
`NEXT_EXEC_TIME` datetime DEFAULT NULL COMMENT '任务名',
PRIMARY KEY (`ID`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=20 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;