SpringBoot定时任务动态执行
1. 背景
之前遇到的项目中有个这样的需求,要求定时执行某些任务,但是执行时间是在页面上配置的,配置完之后入库。因此,我们就需要在数据库中获取到任务执行的时间,再进行动态配置执行。
2. CommandLineRunner简介
CommandLineRunner接口在Spring Boot应用程序中提供了一种在应用程序启动时运行代码的简单方法。当你实现了CommandLineRunner接口,并将其实例化为Spring容器中的Bean时,Spring Boot会在启动时自动调用run方法。
CommandLineRunner接口的实现类的启动顺序通常是通过它们在Spring容器中的定义顺序来确定的。这意味着,如果你在配置类中定义了多个实现了CommandLineRunner接口的Bean,那么这些Bean会按照它们在配置类中被声明的顺序依次执行。
下面是一个简单的例子,演示了如何定义多个CommandLineRunner Bean,并展示了它们的启动顺序:
@SpringBootApplication
public class CommandLineRunnerOrderApplication {
public static void main(String[] args) {
SpringApplication.run(CommandLineRunnerOrderApplication.class, args);
}
@Bean
public CommandLineRunner runnerOne() {
return args -> System.out.println("Runner One starts");
}
@Bean
public CommandLineRunner runnerTwo() {
return args -> System.out.println("Runner Two starts");
}
}
在这个例子中,runnerOne 会首先被调用,然后是 runnerTwo。输出顺序会反映这一启动顺序。
3. 实现编码
- 首先定义任务实体类及其数据库操作mapper接口。
实体类:
package com.cron.test.dao;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class MyTask implements Serializable {
private String taskId; // 任务id
private String taskName; // 任务名称
private String taskCron; // 任务执行时间cron表达式
private String classPath; // 任务类路径
private Boolean taskSwitch; // 任务是否开启
}
数据库操作接口:
package com.cron.test.mapper;
import com.cron.test.dao.MyTask;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
@Mapper
public interface MyTaskMapper {
List<MyTask> findAllTasks();
}
- Spring容器工具类,主要用于根据类路径获取实例
package com.cron.test.config;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
/**
* Spring容器工具类
*/
@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);
}
}
- ThreadPoolTaskScheduler配置类
通过源码得知,ThreadPoolTaskScheduler类实现了SchedulingTaskExecutor和TaskScheduler接口。
该类中schedule(Runnable task, Trigger trigger)方法,通过分别传入线程任务(业务逻辑)和Trigger触发器对象作为参数,支持动态创建指定 cron 表达式的定时任务,这就解决了每次修改完配置都需要重启的问题。
package com.cron.test.config;
import org.springframework.context.annotation.Bean;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.stereotype.Component;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ScheduledFuture;
@Component
public class ScheduingConfig {
public static ConcurrentHashMap<String, ScheduledFuture> taskCache = new ConcurrentHashMap<>();
public static volatile ThreadPoolTaskScheduler threadPoolTaskScheduler = null;
@Bean
public ThreadPoolTaskScheduler threadPoolTaskScheduler() {
if (threadPoolTaskScheduler == null) {
synchronized (ThreadPoolTaskScheduler.class) {
if (threadPoolTaskScheduler == null) {
threadPoolTaskScheduler = new ThreadPoolTaskScheduler();
threadPoolTaskScheduler.setPoolSize(200);
threadPoolTaskScheduler.setThreadNamePrefix("timingTaskExecutor-");
threadPoolTaskScheduler.setAwaitTerminationSeconds(30);
threadPoolTaskScheduler.setWaitForTasksToCompleteOnShutdown(true);
}
}
}
return threadPoolTaskScheduler;
}
}
- 定时任务管理类(创建和删除)
package com.cron.test.config;
import com.cron.test.dao.MyTask;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.Trigger;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.scheduling.support.CronTrigger;
import org.springframework.scheduling.support.PeriodicTrigger;
import org.springframework.util.ObjectUtils;
import javax.annotation.Resource;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ScheduledFuture;
@Configuration
public class DynamicTask {
private static final Logger LOGGER = LoggerFactory.getLogger(DynamicTask.class);
@Resource
private SpringBeanUtils springBeanUtils;
@Resource
private ThreadPoolTaskScheduler threadPoolTaskScheduler;
// 存放定时任务信息
public static ConcurrentHashMap<String, MyTask> timingTaskMap = new ConcurrentHashMap<>();
/**
* @param myTasks
*/
public void refreshAllTask(List<MyTask> myTasks) {
for (MyTask myTask : myTasks) {
if (ObjectUtils.isEmpty(myTask) && ObjectUtils.isEmpty(myTask.getTaskId())) {
continue;
}
try {
refreshSingleTask(myTask);
} catch (Exception e) {
LOGGER.error("刷新定时任务失败");
e.printStackTrace();
}
}
LOGGER.info("统计任务数:{}", ScheduingConfig.taskCache.size());
}
/**
* 刷新任务,添加或删除任务
* @param myTask
*/
void refreshSingleTask(MyTask myTask) {
if (ObjectUtils.isEmpty(myTask)) {
return;
}
String taskId = myTask.getTaskId();
String taskTaskExecuteCron = myTask.getTaskCron();
String taskName = myTask.getTaskName();
boolean flag1 = ScheduingConfig.taskCache.containsKey(taskId);
boolean flag2 = timingTaskMap.containsKey(taskId);
ScheduledFuture scheduledFuture = null;
if (flag1 && flag2) { // 任务id已存在
if (!myTask.getTaskSwitch()) { // 任务关闭
ScheduingConfig.taskCache.get(taskId).cancel(true); // 任务停止
// 删除任务id
ScheduingConfig.taskCache.remove(taskId);
timingTaskMap.remove(taskId);
LOGGER.info("任务删除,id:{},名称:{}", taskId, taskName);
} else { // 任务开启
ScheduledFuture future = ScheduingConfig.taskCache.get(taskId);
future.cancel(true);
future = threadPoolTaskScheduler.schedule(getRunnableInstance(myTask.getClassPath()),
getTrigger(null, taskTaskExecuteCron));
timingTaskMap.put(taskId, myTask);
ScheduingConfig.taskCache.put(taskId, future);
}
} else if (myTask.getTaskSwitch()) { // 任务id不存在,判断任务是否开启
scheduledFuture = threadPoolTaskScheduler.schedule(getRunnableInstance(myTask.getClassPath()),
getTrigger(null, taskTaskExecuteCron));
ScheduingConfig.taskCache.put(taskId, scheduledFuture);
timingTaskMap.put(taskId, myTask);
}
LOGGER.info("处理定时任务----id:{},任务名称:{},taskTaskExecuteCron:{},放到定时任务池结束>>>>>>>>>>>>>>>>>", myTask.getTaskId(), taskName, taskTaskExecuteCron);
}
/**
* @param classPath
* @return
*/
Runnable getRunnableInstance(String classPath) {
Runnable runnable = null;
try {
Class<?> newInstance = Class.forName(classPath);
runnable = (Runnable) springBeanUtils.getBean(newInstance);
} catch (ClassNotFoundException exception) {
LOGGER.error("getRunnableInstance 失败");
}
return runnable;
}
/**
* 触发器类型 : 定时触发 | 间隔时间触发
*
* @param type
* @param value
* @return
*/
static Trigger getTrigger(Integer type, String value) {
if (null == type) return new CronTrigger(value);
if (type == 1) return new CronTrigger(value);
if (type == 2) return new PeriodicTrigger(1);
return null;
}
}
- 任务类
1)任务1
package com.cron.test.task;
public class Task1Runnable implements Runnable{
@Override
public void run() {
// TODO 任务1需要执行的内容
}
}
2)任务2:
package com.cron.test.task;
public class Task2Runnable implements Runnable{
@Override
public void run() {
// TODO 任务2需要执行的内容
}
}
779

被折叠的 条评论
为什么被折叠?



