基于springboot实现定时任务的增删改启停

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了基于springboot实现定时任务的增删改启停相关的知识,希望对你有一定的参考价值。

该功能主要是基于 TaskScheduler 和  CronTask两个类来实现。

直接干代码 

1. 创建一个springboot 工程,依赖如下

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.4.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>qinfeng.zheng</groupId>
    <artifactId>job-demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>job-demo</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>1.3.2</version>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.47</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

 

2. 数据库配置

spring.datasource.driver-class-name = com.mysql.jdbc.Driver
spring.datasource.url= jdbc:mysql://120.79.34.98:3306/test?useUnicode=yes&characterEncoding=UTF-8&useSSL=false
spring.datasource.username = root
spring.datasource.password = 123456

 

3. 代码

 

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;

@Configuration
public class SchedulingConfig {
    @Bean
    public TaskScheduler taskScheduler() {
        ThreadPoolTaskScheduler taskScheduler = new ThreadPoolTaskScheduler();
        // 定时任务执行线程池核心线程数
        taskScheduler.setPoolSize(4);
        taskScheduler.setRemoveOnCancelPolicy(true);
        taskScheduler.setThreadNamePrefix("TaskSchedulerThreadPool-");
        return taskScheduler;
    }
}

 

import java.util.concurrent.ScheduledFuture;

public final class ScheduledTask {

    volatile ScheduledFuture future;

    /**
     * 取消定时任务
     */
    public void cancel() {
        ScheduledFuture future = this.future;
        if (future != null) {
            future.cancel(true);
        }
    }
}

 

import lombok.extern.slf4j.Slf4j;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;

import java.lang.reflect.Method;
import java.util.Objects;

/**
 * 通过反射的方式 调用真定需要执行的方法
 */
@Slf4j
public class SchedulingRunnable implements Runnable {
    private String beanName;
    private String methodName;
    private String params;

    public SchedulingRunnable(String beanName, String methodName, String 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 {
            Object target = SpringContextUtils.getBean(beanName);
            Method method = null;
            if (!StringUtils.isEmpty(params)) {
                method = target.getClass().getDeclaredMethod(methodName, String.class);
            } else {
                method = target.getClass().getDeclaredMethod(methodName);
            }

            ReflectionUtils.makeAccessible(method);
            if (!StringUtils.isEmpty(params)) {
                method.invoke(target, params);
            } else {
                method.invoke(target);
            }
        } catch (Exception ex) {
            log.error("定时任务执行异常 - bean:{},方法:{},参数:{} ", beanName, methodName, params, ex);
        }

        long times = System.currentTimeMillis() - startTime;
        log.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);
    }
}

 

import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;

@Component
public class SpringContextUtils implements ApplicationContextAware {

    private static ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        SpringContextUtils.applicationContext = applicationContext;
    }

    public static Object getBean(String name) {
        return applicationContext.getBean(name);
    }

}

 

定时任务注册类

import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.scheduling.config.CronTask;
import org.springframework.scheduling.config.ScheduledTaskRegistrar;
import org.springframework.stereotype.Component;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 可参考spring 提供的定时任务注册类 {@link ScheduledTaskRegistrar}
 */
@Component
public class CronTaskRegistrar implements DisposableBean {

    /**
     * 缓存
     */
    private final Map<Runnable, ScheduledTask> scheduledTasks = new ConcurrentHashMap<>(16);

    @Autowired
    private TaskScheduler taskScheduler;

    /**
     * 添加一个定时任务
     * 其核心就是靠spring提供的 CronTask 类来实现
     *
     * @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();
            if (this.scheduledTasks.containsKey(task)) {
                removeCronTask(task);
            }
            this.scheduledTasks.put(task, scheduleCronTask(cronTask));
        }
    }

    public void removeCronTask(Runnable task) {
        ScheduledTask scheduledTask = this.scheduledTasks.remove(task);
        if (scheduledTask != null)
            scheduledTask.cancel();
    }

    public ScheduledTask scheduleCronTask(CronTask cronTask) {
        ScheduledTask scheduledTask = new ScheduledTask();
        scheduledTask.future = this.taskScheduler.schedule(cronTask.getRunnable(), cronTask.getTrigger());
        return scheduledTask;
    }


    @Override
    public void destroy() {
        for (ScheduledTask task : this.scheduledTasks.values()) {
            task.cancel();
        }
        this.scheduledTasks.clear();
    }
}

 

下面是将job信息记录到数据库,以便于启动项目时,就可以运行定时任务

 

CREATE TABLE `sys_job` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `jobId` bigint(20) DEFAULT NULL,
  `beanName` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL,
  `methodParams` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL,
  `cronExpression` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL,
  `jobStatus` char(1) COLLATE utf8_unicode_ci DEFAULT NULL,
  `remark` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL,
  `createTime` date DEFAULT NULL,
  `updateTime` date DEFAULT NULL,
  `methodName` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=15 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;

 

import lombok.Data;

import java.util.Date;

@Data
public class SysJobPO {
    /**
     * 任务ID
     */
    private Integer jobId;
    /**
     * bean名称 , 如示例中的: demoTask
     */
    private String beanName;
    /**
     * 方法名称
     */
    private String methodName;
    /**
     * 方法参数
     */
    private String methodParams;
    /**
     * cron表达式
     */
    private String cronExpression;
    /**
     * 状态(1正常 0暂停)
     */
    private Integer jobStatus;
    /**
     * 备注
     */
    private String remark;
    /**
     * 创建时间
     */
    private Date createTime;
    /**
     * 更新时间
     */
    private Date updateTime;
}

 

