目录
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 {@Autowiredprivate CarBrandService carBrandService;@Autowiredprivate CarModelsService carModelsService;@Autowiredprivate 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,IDfrom ur_config_allwhere 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_TIMEfrom ur_task_testwhere TASK_NAME = #{taskName}""")UrTaskTest selectByTaskName(String taskName);@Update("""update ur_task_test setPROCESS_ID = #{processId},UPDATE_TIME = #{updateTime},VERSION = VERSION+1where 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);@Autowiredprivate UrConfigAllService urConfigAllService;@Autowiredprivate 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();@Overridepublic void run(){//生成任务进程uuidString uuid = UUID.randomUUID().toString();//根据taskName在ur_task_test表中查询执行进程UrTaskTest urTaskTest = taskTestService.selectByTaskName(taskName);//获取任务进程的statusif (urTaskTest==null){System.out.println("任务 :" + taskName + " 进程不存在");return;}//判断status,如果是禁用就直接returnif (StringUtils.equals(String.valueOf(urTaskTest.getStatus()),EnumTaskRule.STATUS0.getCode()))return;//uuid设置为进程idurTaskTest.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 {@Autowiredprivate ApplicationContext applicationContext;@Autowiredprivate UrTaskTestService urTaskTestService;private final static Logger log = LoggerFactory.getLogger(TaskMain.class);//注册定时任务@Overridepublic 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对象,并设置其任务名称taskNmaeBaseTask 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和classNameurTaskTest.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 {@Overridepublic void before() {}@Overridepublic 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);}@Overridepublic 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;