思路:首先是能够获取任务具体执行的类名,然后将任务类名以及cron表达式新增到数据库保存起来,有一个监听类,根据任务类名和cron表达式用来运行该定时任务。
具体来说:
- 项目一启动,就要去查询定时任务表,并把数据库运行状态的任务加到监听表,监听执行,具体在 2.3,定时任务监听器;
- 然后就是2.4是实现具体的操作,比如:增删改查定时任务、直接执行一次定时任务。
1、创建表
用来保存定时任务信息。
CREATE TABLE `dev_job` (
`ID` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
`NAME` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '名称',
`CODE` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '编码',
`CATEGORY` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '分类',
`ACTION_CLASS` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '任务类名',
`CRON_EXPRESSION` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT 'cron表达式',
`JOB_STATUS` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '任务状态',
`SORT_CODE` int(11) DEFAULT NULL COMMENT '排序码',
`EXT_JSON` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci COMMENT '扩展信息',
`DELETE_FLAG` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '删除标志',
`CREATE_TIME` datetime DEFAULT NULL COMMENT '创建时间',
`CREATE_USER` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '创建用户',
`UPDATE_TIME` datetime DEFAULT NULL COMMENT '修改时间',
`UPDATE_USER` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '修改用户',
PRIMARY KEY (`ID`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci ROW_FORMAT=DYNAMIC COMMENT='定时任务';
INSERT INTO `mybatisplus`.`dev_job`(`ID`, `NAME`, `CODE`, `CATEGORY`, `ACTION_CLASS`, `CRON_EXPRESSION`, `JOB_STATUS`, `SORT_CODE`, `EXT_JSON`, `DELETE_FLAG`, `CREATE_TIME`, `CREATE_USER`, `UPDATE_TIME`, `UPDATE_USER`) VALUES ('1648226556708933634', '定时任务1', NULL, NULL, '', '*/5 * * * * ?', 'RUNNING', 0, NULL, NULL, '2023-04-18 15:24:49', NULL, NULL, NULL);
2、实现代码
2.1、定时器执行者接口
定时器执行者接口,定时器都要实现本接口。
/**
* 定时器执行者,定时器都要实现本接口,并需要把实现类加入到spring容器中
**/
public interface CommonTimerTaskRunner {
/**
* 任务执行的具体内容
**/
void action();
}
2.2、测试-实例
具体执行定时任务的一个类,实现CommonTimerTaskRunner,为了获取该类的类名。
/**
* 定时器的一个示例
**/
@Slf4j
@Component
public class DevJobTimerTaskRunner implements CommonTimerTaskRunner {
private int n = 1;
@Override
public void action() {
log.info("我是一个定时任务,正在在被执行第" + n + "次");
n = n + 1;
}
}
2.3、定时任务监听器
查询数据库中的定时任务,放进去等待时间执行。
/**
* 定时任务监听器,系统启动时将定时任务启动
*/
@Slf4j
@Configuration
public class DevJobListener implements ApplicationListener<ApplicationStartedEvent>, Ordered {
@SuppressWarnings("ALL")
@Override
public void onApplicationEvent( ApplicationStartedEvent applicationStartedEvent) {
SpringUtil.getBean(DevJobService.class).list(new LambdaQueryWrapper<DevJob>().eq(DevJob::getJobStatus, DevJobStatusEnum.RUNNING.getValue()).orderByAsc(DevJob::getSortCode))
.forEach(devJob -> CronUtil.schedule(devJob.getId(), devJob.getCronExpression(), () -> {
try {
// 运行定时任务
((CommonTimerTaskRunner) SpringUtil.getBean(Class.forName(devJob.getActionClass()))).action();
} catch (ClassNotFoundException e) {
System.out.println("定时任务找不到对应的类,名称为:" + devJob.getActionClass());
}
}));
// 设置秒级别的启用
CronUtil.setMatchSecond(true);
// 启动定时器执行器
CronUtil.restart();
}
@Override
public int getOrder() {
return LOWEST_PRECEDENCE;
}
}
2.4、controller类
@Api(tags = "定时任务")
@RestController
public class DevJobController {
@Autowired
DevJobService devJobService;
/**
* 添加定时任务
*
*/
@ApiOperation("添加定时任务")
@PostMapping("/dev/job/add")
public Boolean add(@RequestBody @Valid DevJob devJob) {
return devJobService.add(devJob);
}
/**
* 获取定时任务列表
*/
@ApiOperation("获取定时任务列表")
@GetMapping("/dev/job/list")
public List<DevJob> list(DevJob devJob) {
return devJobService.getlist(devJob);
}
/**
* 获取定时任务类
*
*/
@ApiOperation("获取定时任务类")
@GetMapping("/dev/job/getActionClass")
public List<String> getActionClass() {
return devJobService.getActionClass();
}
}
2.4、service类
public interface DevJobService extends IService<DevJob> {
/**
* 获取定时任务列表
*/
List<DevJob> getlist(DevJob devJob);
/**
* 添加定时任务
*/
Boolean add(DevJob devJob);
/**
* 编辑定时任务
*/
void edit(DevJob devJob);
/**
* 删除定时任务
*
*/
void delete(List<DevJob> devJobList);
/**
* 停止定时任务
**/
void stopJob(DevJob devJob);
/**
* 运行定时任务
**/
void runJob(DevJob devJob);
/**
* 立即运行定时任务
*
**/
void runJobNow(DevJob devJob);
/**
* 获取定时任务类名
*
**/
List<String> getActionClass();
}
2.4、serviceImpl类
@Service
public class DevJobServiceImpl extends ServiceImpl<DevJobMapper, DevJob>
implements DevJobService{
@Override
public Boolean add(DevJob devJob) {
checkParam(devJob);
devJob.setCode(RandomUtil.randomString(10));
devJob.setJobStatus(DevJobStatusEnum.STOPPED.getValue());
return this.save(devJob);
}
@Override
public void edit(DevJob devJobEditParam) {
DevJob devJob = this.getById(devJobEditParam.getId());
if(devJob.getJobStatus().equals(DevJobStatusEnum.RUNNING.getValue())) {
throw new RuntimeException("运行中的定时任务不可编辑,id值为:");
}
checkParam(devJobEditParam);
BeanUtil.copyProperties(devJobEditParam, devJob);
this.updateById(devJob);
}
@Transactional(rollbackFor = Exception.class)
@Override
public void delete(List<DevJob> devJobIdParamList) {
List<String> devJobIdList = CollStreamUtil.toList(devJobIdParamList, DevJob::getId);
if(ObjectUtil.isNotEmpty(devJobIdList)) {
// 将运行中的停止
devJobIdList.forEach(CronUtil::remove);
// 执行删除
this.removeByIds(devJobIdList);
}
}
@Transactional(rollbackFor = Exception.class)
@Override
public void stopJob(DevJob devJobIdParam) {
DevJob devJob = this.getById(devJobIdParam.getId());
if(devJob.getJobStatus().equals(DevJobStatusEnum.STOPPED.getValue())) {
throw new RuntimeException("定时任务已经处于停止状态,id值为:");
}
// 将运行中的定时任务停止
CronUtil.remove(devJob.getId());
this.update(new LambdaUpdateWrapper<DevJob>().eq(DevJob::getId, devJobIdParam.getId())
.set(DevJob::getJobStatus, DevJobStatusEnum.STOPPED.getValue()));
}
@Transactional(rollbackFor = Exception.class)
@Override
public void runJob(DevJob devJobIdParam) {
DevJob devJob = this.getById(devJobIdParam.getId());
if(devJob.getJobStatus().equals(DevJobStatusEnum.RUNNING.getValue())) {
throw new RuntimeException("定时任务已经处于运行状态,id值为");
}
CronUtil.schedule(devJob.getId(), devJob.getCronExpression(), () -> {
try {
// 运行定时任务
((CommonTimerTaskRunner) SpringUtil.getBean(Class.forName(devJob.getActionClass()))).action();
} catch (ClassNotFoundException e) {
throw new RuntimeException("定时任务找不到对应的类,名称");
}
});
this.update(new LambdaUpdateWrapper<DevJob>().eq(DevJob::getId, devJobIdParam.getId())
.set(DevJob::getJobStatus, DevJobStatusEnum.RUNNING.getValue()));
}
@Override
public void runJobNow(DevJob devJobIdParam) {
DevJob devJob = this.getById(devJobIdParam.getId());
if(devJob.getJobStatus().equals(DevJobStatusEnum.STOPPED.getValue())) {
// 如果是停止的,则先开启运行
this.runJob(devJobIdParam);
}
try {
// 直接运行一次
((CommonTimerTaskRunner) SpringUtil.getBean(Class.forName(devJob.getActionClass()))).action();
} catch (ClassNotFoundException e) {
throw new RuntimeException("定时任务找不到对应的类,名称为");
}
}
private void checkParam(DevJob devJobEditParam) {
if(!CronExpression.isValidExpression(devJobEditParam.getCronExpression())) {
//throw new RuntimeException(("cron表达式:{}格式不正确", ()));
throw new RuntimeException(("cron表达式格式不正确"));
}
try {
Class<?> actionClass = Class.forName(devJobEditParam.getActionClass());
if(!CommonTimerTaskRunner.class.isAssignableFrom(actionClass)) {
List<String> actionClassArr = StrUtil.split(devJobEditParam.getActionClass(), StrUtil.DOT);
//throw new CommonException("定时任务对应的类:{}不符合要求", (() - 1));
throw new RuntimeException("定时任务对应的类不符合要求");
}
} catch (ClassNotFoundException e) {
//throw new CommonException("定时任务找不到对应的类,名称为:{}", ());
throw new RuntimeException("定时任务找不到对应的类名称");
}
boolean hasSameJob = this.count(new LambdaQueryWrapper<DevJob>()
.eq(DevJob::getActionClass, devJobEditParam.getActionClass())
.eq(DevJob::getCronExpression, devJobEditParam.getCronExpression())
.ne(DevJob::getId, devJobEditParam.getId())) > 0;
if (hasSameJob) {
//throw new CommonException("存在重复的定时任务,名称为:{}", ());
throw new RuntimeException("存在重复的定时任务");
}
}
@Override
public List<DevJob> getlist(DevJob devJob) {
return this.list();
}
@Override
public List<String> getActionClass() {
Map<String, CommonTimerTaskRunner> commonTimerTaskRunnerMap = SpringUtil.getBeansOfType(CommonTimerTaskRunner.class);
if (ObjectUtil.isNotEmpty(commonTimerTaskRunnerMap)) {
Collection<CommonTimerTaskRunner> values = commonTimerTaskRunnerMap.values();
return values.stream().map(commonTimerTaskRunner -> commonTimerTaskRunner.getClass().getName()).collect(Collectors.toList());
} else {
return CollectionUtil.newArrayList();
}
}
}
2.5、状态枚举类和实体类
/**
* 定时任务状态枚举
*/
@Getter
public enum DevJobStatusEnum {
/**
* 运行
*/
RUNNING("RUNNING"),
/**
* 停止
*/
STOPPED("STOPPED");
private final String value;
DevJobStatusEnum(String value) {
this.value = value;
}
public static void validate(String value) {
boolean flag = RUNNING.getValue().equals(value) || STOPPED.getValue().equals(value);
if(!flag) {
throw new RuntimeException("不支持的定时任务状态");
}
}
}
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import java.io.Serializable;
import java.util.Date;
import lombok.Data;
/**
* 定时任务
*
* @TableName dev_job
*/
@TableName(value = "dev_job")
@Data
public class DevJob implements Serializable {
/**
*
*/
@TableId
private String id;
/**
* 名称
*/
private String name;
/**
* 编码
*/
private String code;
/**
* 分类
*/
private String category;
/**
* 任务类名
*/
private String actionClass;
/**
* cron表达式
*/
private String cronExpression;
/**
* 任务状态
*/
private String jobStatus;
/**
* 排序码
*/
private Integer sortCode;
/**
* 扩展信息
*/
private String extJson;
/**
* 删除标志
*/
private String deleteFlag;
/**
* 创建时间
*/
private Date createTime;
/**
* 创建用户
*/
private String createUser;
/**
* 修改时间
*/
private Date updateTime;
/**
* 修改用户
*/
private String updateUser;
@TableField(exist = false)
private static final long serialVersionUID = 1L;
}