//小人

Spring Schedule定时任务进阶篇(调度器)

Spring Schedule背后支持多种任务调度方案,如JDK Timer、concurrent包下的ScheduledExecutorService以及Quartz等。Spring通过封装这些底层实现,为开发者提供了统一的接口和配置方式来处理定时任务。

接下来通过SpringBoot+数据库来实现根据数据库数据来动态管理我们的定时任务,我这里使用的是ORACLE数据库

  1. 配置Spring Boot项目与Oracle或其他数据库连接,以便从数据库中读取定时任务信息。

       <!--oracle 依赖-->
            <dependency>
                <groupId>com.oracle.ojdbc</groupId>
                <artifactId>ojdbc8</artifactId>
                <scope>runtime</scope>
            </dependency>

     

  2. 在数据库中创建一个表来存储定时任务,包括但不限于以下列
    id(主键)
    
    bean_name(类名)
    
    method_name(方法名)
    
    cron_expression(定时表达式)
    
    status(状态)
    

      

  3. 创建一个TaskDefinition实体类来映射数据库表结构,例如:
    public class TQuzetJob{
        private Long id;
        private String beanName;
        private String methodName;
        private String cronExpression;
        private String jobStatus;
        // 构造函数、getter/setter等
    }
    

      

  4. 创建一个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("执行完毕");
        }
     }

     

  5. 创建一个定时任务执行类
    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);
        }
    }

     

  6. 通过上下文工具类获取数据库配置的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);
        }
    
    }

     

  7. 上面这些都配置好后我们写一个测试用例
    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框架):

  8. 从调度器中移除已经添加的定时任务:移除定时任务我们需要用到添加到调度器中得到的返回值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("执行完毕");
        }

    调用此方法后就可以发现定时任务已经停止

     

posted @ 2024-03-16 11:12  H_Q  阅读(128)  评论(0编辑  收藏  举报