基于SpirngBoot实现动态定时任务
基于SpirngBoot实现动态定时任务
1.执行流程
一图胜千言

2.准备工作
1.建Springboot工程
2.引入依赖
这里只需要引入lombok和web依赖即可
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
3.代码实现
主启动
@SpringBootApplication
public class TimetaskApplication {
public static void main(String[] args) {
SpringApplication.run(TimetaskApplication.class, args);
}
}
1.SpringContextUtils
package com.llp.timetask.task;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
/**
* @ClassName SpringContextUtils
* @Description: spring获取bean工具类
* @Author llp
* @Date Created in 2020/12/17
**/
@Component
public class SpringContextUtils implements ApplicationContextAware {
private static ApplicationContext applicationContext = null;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
if (SpringContextUtils.applicationContext == null) {
SpringContextUtils.applicationContext = applicationContext;
}
}
//获取applicationContext
public static ApplicationContext getApplicationContext() {
return applicationContext;
}
//通过name获取 Bean.
public static Object getBean(String name) {
return getApplicationContext().getBean(name);
}
//通过class获取Bean.
public static <T> T getBean(Class<T> clazz) {
return getApplicationContext().getBean(clazz);
}
//通过name,以及Clazz返回指定的Bean
public static <T> T getBean(String name, Class<T> clazz) {
return getApplicationContext().getBean(name, clazz);
}
}
2.任务线程
package com.llp.timetask.task;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.ReflectionUtils;
import java.lang.reflect.Method;
import java.util.Objects;
/**
* @ClassName SendMsgTask
* @Description: 定时任务运行类
* @Author llp
* @Date Created in 2020/12/17
**/
@Slf4j
public class SchedulingRunnable implements Runnable {
private String beanName;
private String methodName;
private Object[] params;
public SchedulingRunnable(String beanName, String methodName) {
this(beanName, methodName, new Object[0]);
}
public SchedulingRunnable(String beanName, String methodName, Object ...params ) {
this.beanName = beanName;
this.methodName = methodName;
this.params = params;
}
@Override
public void run() {
log.info("定时任务开始执行 - bean:{},方法:{},参数:{}", beanName, methodName, params);
long startTime = System.currentTimeMillis();
try {
//获取定时任务Job类
Object target = SpringContextUtils.getBean(beanName);
Method method = null;
//判断定时任务是否携带参数
if (null != params && params.length > 0) {
//根据参数长度定义Class数组的长度
Class<?>[] paramCls = new Class[params.length];
for (int i = 0; i < params.length; i++) {
//获取每个参数的运行时类型,并赋值给Class数组的每个元素
paramCls[i] = params[i].getClass();
}
//target.getClass()获取Class对象,在通过Class对象获取带参Method对象
method = target.getClass().getDeclaredMethod(methodName, paramCls);
} else {
//获取不带参的Method对象
method = target.getClass().getDeclaredMethod(methodName);
}
/**
* 使给定的方法可访问,必要时显式设置它可访问。
* {@code setAccessible(true)}
* 方法仅在实际需要时调用,以避免与 JVM SecurityManager(如果处于活动状态)发生不必要的冲突。
* 可以简单理解,方法如果是非public修饰的,则进行爆破(关闭访问检查)
* method.setAccessible(true);
*/
// method.setAccessible(true);
ReflectionUtils.makeAccessible(method);
if (null != params && params.length > 0) {
//执行有参方法
method.invoke(target, params);
} else {
//执行无参方法
method.invoke(target);
}
} catch (Exception ex) {
log.error(String.format("定时任务执行异常 - bean:%s,方法:%s,参数:%s ", beanName, methodName, params), ex);
}
//计算定时任务执行耗时时间(单位:毫秒)
long times = System.currentTimeMillis() - startTime;
log.info("定时任务执行结束 - bean:{},方法:{},参数:{},耗时:{} 毫秒", beanName, methodName, params, times);
}
/**
* 重新equals方法
* 1.比较两个对象的引用地址是否相同
* 2.考虑通过反射获取对象的方式,Class类型应该为同一个对象
* 3.如果创建的是不同的对象则根据构造参数进行比较 beanName 和 methodName是必填的,因此对外不提供无参构造
* (1)如果params为空,则根据beanName,methodName来判断对象是否属于同一个对象
* (2)如果params不为空,则根据beanName,methodName,params来判断对象是否属于同一个对象
* @param o
* @return
*/
@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);
}
/**
* hash值的比较也是同理
* (1)如果params为空,则根据beanName,methodName来判断对象是否属于同一个对象
* (2)如果params不为空,则根据beanName,methodName,params来判断对象是否属于同一个对象
* @return
*/
@Override
public int hashCode() {
if (params == null) {
return Objects.hash(beanName, methodName);
}
return Objects.hash(beanName, methodName, params);
}
}
3.CronTaskRegistrar用于管理定时任务
package com.llp.timetask.task;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.scheduling.config.CronTask;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* @ClassName CronTaskRegistrar
* @Description: 添加定时任务注册类,用来增加、删除定时任务。
* @Author llp
* @Date Created in 2020/12/17
**/
@Component
public class CronTaskRegistrar implements DisposableBean {
/**
* 考虑后面可能会有多个区县同时添加定时任务的可能性
* 创建ConcurrentHashMap集合用于存放定时任务
*/
private final Map<Runnable, ScheduledTask> scheduledTasks = new ConcurrentHashMap<>(16);
@Resource
private TaskScheduler taskScheduler;
public TaskScheduler getScheduler() {
return this.taskScheduler;
}
/**
* 新增定时任务
* @param task
* @param cronExpression
*/
public void addCronTask(Runnable task, String cronExpression) {
addCronTask(new CronTask(task, cronExpression));
}
public void addCronTask(CronTask cronTask) {
if (cronTask != null) {
//获取任务线程类
Runnable task = cronTask.getRunnable();
//判断map集合中是否存在对应的key,如果存在则删除之前按的key,以保证最新的定时任务
if (this.scheduledTasks.containsKey(task)) {
removeCronTask(task);
}
this.scheduledTasks.put(task, scheduleCronTask(cronTask));
}
}
/**
* 移除定时任务
* @param task
*/
public void removeCronTask(Runnable task) {
ScheduledTask scheduledTask = this.scheduledTasks.remove(task);
if (scheduledTask != null) {
scheduledTask.cancel();
}
}
/**
*
* @param cronTask
* @return
*/
public ScheduledTask scheduleCronTask(CronTask cronTask) {
//定时任务控制类
ScheduledTask scheduledTask = new ScheduledTask();
/**
* @param1:任务线程类
* @param2:触发器
*/
//ScheduledFuture<?> schedule调度给定的 {@link Runnable},只要触发器指示下一个执行时间就调用它。
scheduledTask.future = this.taskScheduler.schedule(cronTask.getRunnable(), cronTask.getTrigger());
return scheduledTask;
}
/**
* 在销毁 bean 时由包含 {@code BeanFactory} 调用。
* @throws 出现关闭错误时的异常。异常将被记录但不会重新抛出以允许其他 bean 也释放它们的资源
*/
@Override
public void destroy() {
for (ScheduledTask task : this.scheduledTasks.values()) {
task.cancel();
}
this.scheduledTasks.clear();
}
}
4.定时任务控制类
package com.llp.timetask.task;
import java.util.concurrent.ScheduledFuture;
/**
* @ClassName ScheduledTask
* @Description: 定时任务控制类
* @Author llp
* @Date Created in 2020/12/17
**/
public final class ScheduledTask {
public volatile ScheduledFuture<?> future;
/**
* 取消定时任务
*/
public void cancel() {
ScheduledFuture<?> future = this.future;
if (future != null) {
//true会尝试去中断正在运行的定时任务
future.cancel(true);
}
}
}
5.线程池配置
package net.cqnews.fifth.task.sendtiming;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
/**
* @ClassName SchedulingConfig
* @Description: 定时任务配置类
* @Author llp
* @Date Created in 2020/12/17
**/
@Configuration
public class SchedulingConfig {
@Bean
public TaskScheduler taskScheduler() {
ThreadPoolTaskScheduler taskScheduler = new ThreadPoolTaskScheduler();
// 定时任务执行线程池核心线程数
taskScheduler.setPoolSize(4);
taskScheduler.setRemoveOnCancelPolicy(true);
taskScheduler.setThreadNamePrefix("TaskSchedulerThreadPool-");
return taskScheduler;
}
}
3.测试
1.Controller
@RestController
public class MyTestController {
@Resource
private MessageSendService messageSendService;
@GetMapping("/addTask")
public void addTask(@RequestParam(name = "content",required = true) String content){
messageSendService.sendMsg(content);
}
}
2.service
@Service
public class MessageSendService {
@Resource
private CronTaskRegistrar cronTaskRegistrar;
public void sendMsg(String content) {
//模拟发送时间,为当前时间的1分钟之后执行
LocalDateTime sendTime = LocalDateTime.now();
int second = sendTime.getSecond();
int minute = sendTime.getMinute()+1;
int hour = sendTime.getHour();
int dayOfMonth = sendTime.getDayOfMonth();
int monthValue = sendTime.getMonthValue();
//1.创建SchedulingRunnable线程类
SchedulingRunnable task = new SchedulingRunnable("SendMsgTask","sendTiming",content);
//2.添加任务
cronTaskRegistrar.addCronTask(task, second + " " + minute + " " + hour + " " + dayOfMonth + " " + monthValue + " ? ");
}
}
3.Job类
package com.llp.timetask.task;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
/**
* @ClassName SendMsgTask
* @Description: 定时发送任务
* @Author llp
* @Date Created in 2020/12/17
**/
@Slf4j
@Component("SendMsgTask")
@RequiredArgsConstructor
public class SendMsgTask {
/**
* 定时发送
*/
public void sendTiming(String content) {
//TODO 发送相关的业务
System.out.println("定时任务执行,发送内容: "+content);
}
}
测试结果
定时任务开始执行 - bean:SendMsgTask,方法:sendTiming,参数:[孙悟空]
定时任务执行,发送内容: 孙悟空
2022-03-09 16:31:20.016 INFO 17692 --- [lerThreadPool-1] c.llp.timetask.task.SchedulingRunnable : 定时任务执行结束 - bean:SendMsgTask,方法:sendTiming,参数:[孙悟空],耗时:1 毫秒