import org.apache.ibatis.annotations.*;
import qinfeng.zheng.jobdemo.SysJobPO;

import java.util.List;

@Mapper
public interface SysJobMapper {
    @Insert("INSERT INTO sys_job(jobId,beanName,methodParams,cronExpression,jobStatus,remark,createTime,updateTime,methodName) values (#{jobId}," +
            "#{beanName},#{methodParams},#{cronExpression},#{jobStatus},#{remark},#{createTime},#{updateTime},#{methodName})")
    boolean addSysJob(SysJobPO sysJob);

    @Select("select * from sys_job where jobStatus =  #{jobStatus}")
    List<SysJobPO> getSysJobListByStatus(Integer jobStatus);

    @Select("select * from sys_job where jobId =  #{jobId}")
    SysJobPO findTaskJobByJobId(Integer jobId);

    @Delete("delete  from sys_job where jobId =  #{jobId}")
    boolean deleteTaskJobByJobId(Integer jobId);

    /**
     * 这儿只是修改corn表达式 和 状态。 测试使用
     *
     * @param sysJobPO
     * @return
     */
    @Update("update sys_job set cronExpression = #{cronExpression} , jobStatus =  #{jobStatus} where  jobId=  #{jobId}")
    boolean updateTaskJob(SysJobPO sysJobPO);
}

 

/**
 * job状态控制 
 */
public enum SysJobStatus {
    NORMAL("正常", 1), SUSPEND("暂停", 0);


    private String desc;
    private Integer index;

    private SysJobStatus(String desc, Integer index) {
        this.desc = desc;
        this.index = index;
    }

    public String desc() {
        return this.desc;
    }

    public Integer index() {
        return this.index;
    }
}

 

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import qinfeng.zheng.jobdemo.CronTaskRegistrar;
import qinfeng.zheng.jobdemo.SchedulingRunnable;
import qinfeng.zheng.jobdemo.SysJobPO;
import qinfeng.zheng.jobdemo.SysJobStatus;
import qinfeng.zheng.jobdemo.mapper.SysJobMapper;

import java.util.List;

@Service
public class SysJobRunner implements CommandLineRunner {

    private static final Logger logger = LoggerFactory.getLogger(SysJobRunner.class);

    @Autowired
    private SysJobMapper sysJobMapper;

    @Autowired
    private CronTaskRegistrar cronTaskRegistrar;

    @Override
    public void run(String... args) {
        // 初始加载数据库里状态为正常的定时任务
        List<SysJobPO> jobList = sysJobMapper.getSysJobListByStatus(SysJobStatus.NORMAL.index());
        if (!CollectionUtils.isEmpty(jobList)) {
            for (SysJobPO job : jobList) {
                SchedulingRunnable task = new SchedulingRunnable(job.getBeanName(), job.getMethodName(), job.getMethodParams());
                cronTaskRegistrar.addCronTask(task, job.getCronExpression());
            }
            logger.info("定时任务已加载完毕...");
        }
    }
}

 

定时job测试类

import org.springframework.stereotype.Component;

/**
 * 该类用于测试定时任务
 */
@Component("demoTask")
public class DemoTask {

    public void taskWithParams(String params) {
        System.out.println("执行有参示例任务:" + params);
    }

    public void taskNoParams() {
        System.out.println("执行无参示例任务");
    }
}

 

启动springboot项目时,就会通过 SysJobRunner  加载数据库表中job信息,从而启动定时任务。

 

下面再写一个controller控制器,实现与前端交互,从而实现定时job的增、删、改,切换

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import qinfeng.zheng.jobdemo.CronTaskRegistrar;
import qinfeng.zheng.jobdemo.SchedulingRunnable;
import qinfeng.zheng.jobdemo.SysJobPO;
import qinfeng.zheng.jobdemo.SysJobStatus;
import qinfeng.zheng.jobdemo.mapper.SysJobMapper;

@RestController
public class TaskController {
    @Autowired
    private SysJobMapper sysJobMapper;

    @Autowired
    private CronTaskRegistrar cronTaskRegistrar;

