org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler
定时任务调度线程池
CREATE TABLE `t_sys_job` (
`id` bigint(20) unsigned zerofill NOT NULL AUTO_INCREMENT COMMENT '任务key',
`job_name` varchar(64) NOT NULL COMMENT '任务名称',
`bean_class` varchar(128) NOT NULL COMMENT '类路径',
`cron` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT 'cron表达式',
`status` tinyint(1) NOT NULL,
`is_deleted` tinyint(1) DEFAULT '0' COMMENT '删除标识 1是 0否',
`create_time` datetime DEFAULT NULL,
`update_time` datetime DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
@Configuration
@Slf4j
@EnableScheduling
public class SchedulingConfigure {
@Bean
public ThreadPoolTaskScheduler threadPoolTaskScheduler() {
ThreadPoolTaskScheduler threadPoolTaskScheduler = new ThreadPoolTaskScheduler();
threadPoolTaskScheduler.setPoolSize(20);
threadPoolTaskScheduler.setThreadNamePrefix("schedule-task-");
threadPoolTaskScheduler.setWaitForTasksToCompleteOnShutdown(true);
threadPoolTaskScheduler.setAwaitTerminationSeconds(60);
log.info(">>>ThreadPoolTaskScheduler定时任务线程池初始化配置完成");
return threadPoolTaskScheduler;
}
}
@Data
@TableName("t_sys_job")
public class SysJob {
private static final long serialVersionUID = 1L;
/**
* 任务名称
*/
private String jobName;
/**
* 类路径
*/
private String beanClass;
/**
* cron表达式
*/
private String cron;
/**
* 状态值
*/
private Integer status;
/**
* 主键id
*/
@TableId(value = "id", type = IdType.AUTO)
private Long id;
/**
* 创建时间
*/
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
/**
* 更新时间
*/
@TableField(fill = FieldFill.INSERT)
private LocalDateTime updateTime;
/**
* 删除标志
* 1 删除
* 0 未删除
*/
private Integer isDeleted;
}
public interface ISysJobService extends IService<SysJob> {
/**
* 获取全部任务列表
*
* @return List<SysJob>
*/
List<SysJob> listAllJob();
/**
* 更新任务状态
*
* @param jobId 工作id
* @param status 状态
*/
void updateJobStatus(Long jobId, Integer status);
/**
* 新增或更新定时任务信息
*
* @param entity 定时任务实体类
*/
void addOrUpdate(SysJob entity);
/**
* 删除定时任务
*
* @param jobId 定时任务id
*/
void deleteByJobId(Long jobId);
/**
* 启动任务
*
* @param jobId 工作id
*/
void startJob(Long jobId);
/**
* 停止任务
*
* @param jobId 工作id
*/
void stopJob(Long jobId);
}
@Service
public class SysJobServiceImpl extends ServiceImpl<SysJobMapper, SysJob> implements ISysJobService {
@Resource
private ScheduledJobService scheduledJobService;
@Override
public List<SysJob> listAllJob() {
LambdaQueryWrapper<SysJob> wrapper = Wrappers.lambdaQuery();
wrapper.eq(SysJob::getIsDeleted, 0);
return list(wrapper);
}
@Override
public void updateJobStatus(Long jobId, Integer status) {
LambdaUpdateWrapper<SysJob> updateWrapper = Wrappers.lambdaUpdate();
updateWrapper.set(SysJob::getStatus, status)
.set(SysJob::getUpdateTime, LocalDateTime.now())
.eq(SysJob::getId, jobId);
update(updateWrapper);
}
@Override
public void addOrUpdate(SysJob entity) {
// 检查任务是否重复创建
LambdaQueryWrapper<SysJob> wrapper = Wrappers.lambdaQuery();
wrapper.eq(SysJob::getJobName, entity.getJobName())
.eq(SysJob::getBeanClass, entity.getBeanClass())
.eq(SysJob::getIsDeleted, 0);
if (entity.getId() != null) {
wrapper.ne(SysJob::getId, entity.getId());
}
if (list(wrapper).size() > 0) {
throw new BaseException("任务重复创建");
}
// 新增任务,如果是编辑,则删除原来任务创建新任务
if (entity.getId() != null) {
// 删除原任务
SysJob sysJob = getById(entity.getId());
if (JobStatusEnum.RUNNING.getStatus().equals(sysJob.getStatus())) {
throw new BaseException("任务运行中,不允许删除");
}
sysJob.setJobName(sysJob.getJobName() + "_已删除");
sysJob.setStatus(JobStatusEnum.DELETED.getStatus());
// 修改原任务状态,以及从调度器中删除
updateById(sysJob);
scheduledJobService.cancel(sysJob.getId());
// 置空id进行新增覆盖
entity.setId(null);
}
// 创建任务但未加入调度器
entity.setStatus(JobStatusEnum.NOT_SCHEDULE.getStatus());
// 新增任务
save(entity);
}
@Override
public void deleteByJobId(Long jobId) {
SysJob sysJob = getById(jobId);
if (JobStatusEnum.RUNNING.getStatus().equals(sysJob.getStatus())) {
throw new BaseException("任务运行中,不允许删除");
}
// 删除任务
LambdaUpdateWrapper<SysJob> updateWrapper = Wrappers.lambdaUpdate();
updateWrapper.set(SysJob::getIsDeleted, 1)
.set(SysJob::getUpdateTime, LocalDateTime.now())
.eq(SysJob::getId, jobId);
update(updateWrapper);
// 从调度器中移除任务
scheduledJobService.cancel(jobId);
}
@Override
public void startJob(Long jobId) {
scheduledJobService.start(jobId);
}
@Override
public void stopJob(Long jobId) {
scheduledJobService.stop(jobId);
}
}
@RestController
@RequestMapping("/sys/job")
public class SysJobController {
@Resource
private ISysJobService sysJobService;
@GetMapping("/all")
public ResultInfo getAllJobList() {
return ResultInfo.success(sysJobService.listAllJob());
}
@PostMapping("/addOrUpdate")
public ResultInfo addJob(@RequestBody SysJob sysJob) {
sysJobService.addOrUpdate(sysJob);
return ResultInfo.success();
}
@PostMapping("/delete/{jobId}")
public ResultInfo deleteByJobId(@PathVariable("jobId") Long jobId) {
sysJobService.deleteByJobId(jobId);
return ResultInfo.success("删除成功");
}
@PostMapping("/start/{jobId}")
public ResultInfo start(@PathVariable("jobId") Long jobId) {
sysJobService.startJob(jobId);
return ResultInfo.success("启动成功");
}
@PostMapping("/stop/{jobId}")
public ResultInfo stop(@PathVariable("jobId") Long jobId) {
sysJobService.stopJob(jobId);
return ResultInfo.success("暂停成功");
}
}
public enum JobStatusEnum {
/**
* 未加入调度器,(创建还未启动)
*/
NOT_SCHEDULE(0, "未加入调度器"),
/**
* 加入调度器,但未运行,(已启动但是还没运行)
*/
SCHEDULED_BUT_NOT_RUNNING(1, "加入调度器,但未运行"),
/**
* 运行中
*/
RUNNING(2, "运行中"),
/**
* 从调度器中已删除
*/
DELETED(3, "任务已删除"),
;
private Integer status;
private String detail;
JobStatusEnum(Integer status, String detail) {
this.status = status;
this.detail = detail;
}
public Integer getStatus() {
return status;
}
public void setStatus(Integer status) {
this.status = status;
}
public String getDetail() {
return detail;
}
public void setDetail(String detail) {
this.detail = detail;
}
}
@Component
public class SpringBeanUtils implements ApplicationContextAware {
private static ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
SpringBeanUtils.applicationContext = applicationContext;
}
/**
* 获取applicationContext
*/
public static ApplicationContext getApplicationContext() {
return applicationContext;
}
/**
* 通过name获取 Bean.
*/
public Object getBean(String name) {
return getApplicationContext().getBean(name);
}
/**
* 通过class获取Bean.
*/
public <T> T getBean(Class<T> clazz) {
return getApplicationContext().getBean(clazz);
}
/**
* 通过name,以及Clazz返回指定的Bean
*/
public <T> T getBean(String name, Class<T> clazz) {
return getApplicationContext().getBean(name, clazz);
}
}
@Service
@Slf4j
public class ScheduledJobService {
@Resource
private SpringBeanUtils springBeanUtils;
@Resource
private ISysJobService sysJobService;
@Resource
private ThreadPoolTaskScheduler threadPoolTaskScheduler;
/**
* 保存已经加入调度器的任务map
*/
private final ConcurrentHashMap<Long, ScheduledFuture<?>> scheduledFutureMap = new ConcurrentHashMap<>();
private final ReentrantLock lock = new ReentrantLock();
/**
* 初始化启动任务
*/
public void init() {
List<SysJob> sysJobs = sysJobService.listAllJob();
if (sysJobs.size() == 0) {
return;
}
for (SysJob sysJob : sysJobs) {
if (JobStatusEnum.NOT_SCHEDULE.getStatus().equals(sysJob.getStatus())
|| this.isScheduled(sysJob.getId())) {
// 任务未加入调度器或已经加入调度器的过滤
continue;
}
// 状态为已加入调度器或上次运行中还未结束的 都加入调度器中等待下次运行
this.doScheduleJob(sysJob);
}
log.info("定时任务初始化完成");
}
/**
* 启动任务
*
* @param jobId job主键id
*/
public void start(Long jobId) {
log.info("启动任务:-> jobId_{}", jobId);
// 加入调度器
schedule(jobId);
log.info("启动任务结束:-> jobId_{}", jobId);
// 更新任务状态
sysJobService.updateJobStatus(jobId, JobStatusEnum.SCHEDULED_BUT_NOT_RUNNING.getStatus());
}
/**
* 停止任务
*
* @param jobId job主键id
*/
public void stop(Long jobId) {
log.info("停止任务:-> jobId_{}", jobId);
// 取消任务
cancel(jobId);
log.info("停止任务结束:-> jobId_{}", jobId);
// 更新表中任务状态为已停止
sysJobService.updateJobStatus(jobId, JobStatusEnum.NOT_SCHEDULE.getStatus());
}
/**
* 取消任务
*
* @param jobId job主键id
*/
public void cancel(Long jobId) {
// 任务是否存在
if (scheduledFutureMap.containsKey(jobId)) {
ScheduledFuture<?> scheduledFuture = scheduledFutureMap.get(jobId);
if (!scheduledFuture.isCancelled()) {
// 取消调度
scheduledFuture.cancel(true);
}
}
}
private void schedule(Long jobId) {
// 添加锁,只允许单个线程访问,防止任务启动多次
lock.lock();
try {
if (isScheduled(jobId)) {
log.error("任务jobId_{}已经加入调度器,无需重复操作", jobId);
return;
}
// 通过jobKey查询jobBean对象
SysJob sysJob = Optional.ofNullable(sysJobService.getById(jobId))
.orElseThrow(() -> new BaseException("任务不存在"));
// 启动定时任务
doScheduleJob(sysJob);
} finally {
// 释放锁资源
lock.unlock();
}
}
/**
* 任务是否已经进入调度器
*
* @param jobId 任务id
* @return {@link Boolean}
*/
private Boolean isScheduled(Long jobId) {
if (scheduledFutureMap.containsKey(jobId)) {
return !scheduledFutureMap.get(jobId).isCancelled();
}
return false;
}
/**
* 执行启动任务
*
* @param sysJob 任务实体类对象
*/
private void doScheduleJob(SysJob sysJob) {
Long jobId = sysJob.getId();
String beanClass = sysJob.getBeanClass();
String jobName = sysJob.getJobName();
String cron = sysJob.getCron();
// 从Spring中获取目标的job业务实现类
BaseJob baseJob = parseFrom(beanClass);
if (baseJob == null) {
return;
}
baseJob.setJobId(jobId);
baseJob.setJobName(jobName);
ScheduledFuture<?> scheduledFuture = threadPoolTaskScheduler.schedule(baseJob,
triggerContext -> {
CronTrigger cronTrigger = new CronTrigger(cron);
return cronTrigger.nextExecutionTime(triggerContext);
});
log.info("任务加入调度器 -> jobId:{},jobName:{}", jobId, jobName);
// 将启动的任务放入map
assert scheduledFuture != null;
scheduledFutureMap.put(jobId, scheduledFuture);
}
private BaseJob parseFrom(String beanClass) {
try {
Class<?> clazz = Class.forName(beanClass);
return (BaseJob) springBeanUtils.getBean(clazz);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return null;
}
}
@Data
public abstract class BaseJob implements Runnable {
/**
* 任务id
*/
private Long jobId;
/**
* 任务名称
*/
private String jobName;
}
@Component
@Slf4j
public class DemoJob extends BaseJob {
@Resource
private ISysJobService sysJobService;
@Override
public void run() {
Long jobId = getJobId();
// 修改状态为运行中
sysJobService.updateJobStatus(jobId, JobStatusEnum.RUNNING.getStatus());
try {
//TODO 业务逻辑代码执行
log.info("DemoJob.class_jobId_{},jobName_{}_开始执行..当前时间{}", jobId, getJobName(), LocalDateTime.now());
Thread.sleep(3000L);
} catch (Exception ex) {
ex.printStackTrace();
} finally {
sysJobService.updateJobStatus(jobId, JobStatusEnum.SCHEDULED_BUT_NOT_RUNNING.getStatus());
}
}
}
@SpringBootApplication(scanBasePackages = {"com.example.grape"})
@MapperScan("com.example.grape.dao.mapper")
@EnableScheduling
public class GrapeApplication {
public static void main(String[] args) {
SpringApplication.run(GrapeApplication.class, args);
}
}