Spring Schedule定时任务进阶篇(调度器)
Spring Schedule背后支持多种任务调度方案,如JDK Timer、concurrent包下的ScheduledExecutorService以及Quartz等。Spring通过封装这些底层实现,为开发者提供了统一的接口和配置方式来处理定时任务。
接下来通过SpringBoot+数据库来实现根据数据库数据来动态管理我们的定时任务,我这里使用的是ORACLE数据库
-
配置Spring Boot项目与Oracle或其他数据库连接,以便从数据库中读取定时任务信息。
<!--oracle 依赖--> <dependency> <groupId>com.oracle.ojdbc</groupId> <artifactId>ojdbc8</artifactId> <scope>runtime</scope> </dependency>
- 在数据库中创建一个表来存储定时任务,包括但不限于以下列
id(主键) bean_name(类名) method_name(方法名) cron_expression(定时表达式) status(状态)
- 创建一个TaskDefinition实体类来映射数据库表结构,例如:
public class TQuzetJob{ private Long id; private String beanName; private String methodName; private String cronExpression; private String jobStatus; // 构造函数、getter/setter等 }
- 创建一个TaskSchedulerService,用于从数据库中加载任务并注册到调度器中:
import org.springframework.scheduling.TaskScheduler; import org.springframework.scheduling.config.CronTask; @Service @Transactional public class DemoServiceImpl implements IDemoService { @Autowired private TQuzetJobMapper quzetJobMapper; @Autowired private ApplicationContext applicationContext; @Autowired private TaskScheduler taskScheduler; private final Map<String, ScheduledFuture<?>> scheduleMap = new ConcurrentHashMap<>(16); @Override public Result startTask() { //从数据库中获取需要添加到任务调度器的信息 QueryWrapper<TQuzetJob> queryWrapper = new QueryWrapper<>(); queryWrapper.eq("JOB_STATUS","0"); List<TQuzetJob> jobList = quzetJobMapper.selectList(queryWrapper); jobList.forEach(job -> { try { SchedulingRunnable task = new SchedulingRunnable(job.getBeanName(), job.getMethodName(), job.getMethodParams()); CronTask cronTask = new CronTask(task, job.getCronExpression()); //调度任务返回的ScheduledFuture对象,它是唯一可以取消调度任务的引用 ScheduledFuture<?> schedule = taskScheduler.schedule(cronTask.getRunnable(), cronTask.getTrigger()); scheduleMap.put(job.getId(),schedule); } catch (Exception e) { e.printStackTrace(); } }); return Result.ok("执行完毕"); } }
- 创建一个定时任务执行类
package com.config.quzet; import com.utils.SpringContextUtils; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.util.ReflectionUtils; import java.lang.reflect.Method; import java.util.Objects; 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() { logger.info("定时任务开始执行 - bean:{},方法:{},参数:{}", beanName, methodName, params); long startTime = System.currentTimeMillis(); try { Object target = SpringContextUtils.getBean(beanName); Method method = null; if (StringUtils.isNotEmpty(params)) { method = target.getClass().getDeclaredMethod(methodName, String.class); } else { method = target.getClass().getDeclaredMethod(methodName); } ReflectionUtils.makeAccessible(method); if (StringUtils.isNotEmpty(params)) { method.invoke(target, params); } else { method.invoke(target); } } catch (Exception ex) { ex.printStackTrace(); logger.error(String.format("定时任务执行异常 - bean:%s,方法:%s,参数:%s ", beanName, methodName, params), ex); } long times = System.currentTimeMillis() - startTime; logger.info("定时任务执行结束 - bean:{},方法:{},参数:{},耗时:{} 毫秒", beanName, methodName, params, times); } @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); } }
- 通过上下文工具类获取数据库配置的bean
package com.utils; import org.springframework.beans.BeansException; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.stereotype.Component; /** * spring上下文工具类,用于普通类调用springIOC中的对象 * */ @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; } } /** * @apiNote 获取applicationContext */ public static ApplicationContext getApplicationContext() { return applicationContext; } /** * @apiNote 通过name获取 Bean. */ public static Object getBean(String name) { return getApplicationContext().getBean(name); } /** * @apiNote 通过class获取Bean. */ public static <T> T getBean(Class<T> clazz) { return getApplicationContext().getBean(clazz); } /** * @apiNote 通过name, 以及Clazz返回指定的Bean */ public static <T> T getBean(String name, Class<T> clazz) { return getApplicationContext().getBean(name, clazz); } }
- 上面这些都配置好后我们写一个测试用例
package com.demo.controller; import com.commom.CommonConstant; import com.commom.HandleLog; import com.commom.Result; import com.demo.service.IDemoService; import com.modules.entity.vo.LoginVo; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RestController; @Component("Demo") @RestController @Api(tags="测试") public class DemoController { @Autowired private IDemoService service; @GetMapping(value ="/startTask") @ApiOperation(value ="测试从数据库读取启动定时任务") public Result startTask(){ return service.startTask(); } @ApiOperation(value ="定时任务") public void taskTest(){ System.out.println("执行定时任务"); } }
调用接口就发现数据库内的定时任务已经加载到了taskScheduler任务调度器中了,
在上面用到了一个非常关键的类
TaskScheduler
它是来自于 org.springframework.scheduling.concurrent 包的一个接口。这个接口定义了调度任务的功能,允许应用程序异步地安排任务在未来某个时间点执行。在Spring Boot应用中,若要使用
TaskScheduler
,通常需要导入如下的Maven依赖(如果使用的是Spring Boot,默认已经包含了此依赖,因为Spring Boot包含了完整的Spring框架): - 从调度器中移除已经添加的定时任务:移除定时任务我们需要用到添加到调度器中得到的返回值ScheduledFutuer<?>
@Override public Result stopTask(String id, Boolean b) { //从 ScheduledFuture<?> scheduledFuture = scheduleMap.get(id); // 检查 ScheduledFuture 是否存在并取消任务 if (scheduledFuture != null) { scheduledFuture.cancel(b); // 参数 false 表示不强制中断正在执行的任务 // 如果需要强制中断,则传入 true // scheduledFuture.cancel(true); // 从 scheduleMap 中移除该任务 scheduleMap.remove(id); } return Result.ok("执行完毕"); }
调用此方法后就可以发现定时任务已经停止