    @PostMapping("/addTask")
    public String addTask(SysJobPO sysJob) {
        boolean success = sysJobMapper.addSysJob(sysJob);
        if (!success)
            return "新增失败";
        else {
            if (sysJob.getJobStatus().equals(SysJobStatus.NORMAL.index())) {
                SchedulingRunnable task = new SchedulingRunnable(sysJob.getBeanName(), sysJob.getMethodName(), sysJob.getMethodParams());
                // 注册定时任务
                cronTaskRegistrar.addCronTask(task, sysJob.getCronExpression());
            }
        }
        return "SUCCESS";
    }

    @DeleteMapping("/deleteTask/{jobId}")
    public String deleteTask(@PathVariable Integer jobId) {
        SysJobPO existJob = sysJobMapper.findTaskJobByJobId(jobId);
        boolean success = sysJobMapper.deleteTaskJobByJobId(jobId);
        if (!success)
            return "删除失败";
        else {
            if (existJob.getJobStatus().equals(SysJobStatus.NORMAL.index())) {
                SchedulingRunnable task = new SchedulingRunnable(existJob.getBeanName(), existJob.getMethodName(), existJob.getMethodParams());
                // 删除定时任务
                cronTaskRegistrar.removeCronTask(task);
            }
        }
        return "SUCCESS";
    }


    /**
     * 修改定时任务
     *
     * @param sysJob
     * @return
     */
    @PostMapping("/updateTask")
    public String updateTaskJob(SysJobPO sysJob) {
        SysJobPO existJob = sysJobMapper.findTaskJobByJobId(sysJob.getJobId());
        boolean success = sysJobMapper.updateTaskJob(sysJob);
        if (!success)
            return "修改成功";
        else {
            // 1. 先删除原来的定时任务(Map缓存)
            if (existJob.getJobStatus().equals(SysJobStatus.NORMAL.index())) {
                SchedulingRunnable task = new SchedulingRunnable(existJob.getBeanName(), existJob.getMethodName(), existJob.getMethodParams());
                cronTaskRegistrar.removeCronTask(task);
            }
            //2. 新增定时任务(放到Map缓存中)
            if (sysJob.getJobStatus().equals(SysJobStatus.NORMAL.index())) {
                SchedulingRunnable task = new SchedulingRunnable(sysJob.getBeanName(), sysJob.getMethodName(), sysJob.getMethodParams());
                cronTaskRegistrar.addCronTask(task, sysJob.getCronExpression());
            }
        }
        return "SUCCESS";
    }

    /**
     * 启,停定时任务的状态切换
     */

    @GetMapping("/trigger/{jobId}")
    public String triggerTaskJob(@PathVariable Integer jobId) {
        SysJobPO existJob = sysJobMapper.findTaskJobByJobId(jobId);
        // 1.如果原先是启动状态 ,那么就停止吧(从Map缓存中删除, 并将表中状态置为0)
        if (existJob.getJobStatus().equals(SysJobStatus.NORMAL.index())) {
            SchedulingRunnable task = new SchedulingRunnable(existJob.getBeanName(), existJob.getMethodName(), existJob.getMethodParams());
            cronTaskRegistrar.removeCronTask(task);
            existJob.setJobStatus(0);
            sysJobMapper.updateTaskJob(existJob);
        } else {
            SchedulingRunnable task = new SchedulingRunnable(existJob.getBeanName(), existJob.getMethodName(), existJob.getMethodParams());
            cronTaskRegistrar.addCronTask(task, existJob.getCronExpression());
            existJob.setJobStatus(1);
            sysJobMapper.updateTaskJob(existJob);
        }
        return "SUCCESS";
    }
}

 

好,代码到此为此。

 

最后来测试一把,首先启动springboot项目

 

可以看到,项目一启动,就将数据库表中记录的一个定时任务启动了。

 

然后,我们调用addTask接口,新增一个定时job

 

 

可以看到,现在启动了两个job. 

 

数据库也有两条job数据, 下次启动项目时,两个job就会同时启动了。

+----+-------+----------+--------------+----------------+-----------+--------------------------+------------+------------+----------------+
| id | jobId | beanName | methodParams | cronExpression | jobStatus | remark                   | createTime | updateTime | methodName     |
+----+-------+----------+--------------+----------------+-----------+--------------------------+------------+------------+----------------+
|  7 |     1 | demoTask | NULL         | */5 * * * * ?  | 1         | 测试无参定时任务            | 2020-02-15 | 2020-02-15 | taskNoParams   |
| 15 |    10 | demoTask | 123456       | */10 * * * * ? | 1         | NULL                     | NULL       | NULL       | taskWithParams |
+----+-------+----------+--------------+----------------+-----------+--------------------------+------------+------------+----------------+

 

以上是关于基于springboot实现定时任务的增删改启停的主要内容,如果未能解决你的问题,请参考以下文章

实践:SpringBoot实现定时任务的动态增删启停

SpringBoot实现动态增删启停定时任务

SpringBoot实现动态增删启停定时任务

SpringBoot实现动态增删启停定时任务

SpringBoot实现动态增删启停定时任务

SpringBoot实现动态增删启停定时任务