前提条件:建表
建表sql语句
DROP TABLE IF EXISTS `scheduled_job`;
CREATE TABLE `scheduled_job` (
`job_id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键id',
`job_key` varchar(128) COLLATE utf8_bin NOT NULL COMMENT '定时任务完整类名',
`cron_expression` varchar(20) COLLATE utf8_bin NOT NULL COMMENT 'cron表达式',
`job_name` varchar(20) COLLATE utf8_bin NOT NULL COMMENT '任务名称',
`task_explain` varchar(50) COLLATE utf8_bin NOT NULL DEFAULT '' COMMENT '执行周期',
`job_desc` varchar(500) COLLATE utf8_bin DEFAULT NULL COMMENT '任务描述',
`status` tinyint(4) NOT NULL DEFAULT '1' COMMENT '状态,1:正常;-1:停用',
PRIMARY KEY (`job_id`),
UNIQUE KEY `job_key` (`job_key`),
UNIQUE KEY `cron_key_unique_idx` (`job_key`)
) ENGINE=InnoDB AUTO_INCREMENT=9 DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='定时任务表';
创建定时任务调度线程池
/**
* @author ljm
*/
@Configuration
@Slf4j
public class ScheduledConfig {
@Bean
public ThreadPoolTaskScheduler threadPoolTaskScheduler() {
log.info("创建定时任务调度线程池 start");
ThreadPoolTaskScheduler threadPoolTaskScheduler = new ThreadPoolTaskScheduler();
threadPoolTaskScheduler.setPoolSize(60);
threadPoolTaskScheduler.setThreadNamePrefix("taskExecutor-");
threadPoolTaskScheduler.setWaitForTasksToCompleteOnShutdown(true);
threadPoolTaskScheduler.setAwaitTerminationSeconds(60);
log.info("创建定时任务调度线程池 end");
return threadPoolTaskScheduler;
}
}
定时任务业务类
public interface ScheduledTaskService{
/**
* @Author ljm
* @Description 启动任务
* @Date 2023/1/9
* @return
*/
Boolean start(ScheduledJob scheduledJob);
/**
* @Author ljm
* @Description 停止任务
* @Date 2023/1/9
* @return
*/
Boolean stop(String jobKey);
/**
* @Author ljm
* @Description 重新启动任务
* @Date 2023/1/9
* @return
*/
Boolean restart(ScheduledJob scheduledJob);
/**
* @Author ljm
* @Description 初始化任务
* @Date 2023/1/9
* @return
*/
void initTask();
}
定时任务业务实现类
@Slf4j
@Service
public class ScheduledTaskServiceImpl implements ScheduledTaskService {
/**
* 可重入锁
*/
private ReentrantLock lock = new ReentrantLock();
/**
* 定时任务线程池
*/
@Autowired
private ThreadPoolTaskScheduler threadPoolTaskScheduler;
/**
* 启动状态的定时任务集合
*/
public Map<String, ScheduledFuture> scheduledFutureMap = new ConcurrentHashMap<>();
@Autowired
private ScheduledJobService scheduledJobService;
/**
* @Author ljm
* @Description 启动任务
* @Date 2023/1/9
* @return
*/
@Override
public Boolean start(ScheduledJob scheduledJob) {
String jobKey = scheduledJob.getJobKey();
log.info("启动定时任务"+jobKey);
//添加锁放一个线程启动,防止多人启动多次
log.info("开始加锁");
lock.lock();
try {
if(this.isStart(jobKey)){
log.info("当前任务在启动状态中");
return false;
}
//任务启动
this.doStartTask(scheduledJob);
} finally {
lock.unlock();
log.info("解锁完毕");
}
return true;
}
/**
* 任务是否已经启动
*/
private Boolean isStart(String taskKey) {
//校验是否已经启动
if (scheduledFutureMap.containsKey(taskKey)) {
if (!scheduledFutureMap.get(taskKey).isCancelled()) {
return true;
}
}
return false;
}
/**
* @Author ljm
* @Description 停止任务
* @Date 2023/1/9
* @return
*/
@Override
public Boolean stop(String jobKey) {
log.info("停止任务 "+jobKey);
boolean flag = scheduledFutureMap.containsKey(jobKey);
log.info("当前实例是否存在 "+flag);
if(flag){
ScheduledFuture scheduledFuture = scheduledFutureMap.get(jobKey);
scheduledFuture.cancel(true);
scheduledFutureMap.remove(jobKey);
}
return flag;
}
/**
* @Author ljm
* @Description 重新启动任务
* @Date 2023/1/9
* @return
*/
@Override
public Boolean restart(ScheduledJob scheduledJob) {
log.info("重启定时任务"+scheduledJob.getJobKey());
//停止
this.stop(scheduledJob.getJobKey());
return this.start(scheduledJob);
}
/**
* @Author ljm
* @Description 启动任务
* @Date 2023/1/9
* @return
*/
public void doStartTask(ScheduledJob sj){
log.info(sj.getJobKey());
if(sj.getStatus().intValue() != 1) {
return;
}
Class<?> clazz;
ScheduledOfTask task;
try {
clazz = Class.forName(sj.getJobKey());
task = (ScheduledOfTask) SpringContextUtil.getBean(clazz);
} catch (ClassNotFoundException e) {
throw new IllegalArgumentException("spring_scheduled_cron表数据" + sj.getJobKey() + "有误", e);
}
Assert.isAssignable(ScheduledOfTask.class, task.getClass(), "定时任务类必须实现ScheduledOfTask接口");
ScheduledFuture scheduledFuture = threadPoolTaskScheduler.schedule(task,(triggerContext ->
new CronTrigger(sj.getCronExpression()).nextExecutionTime(triggerContext)));
scheduledFutureMap.put(sj.getJobKey(),scheduledFuture);
}
/**
* @Author ljm
* @Description 初始化任务
* @Date 2023/1/9
* @return
*/
@Override
public void initTask() {
List<ScheduledJob> list = scheduledJobService.list();
for (ScheduledJob scheduledJob : list) {
//未启用
if(scheduledJob.getStatus().intValue() == -1){
continue;
}
doStartTask(scheduledJob);
}
}
}
初始化定时任务
@Slf4j
@Component
public class ScheduledTaskRunner implements ApplicationRunner {
@Autowired
private ScheduledTaskService scheduledTaskService;
/**
* @Author ljm
* @Description 启动项目初始化任务
* @Date 2023/1/9
* @return
*/
@Override
public void run(ApplicationArguments args) throws Exception {
log.info("----初始化定时任务开始----");
scheduledTaskService.initTask();
log.info("----初始化定时任务完成----");
}
}
执行调度任务接口
public interface ScheduledOfTask extends Runnable{
<T> void execute() throws Exception;
@SneakyThrows
@Override
default void run() {
execute();
}
}
@Component
/**
* @Author ljm
* @Description 当一个类实现了这个接口之后,这个类就可以方便的获得ApplicationContext对象(spring上下文),
* Spring发现某个Bean实现了ApplicationContextAware接口,Spring容器会在创建该Bean之后,自动调用该Bean的
* setApplicationContext(参数)方法,调用该方法时,会将容器本身ApplicationContext对象作为参数传递给该方法。
* @Date 2023/1/11 9:43
* @return
*/
public class SpringContextUtil implements ApplicationContextAware {
private static ApplicationContext applicationContext = null;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
if(SpringContextUtil.applicationContext == null){
SpringContextUtil.applicationContext = applicationContext;
}
}
public static ApplicationContext getApplicationContext() {
return applicationContext;
}
public static Object getBean(String name){
return getApplicationContext().getBean(name);
}
public static <T> T getBean(Class<T> clazz){
return getApplicationContext().getBean(clazz);
}
public static <T> T getBean(String name,Class<T> clazz){
return getApplicationContext().getBean(name, clazz);
}
}
定义类实现任务接口创建调度任务
@Component
@Slf4j
public class TaskJob1 implements ScheduledOfTask {
@Autowired
private RedisUtil redisUtil;
@Autowired
private MockRules mockRules;
@Autowired
private TableNameService tableNameService;
/**
* @Author ljm
* @Description 启动任务
* @Date 2023/2/13 15:20
* @return
*/
@Override
public <T> void execute() throws Exception {
log.info("执行任务 1 "+ LocalDateTime.now());
log.info("执行任务 1 完成 "+ LocalDateTime.now());
}
}
动态修改任务
@Data
@TableName("scheduled_job")
public class ScheduledJob {
@TableId(value = "job_id",type = IdType.AUTO)
private Integer jobId;
@NotBlank(message = "类名不能为空")
private String jobKey;
private String cronExpression;
private String jobName;
private String taskExplain;
private String jobDesc;
private Integer status;
}
@RestController
@CrossOrigin
@RequestMapping("/job")
public class ScheduledJobController {
@Autowired
private ScheduledJobService scheduledJobService;
@ApiOperation("修改并重启定时任务")
@PostMapping(value = "/update")
public Result update(@RequestBody ScheduledJob scheduledJob){
if(scheduledJobService.updateOne(scheduledJob)){
return Result.ok();
}
return Result.fail();
}
@ApiOperation("停止定时任务")
@PostMapping(value = "/stop")
public Result stop(@RequestParam Integer jobId){
boolean stop = scheduledJobService.stop(jobId);
if(stop){
return Result.ok();
}
return Result.fail();
}
public static void main(String[] args) {
System.out.println("=======================================");
}
@ApiOperation("定时任务启用或停用")
@PostMapping(value = "/jobEnable/{jobId}/{status}")
public Result stop(@PathVariable Integer jobId,@PathVariable Integer status) throws Exception {
boolean stop = scheduledJobService.jobEnable(jobId,status);
if(stop){
return Result.ok();
}
return Result.fail();
}
@ApiOperation("修改定时任务")
@PostMapping(value = "/updateJob")
public Result updateJob(@RequestBody ScheduledJob scheduledJob) throws Exception {
Result result = scheduledJobService.updateJob(scheduledJob);
return result;
}
@ApiOperation("定时任务查询")
@PostMapping("/list/{pageNo}/{pageSize}")
public Result JobList(@PathVariable("pageNo") Integer pageNo,
@PathVariable("pageSize") Integer pageSize,
@RequestBody SearchObj searchObj){
Page<ScheduledJob> page = new Page<>(pageNo,pageSize);
Page<ScheduledJob> list = null;
try {
QueryWrapper<ScheduledJob> wrapper = new QueryWrapper<>();
wrapper.like(StringUtils.isNotBlank(searchObj.getJobKey()),"job_key",searchObj.getJobKey());
wrapper.like(StringUtils.isNotBlank(searchObj.getJobName()),"job_name",searchObj.getJobName());
list = scheduledJobService.page(page,wrapper);
return Result.ok(list);
} catch (Exception e) {
return Result.fail();
}
}
@ApiOperation("创建定时任务")
@PostMapping("createJob")
public Result createJob(@Valid @RequestBody ScheduledJob scheduledJob){
Boolean scheduledJobs = scheduledJobService.createJob(scheduledJob);
if(scheduledJobs){
return Result.ok();
}
return Result.fail();
}
}
public interface ScheduledJobService extends IService<ScheduledJob> {
/**
* 修改定时任务,并重新启动
* @param scheduledJob
* @return
*/
boolean updateOne(ScheduledJob scheduledJob);
/**
* @Author ljm
* @Description 停止定时任务
*/
boolean stop(Integer jobId);
/**
* @Author ljm
* @Description 定时任务启用或停用
* @Date 2023/1/9
* @return
*/
boolean jobEnable(Integer jobId, Integer status) throws Exception;
/**
* @Author ljm
* @Description 修改定时任务
* @Date 2023/1/9
* @return
*/
Result updateJob(ScheduledJob scheduledJob) throws Exception;
/**
* @Author ljm
* @Description 创建定时任务
* @Date 2023/1/11
* @return
*/
Boolean createJob(ScheduledJob scheduledJob);
}
@Service
@Slf4j
public class ScheduledJobServiceImpl extends ServiceImpl<ScheduledJobMapper, ScheduledJob> implements ScheduledJobService {
@Autowired
private ScheduledTaskService scheduledTaskService;
/**
* 修改定时任务,并重新启动
* @param scheduledJob
* @return
*/
@Override
public boolean updateOne(ScheduledJob scheduledJob) {
boolean update = updateById(scheduledJob);
if(update){
scheduledTaskService.restart(getById(scheduledJob.getJobId()));
}
return true;
}
/**
* @Author ljm
* @Description 停止定时任务
*/
@Override
public boolean stop(Integer jobId) {
ScheduledJob scheduledJob = baseMapper.selectById(jobId);
Boolean stop = scheduledTaskService.stop(scheduledJob.getJobKey());
return stop;
}
/**
* @Author ljm
* @Description 定时任务启用或停用
* @Date 2023/1/9
* @return
*/
@Override
public boolean jobEnable(Integer jobId, Integer status) throws Exception {
Boolean start = true;
Boolean stop = true;
ScheduledJob scheduledJob = baseMapper.selectById(jobId);
scheduledJob.setStatus(status);
baseMapper.updateById(scheduledJob);
if (status == 1){
start = scheduledTaskService.start(scheduledJob);
}
if (status == 0){
stop = scheduledTaskService.stop(scheduledJob.getJobKey());
}
return start && stop;
}
/**
* @Author ljm
* @Description 修改定时任务
* @Date 2023/1/9
* @return
*/
@Override
public Result updateJob(ScheduledJob scheduledJob) throws Exception {
Integer jobId = scheduledJob.getJobId();
ScheduledJob selectById = baseMapper.selectById(jobId);
Integer status = selectById.getStatus();
if (status == 1){
return Result.ok("任务启动中,不能修改");
}
int update = baseMapper.updateById(scheduledJob);
return Result.ok(update>0);
}
/**
* @Author ljm
* @Description 创建定时任务
* @Date 2023/1/11
* @return
*/
@Override
public Boolean createJob(ScheduledJob scheduledJob) {
scheduledJob.setStatus(0);
boolean save = save(scheduledJob);
return save;
}
}