前言
平时我们使用@Scheduled注解,每次都需要去修改代码。
现在有需求,需要通过在数据库配置cron表达式,以及控制是否开启关闭、
gitee地址:https://gitee.com/nothix/springboot-dynamic-task.git
github地址: https://github.com/shan165310175/springboot-dynamic-task
数据库表
create table dynamic_task_config
(
id int auto_increment
primary key,
name varchar(255) null comment '名称',
remark varchar(255) null comment '描述',
bean_name varchar(255) not null comment 'bean名称',
cron varchar(56) not null,
status int default 0 null comment '0 ok 1 禁用',
create_time datetime null,
update_time datetime null,
constraint dynamic_task_config_beanName_uindex
unique (bean_name)
);
pom.xml文件
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.6</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.9.2</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.9.2</version>
</dependency>
<dependency>
<groupId>com.thetransactioncompany</groupId>
<artifactId>cors-filter</artifactId>
<version>1.3.2</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.18</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.0.7.1</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>commons-lang</groupId>
<artifactId>commons-lang</artifactId>
<version>2.6</version>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.0.4.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<finalName>service-task</finalName>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
动态定时任务接口
DyTask接口,所有定时任务类,实现该接口
/**
* 动态任务
*/
public interface DyTask {
void run();
}
测试定时任务类
@RegisterTask是自定义类,通过该注解,启动时候可以自动把配置信息存入数据库。
@Component
@Slf4j
public class TestDyTask implements DyTask {
/**
* 启动后自动将信息注入到数据库
*/
@RegisterTask(enable = true, override = false, name = "动态任务1 - 测试", remark = "这是备注", cron = "0 */5 * * * ?", status = 0)
@Override
public void run() {
log.info("TestDyTask 测试任务。。。" + LocalDateTime.now());
}
}
定时任务操作接口
public interface DyTaskManager {
/**
* 直接执行定时任务
*/
boolean runTask(String beanName);
/**
* 开始定时任务
*
* @param beanName
* @param cron
*
* @return
*/
boolean scheduleTask(String beanName, String cron);
/**
* 取消定时任务
*
* @param beanName
*
* @return
*/
boolean cancelTask(String beanName);
}
操作类接口实现
@Service
@Slf4j
public class DefaultDyTaskManager implements DyTaskManager, CommandLineRunner, ApplicationContextAware {
private ApplicationContext context;
Map<String, DyTaskInfo> taskMap = new ConcurrentHashMap<>();
@Autowired
private ThreadPoolTaskScheduler taskScheduler;
@Autowired
private IDynamicTaskConfigService dynamicTaskConfigService;
@Override
public boolean cancelTask(String beanName) {
if (StringUtils.isBlank(beanName)) {
return false;
}
DyTaskInfo task = taskMap.remove(beanName);
if (task != null && task.getFuture() != null) {
task.getFuture().cancel(true);
return true;
}
return false;
}
@Override
public boolean runTask(String beanName) {
DyTask target = null;
try {
target = (DyTask) context.getBean(beanName);
}
catch (Throwable e) {
throw new ServiceException("无法获取bean信息, 无法执行任务:" + e.getMessage());
}
// 执行
target.run();
return true;
}
@Override
public boolean scheduleTask(String beanName, String cron) {
DyTask target = null;
try {
target = (DyTask) context.getBean(beanName);
}
catch (Throwable e) {
throw new ServiceException("无法获取bean信息, 无法执行任务:" + e.getMessage());
}
Method method = null;
try {
method = target.getClass().getMethod("run");
}
catch (NoSuchMethodException e) {
throw new ServiceException("无法获取run方法, 无法执行任务:" + e.getMessage());
}
try {
CronTask cronTask = new CronTask(createRunnable(target, method),
new CronTrigger(cron, TimeZone.getTimeZone(ZoneId.systemDefault())));
DyTaskInfo taskInfo = new DyTaskInfo(cronTask);
ScheduledFuture<?> future = taskScheduler.schedule(cronTask.getRunnable(), cronTask.getTrigger());
taskInfo.setFuture(future);
cancelTask(beanName);
taskMap.put(beanName, taskInfo);
return true;
}
catch (Throwable e) {
log.info("配置异常:{}", e.getMessage());
}
return true;
}
private Runnable createRunnable(Object target, Method method) {
Method invocableMethod = AopUtils.selectInvocableMethod(method, target.getClass());
return new ScheduledMethodRunnable(target, invocableMethod);
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
context = applicationContext;
}
@Override
public void run(String... args) throws Exception {
List<DynamicTaskConfig> taskConfigs = dynamicTaskConfigService.findAllByStatus(0);
taskConfigs.forEach(config -> {
if (StringUtils.isNotBlank(config.getBeanName()) && StringUtils.isNotBlank(config.getCron())) {
log.info(">>> 初始化定时任务: {},{},{}", config.getName(), config.getBeanName(), config.getCron());
try {
scheduleTask(config.getBeanName(), config.getCron());
}
catch (Throwable e) {
log.error("初始化失败: {}", e.getMessage());
}
}
});
}
}