在一个外包的项目开发中,甲方未提供xxl-job、quartz等定时任务调度框架,也不能在项目中随意引入新的框架,所以使用多线程ThreadPoolTaskScheduler的方式实现定时任务调度。
本文介绍了在项目限制条件下使用ThreadPoolTaskScheduler实现定时任务调度的解决方案。该方法在不引入额外框架的情况下,利用多线程机制实现了高效的定时任务调度功能,适用于框架受限的开发场景。
1、基本配置类和工具类
/**
* 线程池配置
*/
@Configuration
public class SchedulingConfig {
@Bean
public TaskScheduler taskScheduler() {
ThreadPoolTaskScheduler taskScheduler = new ThreadPoolTaskScheduler();
// 定时任务执行线程池核心线程数
int corePoolSize = Runtime.getRuntime().availableProcessors();
taskScheduler.setPoolSize(corePoolSize);
taskScheduler.setRemoveOnCancelPolicy(true);
//线程名称前缀
taskScheduler.setThreadNamePrefix("TaskSchedulerThreadPool-");
//线程池在关闭时等待所有任务完成
taskScheduler.setWaitForTasksToCompleteOnShutdown(true);
//等待时长,超出时间强制销毁,防止长时间占用导致线程阻塞
taskScheduler.setAwaitTerminationSeconds(60);
//拒绝策略
taskScheduler.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
return taskScheduler;
}
}
/**
* ScheduledFuture包装类
*/
public final class ScheduledTask {
public ScheduledFuture<?> future;
/**
* 取消定时任务
*/
public void cancel() {
ScheduledFuture<?> future = this.future;
if (future != null) {
future.cancel(true);
}
}
}
/**
* Runnable实现类
*/
public class SchedulingRunnable implements Runnable {
private static final Logger logger = LoggerFactory.getLogger(SchedulingRunnable.class);
private String beanName;
private String methodName;
private String params;
public SchedulingRunnable(String beanName, String methodName) {
this(beanName, methodName, null);
}
public SchedulingRunnable(String beanName, String methodName, String params) {
this.beanName = beanName;
this.methodName = methodName;
this.params = params;
}
@Override
public void run() {
try {
Object target = SpringContextUtils.getBean(beanName);
Method method = null;
if (!StringUtils.isEmpty(params)) {
method = target.getClass().getDeclaredMethod(methodName, String.class);
} else {
method = target.getClass().getDeclaredMethod(methodName);
}
ReflectionUtils.makeAccessible(method);
if (!StringUtils.isEmpty(params)) {
method.invoke(target, params);
} else {
method.invoke(target);
}
} catch (Exception ex) {
logger.error(String.format("定时任务执行异常 - bean:%s,方法:%s,参数:%s ", beanName, methodName, params), ex);
}
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
SchedulingRunnable that = (SchedulingRunnable) o;
if (params == null) {
return beanName.equals(that.beanName) &&
methodName.equals(that.methodName) &&
that.params == null;
}
return beanName.equals(that.beanName) &&
methodName.equals(that.methodName) &&
params.equals(that.params);
}
@Override
public int hashCode() {
if (params == null) {
return Objects.hash(beanName, methodName);
}
return Objects.hash(beanName, methodName, params);
}
}
/**
* 操作spring的bean工具类
*/
@Component
public class SpringContextUtils implements ApplicationContextAware {
private static ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext)
throws BeansException {
SpringContextUtils.applicationContext = applicationContext;
}
public static Object getBean(String name) {
return applicationContext.getBean(name);
}
public static <T> T getBean(Class<T> requiredType) {
return applicationContext.getBean(requiredType);
}
public static <T> T getBean(String name, Class<T> requiredType) {
return applicationContext.getBean(name, requiredType);
}
public static boolean containsBean(String name) {
return applicationContext.containsBean(name);
}
public static boolean isSingleton(String name) {
return applicationContext.isSingleton(name);
}
public static Class<? extends Object> getType(String name) {
return applicationContext.getType(name);
}
}
2、定时任务注册
/**
* 定时任务注册
*/
@Component
public class CronTaskRegistrar implements DisposableBean {
private final Map<Runnable, ScheduledTask> scheduledTasks = new ConcurrentHashMap<>(16);
@Resource
private TaskScheduler taskScheduler;
public TaskScheduler getScheduler() {
return this.taskScheduler;
}
public void addCronTask(Runnable task, String cronExpression) {
addCronTask(new CronTask(task, cronExpression));
}
public void addCronTask(CronTask cronTask) {
if (cronTask != null) {
Runnable task = cronTask.getRunnable();
if (this.scheduledTasks.containsKey(task)) {
removeCronTask(task);
}
this.scheduledTasks.put(task, scheduleCronTask(cronTask));
}
}
public void removeCronTask(Runnable task) {
ScheduledTask scheduledTask = this.scheduledTasks.remove(task);
if (scheduledTask != null)
scheduledTask.cancel();
}
public ScheduledTask scheduleCronTask(CronTask cronTask) {
ScheduledTask scheduledTask = new ScheduledTask();
scheduledTask.future = this.taskScheduler.schedule(cronTask.getRunnable(), cronTask.getTrigger());
return scheduledTask;
}
@Override
public void destroy() {
for (ScheduledTask task : this.scheduledTasks.values()) {
task.cancel();
}
this.scheduledTasks.clear();
}
}
3、定时任务预热
/**
* 定时任务预热
*/
@Service
public class SysJobRunner implements CommandLineRunner {
private static final Logger logger = LoggerFactory.getLogger(SysJobRunner.class);
@Resource
private CronTaskRegistrar cronTaskRegistrar;
@Override
public void run(String... args) {
// 初始加载数据库里状态为正常的定时任务
ScheduleSetting existedSysJob = new ScheduleSetting();
List<ScheduleSetting> jobList = existedSysJob.selectList(new LambdaQueryWrapper<ScheduleSetting>().eq(ScheduleSetting::getStatus, true));
if (CollectionUtils.isNotEmpty(jobList)) {
for (ScheduleSetting job : jobList) {
SchedulingRunnable task = new SchedulingRunnable(job.getBeanName(), job.getMethodName(), job.getMethodParams());
cronTaskRegistrar.addCronTask(task, job.getCron());
}
logger.info("定时任务已加载完毕...启用定时任务:{}个", jobList.size());
}else {
logger.info("定时任务启动成功...无定时任务");
}
}
}
4、定时任务配置实体类、mapper、service、serviceImpl
@Data
@TableName("schedule_setting")
@EqualsAndHashCode(callSuper = true)
@ApiModel(value = "定时任务表")
public class ScheduleSetting extends Model<ScheduleSetting> {
private static final long serialVersionUID = 1L;
@TableId(type = IdType.ASSIGN_ID)
@ApiModelProperty(value = "ID")
private Long id;
@ApiModelProperty(value = "bean名称")
private String beanName;
@ApiModelProperty(value = "方法名称")
private String methodName;
@ApiModelProperty(value = "方法参数 json字符串")
private String methodParams;
@ApiModelProperty(value = "cron表达式")
private String cron;
@ApiModelProperty(value = "状态(1正常 0暂停)")
private boolean status;
@ApiModelProperty(value = "备注:存储用于辨识的字段,如排查任务id")
private String remark;
@ApiModelProperty(value = "创建时间")
private Date createTime;
@ApiModelProperty(value = "更新时间")
private Date updateTime;
public static long getSerialVersionUID() {
return serialVersionUID;
}
}
@Mapper
public interface ScheduleSettingMapper extends BaseMapper<ScheduleSetting> {
}
public interface ScheduleSettingService extends IService<ScheduleSetting> {
boolean addCheckTask(RiskCheckTaskVo riskCheckTaskVo) throws IllegalAccessException;
boolean updateCheckTask(RiskCheckTaskVo riskCheckTaskVo) throws IllegalAccessException;
//isChanges 是否启用任务
boolean add(ScheduleSetting scheduleSetting, boolean isChanges);
//isChanges 是否重新启用任务
boolean update(ScheduleSetting scheduleSetting,boolean isChanges);
boolean del(Long id);
// 停止/启动任务
boolean changesStatus(Long id, boolean status);
}
@Service
public class ScheduleSettingServiceImpl extends ServiceImpl<ScheduleSettingMapper, ScheduleSetting> implements ScheduleSettingService {
private static final Logger logger = LoggerFactory.getLogger(ScheduleSettingServiceImpl.class);
@Resource
private CronTaskRegistrar cronTaskRegistrar;
@Override
public boolean addCheckTask(RiskCheckTaskVo riskCheckTaskVo) throws IllegalAccessException {
//转换cron表达式
String cron = TaskUtils.toCronExpression(riskCheckTaskVo.getCycle(), riskCheckTaskVo.getCycleUnit(),riskCheckTaskVo.getStartTime());
ScheduleSetting scheduleSetting = new ScheduleSetting();
scheduleSetting.setBeanName("CheckTask");
scheduleSetting.setMethodName("taskByParams");
scheduleSetting.setMethodParams(JSON.toJSONString(riskCheckTaskVo));
scheduleSetting.setCron(cron);
scheduleSetting.setStatus(riskCheckTaskVo.getStatus());
scheduleSetting.setRemark(riskCheckTaskVo.getId().toString());
return add(scheduleSetting,false);
}
@Override
public boolean updateCheckTask(RiskCheckTaskVo riskCheckTaskVo) throws IllegalAccessException {
//转换cron表达式
String cron = TaskUtils.toCronExpression(riskCheckTaskVo.getCycle(), riskCheckTaskVo.getCycleUnit(),riskCheckTaskVo.getStartTime());
ScheduleSetting scheduleSetting = getOne(new LambdaQueryWrapper<ScheduleSetting>().eq(ScheduleSetting::getRemark, riskCheckTaskVo.getId().toString()));
scheduleSetting.setMethodParams(JSON.toJSONString(riskCheckTaskVo));
scheduleSetting.setCron(cron);
scheduleSetting.setStatus(riskCheckTaskVo.getStatus());
return update(scheduleSetting,false);
}
@Override
public boolean add(ScheduleSetting scheduleSetting, boolean isChanges) {
scheduleSetting.setCreateTime(new Date());
scheduleSetting.setUpdateTime(new Date());
boolean save = save(scheduleSetting);
if (!save) {
logger.error("添加定时任务失败,任务id:{},备注:{}", scheduleSetting.getId(), scheduleSetting.getRemark());
return false;
} else {
if (isChanges && scheduleSetting.getStatus()) {// 添加成功,并且状态是1,直接放入任务器
SchedulingRunnable task = new SchedulingRunnable(scheduleSetting.getBeanName(), scheduleSetting.getMethodName(), scheduleSetting.getMethodParams());
cronTaskRegistrar.addCronTask(task, scheduleSetting.getCron());
}
}
return true;
}
@Override
public boolean update(ScheduleSetting scheduleSetting, boolean isChanges) {
// 查询修改前任务
ScheduleSetting existedSysJob = getById(scheduleSetting.getId());
// 修改任务
scheduleSetting.setCreateTime(new Date());
scheduleSetting.setUpdateTime(new Date());
boolean update = updateById(scheduleSetting);
if (!update) {
logger.error("修改定时任务失败,任务id:{},备注:{}", scheduleSetting.getId(), scheduleSetting.getRemark());
return false;
} else {
// 修改成功,则先删除任务器中的任务,并重新添加
SchedulingRunnable task1 = new SchedulingRunnable(existedSysJob.getBeanName(),
existedSysJob.getMethodName(),
existedSysJob.getMethodParams());
cronTaskRegistrar.removeCronTask(task1);
if (isChanges && scheduleSetting.getStatus()) {// 如果修改后的任务状态是1就加入任务器
SchedulingRunnable task = new SchedulingRunnable(scheduleSetting.getBeanName(),
scheduleSetting.getMethodName(),
scheduleSetting.getMethodParams());
cronTaskRegistrar.addCronTask(task, scheduleSetting.getCron());
}
}
return true;
}
@Override
public boolean del(Long id) {
// 先查询要删除的任务信息
ScheduleSetting existedSysJob = getById(id);
// 删除
boolean del = removeById(id);
if (!del) {
logger.error("删除定时任务失败,任务id:{},备注:{}", existedSysJob.getId(), existedSysJob.getRemark());
return false;
} else {
// 删除成功清除定时任务器中的对应任务
SchedulingRunnable task = new SchedulingRunnable(existedSysJob.getBeanName(),
existedSysJob.getMethodName(),
existedSysJob.getMethodParams());
cronTaskRegistrar.removeCronTask(task);
}
return del;
}
@Override
public boolean changesStatus(Long id, boolean status) {
// 修改任务状态
ScheduleSetting scheduleSetting = getById(id);
scheduleSetting.setStatus(status);
boolean update = updateById(scheduleSetting);
if (!update) {
logger.error("修改定时任务状态失败,任务id:{},备注:{}", scheduleSetting.getId(), scheduleSetting.getRemark());
return false;
}
// 查询修改后的任务信息
ScheduleSetting existedSysJob = getById(id);
// 如果状态是1则添加任务
if (existedSysJob.getStatus()) {
SchedulingRunnable task = new SchedulingRunnable(existedSysJob.getBeanName(),
existedSysJob.getMethodName(),
existedSysJob.getMethodParams());
cronTaskRegistrar.addCronTask(task, existedSysJob.getCron());
} else {
// 否则清除任务
SchedulingRunnable task = new SchedulingRunnable(existedSysJob.getBeanName(),
existedSysJob.getMethodName(),
existedSysJob.getMethodParams());
cronTaskRegistrar.removeCronTask(task);
}
return true;
}
}