springboot任务动态注册、修改、停止、删除和统一管理
github地址:https://github.com/softwarevax/maven/tree/main/unified-task-schedule
1、任务实体类
public class Job {
/**
* 任务id, 用于标识,默认使用全限定类名
*/
private String jobId;
/**
* 任务名称, 默认简单类名
*/
private String jobName;
/**
* cron表达式, 修改后即可生效
*/
private String cron;
/**
* 任务描述
*/
private String description;
/**
* 是否启用, 默认启用, 修改后即可生效
*/
private boolean enable = true;
/**
* 是否处于等待执行下个任务的状态
*/
private boolean active;
/**
* 任务运行类
*/
private Class<? extends Runnable> clazz;
}
2、任务操作
public class JobHandler {
private ScheduledTask scheduledTask;
private TriggerTask triggerTask;
private TriggerContext triggerContext;
}
3、spring动态注册bean,任务运行类中需要注入bean时,可直接注入
public class ApplicationContextUtils implements ApplicationContextAware {
private static ApplicationContext CONTEXT;
/**
* 设置spring上下文
* @param ctx spring上下文
* @throws BeansException
* */
@Override
public void setApplicationContext(ApplicationContext ctx) throws BeansException {
CONTEXT = ctx;
}
/**
* 获取容器
* @return
*/
public static ApplicationContext getApplicationContext() {
return CONTEXT;
}
/**
* 获取容器对象
* @param type
* @param <T>
* @return
*/
public static <T> T getBean(Class<T> type) {
return CONTEXT.getBean(type);
}
public static <T> T getBean(String name,Class<T> clazz){
return CONTEXT.getBean(name, clazz);
}
public static Object getBean(String name){
return CONTEXT.getBean(name);
}
/**
* springboot动态注册bean
* @param clazz
* @param <T>
* @return
*/
public static <T> T register(Class<T> clazz) {
ConfigurableApplicationContext configurableApplicationContext = (ConfigurableApplicationContext) ApplicationContextUtils.getApplicationContext();
DefaultListableBeanFactory defaultListableBeanFactory = (DefaultListableBeanFactory) configurableApplicationContext.getBeanFactory();
BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(clazz);
if(defaultListableBeanFactory.getBeanNamesForType(clazz).length > 0) {
return defaultListableBeanFactory.getBean(clazz);
}
defaultListableBeanFactory.registerBeanDefinition(clazz.getName(), beanDefinitionBuilder.getRawBeanDefinition());
return (T) ApplicationContextUtils.getBean(clazz.getName());
}
}
4、配置SchedulingConfigurer,实现动态任务的关键
public class JobSchedulingConfigurer implements SchedulingConfigurer {
private ScheduledTaskRegistrar registrar;
@Override
public void configureTasks(ScheduledTaskRegistrar scheduledTaskRegistrar) {
this.registrar = scheduledTaskRegistrar;
}
public ScheduledTaskRegistrar getRegistrar() {
return registrar;
}
public void setRegistrar(ScheduledTaskRegistrar registrar) {
this.registrar = registrar;
}
}
5、任务增删改查
public class SchedulerManager {
/**
* 任务容器
*/
private Map<Job, JobHandler> tasks = new ConcurrentHashMap<>();
/**
* 任务注册
*/
@Autowired
private JobSchedulingConfigurer register;
/**
* 新增任务, 自生效
* @param job 任务实体
* @return 返回新增的任务
*/
public Job addJob(Job job) {
Assert.notNull(job, "job can't be null");
ScheduledTaskRegistrar registrar = register.getRegistrar();
Runnable runnable = ApplicationContextUtils.register(job.getClazz());
if(job.getJobId() == null || "".equals(job.getJobId())) {
job.setJobId(job.getClazz().getName());
}
Assert.isNull(this.getJob(job.getJobId()), "任务[" + job.getJobId() + "]已存在");
if(job.getJobName() == null || "".equals(job.getJobName())) {
job.setJobName(ClassUtils.getShortName(job.getClazz()));
}
CronExpress cron = AnnotationUtils.findAnnotation(job.getClazz(), CronExpress.class);
if(cron != null && !"".equals(cron.value())) {
// 注解的属性,大于配置的属性,方便调试
job.setCron(cron.value());
}
job.setEnable(true);
job.setActive(true);
JobHandler entity = new JobHandler();
TriggerTask triggerTask = new TriggerTask(runnable, (TriggerContext triggerContext) -> {
// 每次任务执行均会进入此方法
CronTrigger trigger = new CronTrigger(job.getCron());
entity.setTriggerContext(triggerContext);
return job.isEnable() ? trigger.nextExecutionTime(triggerContext) : null;
});
ScheduledTask scheduledTask = registrar.scheduleTriggerTask(triggerTask);
entity.setScheduledTask(scheduledTask);
entity.setTriggerTask(triggerTask);
tasks.put(job, entity);
return job;
}
/**
* 任务类(必须标注了@CronExpress注解,且实现了Runnable接口)
* @param clazz 接口类
* @return 任务对象
*/
public Job addJob(Class<? extends Runnable> clazz) {
Job job = new Job();
job.setClazz(clazz);
return this.addJob(job);
}
/**
* 获取任务操作对象
* @param jobId 任务id
* @return 任务操作对象
*/
public JobHandler getJobHandler(String jobId) {
return tasks.get(new Job(jobId));
}
/**
* 根据任务id获取任务
* @param jobId 任务id
* @return 任务实体
*/
public Job getJob(String jobId) {
Assert.hasText(jobId, "jobId can't be null");
Set<Job> jobs = tasks.keySet();
if(jobs.size() == 0) {
return null;
}
Iterator<Job> iterator = jobs.iterator();
while (iterator.hasNext()) {
Job next = iterator.next();
if(jobId.equals(next.getJobId())) {
return next;
}
}
return null;
}
/**
* 关闭任务(若任务正在执行,待任务执行完)
* @param jobId 任务id
* @return 是否关闭成功
*/
public boolean shutDown(String jobId) {
try {
JobHandler handler = this.getJobHandler(jobId);
Assert.notNull(handler, "任务[" + jobId + "]不存在");
handler.getScheduledTask().cancel();
Job job = getJob(jobId);
job.setActive(false);
return true;
} catch (Exception e) {
return false;
}
}
/**
* 启动已经注册的任务
* @param jobId 任务id
* @return 是否成功启动
*/
public boolean startUp(String jobId) {
try {
JobHandler handler = this.getJobHandler(jobId);
Assert.notNull(handler, "任务[" + jobId + "]不存在");
register.getRegistrar().scheduleTriggerTask(handler.getTriggerTask());
Job job = getJob(jobId);
job.setActive(true);
return true;
} catch (Exception e) {
return false;
}
}
/**
* 获取所有的任务实体
* @return
*/
public List<Job> getJobs() {
return new ArrayList<>(tasks.keySet());
}
/**
* 删除任务,先关闭再删除
* @param jobId
* @return
*/
public boolean deleteJob(String jobId) {
try {
Job job = this.getJob(jobId);
Assert.notNull(job, "任务[" + jobId + "]不存在");
shutDown(jobId);
tasks.remove(job);
return true;
} catch (Exception e) {
return false;
}
}
}
6、任务运行类
@CronExpress("0/1 * * * * *")
public class HelloJob implements Runnable {
@Override
public void run() {
System.out.println("hello msg" + Thread.currentThread().getId());
}
}
7、测试
ConfigurableApplicationContext ctx = SpringApplication.run(UnifiedTaskScheduleStarterApplication.class, args);
SchedulerManager schedulerManager = ctx.getBean(SchedulerManager.class);
Job job = schedulerManager.addJob(com.github.softwarevax.web.HelloJob.class);
schedulerManager.shutDown(job.getJobId());
所有的任务,放在一个模块中,该模块不需要特殊依赖包,启动时,注册任务,实现统一管理