Spring的SchedulingConfigurer实现定时任务

前提:在做业务平台的时候我们经常会遇到,某些跟时间打交道的需要修改状态,比如说在时间区间之前,属于未生效状态,区间之内属于有效期,区间之后,属于过期,或者需要每天 每周 每月,甚至是年为单位的做一些固定的操作。通过定时任务可以通过开启定时任务来完成这些需求。

我做合同管理模块,合同有未生效,已生效,已过期,三个状态,不可能每次用户登录的时候去判断这个状态,然后修改,这样做会在登录的逻辑里边耦合了合同业务逻辑,同时消耗了登录时间,不太可取。

下便是代码:

依赖:

<?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.3.1.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.xc</groupId>
    <artifactId>timetask</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>timetask</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-thymeleaf</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.0.0</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>commons-lang</groupId>
            <artifactId>commons-lang</artifactId>
            <version>2.6</version>
        </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>
    </dependencies>

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

</project>
pom.xml

主启动类开启定时任务注解

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.web.bind.annotation.RestController;

@SpringBootApplication
@RestController
@EnableScheduling
public class TimetaskApplication {

    public static void main(String[] args) {
        SpringApplication.run(TimetaskApplication.class, args);
    }

}

SpringUtil工具类:需要ApplicationContextAware接口

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

@Component
public class SpringUtil implements ApplicationContextAware {

    private Logger logger= LoggerFactory.getLogger(SpringUtil.class);

    private static ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        if(SpringUtil.applicationContext == null) {
            SpringUtil.applicationContext = applicationContext;
        }
        logger.info("========ApplicationContext配置成功,在普通类可以通过调用SpringUtils.getAppContext()获取applicationContext对象,applicationContext="+SpringUtil.applicationContext+"========");
    }

    //获取applicationContext
    public static ApplicationContext getApplicationContext() {
        return applicationContext;
    }

    //通过name获取 Bean.
    public  static Object getBean(String name){
        Class cla = null;
        try {
            cla=Class.forName(name);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        return getApplicationContext().getBean(cla);
    }

    //通过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);
    }



}
SpringUtil.java

SysTaskController实现SchedulingConfigurer接口,配置定时任务以及开启定时任务

import com.xc.timetask.entity.Task;
import com.xc.timetask.service.TaskService;
import com.xc.timetask.util.SpringUtil;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Lazy;
import org.springframework.scheduling.Trigger;
import org.springframework.scheduling.TriggerContext;
import org.springframework.scheduling.annotation.SchedulingConfigurer;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.scheduling.config.ScheduledTaskRegistrar;
import org.springframework.scheduling.support.CronTrigger;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ScheduledFuture;

@Lazy(value = false)
@Component
public class SysTaskController implements SchedulingConfigurer {

    protected static Logger logger = LoggerFactory.getLogger(SysTaskController.class);

    private SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

    private static ThreadPoolTaskScheduler threadPoolTaskScheduler = new ThreadPoolTaskScheduler();

    private static Map<String,ScheduledFuture<?>> scheduledFutureMap = new HashMap<>();

    @Resource
    private TaskService taskService;

    //从数据库里取得所有要执行的定时任务
    private List<Task> getAllTasks() throws Exception {
        List<Task> list=taskService.selectyunx();
        return list;
    }
    static {
        threadPoolTaskScheduler.initialize();
    }
    @Override
    public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
        List<Task> tasks= null;
        try {
            tasks = getAllTasks();
        } catch (Exception e) {
            e.printStackTrace();
        }
        logger.info("定时任务启动,预计启动任务数量="+tasks.size()+"; time="+sdf.format(new Date()));

        //校验数据(这个步骤主要是为了打印日志,可以省略)
        checkDataList(tasks);

        //通过校验的数据执行定时任务
        int count = 0;
        if(tasks.size()>0) {
            for (Task task:tasks) {
                try {
                    //taskRegistrar.addTriggerTask(getRunnable(task), getTrigger(task));
                    start(task);
                    count++;
                } catch (Exception e) {
                    logger.error("定时任务启动错误:" + task.getBean_name() + ";" + task.getMethod_name() + ";" + e.getMessage());
                }
            }
        }
        logger.info("定时任务实际启动数量="+count+"; time="+sdf.format(new Date()));
    }
    private static Runnable getRunnable(Task task){
        return new Runnable() {
            @Override
            public void run() {
                try {
                    Object obj = SpringUtil.getBean(task.getBean_name());
                    Method method = obj.getClass().getMethod(task.getMethod_name());
                    method.invoke(obj);
                } catch (InvocationTargetException e) {
                    logger.error("定时任务启动错误,反射异常:"+task.getBean_name()+";"+task.getMethod_name()+";"+ e.getMessage());
                } catch (Exception e) {
                    logger.error(e.getMessage());
                }
            }
        };
    }


    private static Trigger getTrigger(Task task){
        return new Trigger() {
            @Override
            public Date nextExecutionTime(TriggerContext triggerContext) {
                //将Cron 0/1 * * * * ? 输入取得下一次执行的时间
                CronTrigger trigger = new CronTrigger(task.getCron());
                Date nextExec = trigger.nextExecutionTime(triggerContext);
                return nextExec;
            }
        };

    }

    private List<Task> checkDataList(List<Task> list) {
        String errMsg="";
        for(int i=0;i<list.size();i++){
            if(!checkOneData(list.get(i)).equalsIgnoreCase("success")){
                errMsg+=list.get(i).getName()+";";
                list.remove(list.get(i));
                i--;
            };
        }
        if(!StringUtils.isBlank(errMsg)){
            errMsg="未启动的任务:"+errMsg;
            logger.error(errMsg);
        }
        return list;
    }

    public static String checkOneData(Task task){
        String result="success";
        Class cal= null;
        try {
            cal = Class.forName(task.getBean_name());

            Object obj = SpringUtil.getBean(cal);
            Method method = obj.getClass().getMethod(task.getMethod_name());
            String cron=task.getCron();
            if(StringUtils.isBlank(cron)){
                result="定时任务启动错误,无cron:"+task.getName();
                logger.error(result);
            }
        } catch (ClassNotFoundException e) {
            result="定时任务启动错误,找不到类:"+task.getBean_name()+ e.getMessage();
            logger.error(result);
        } catch (NoSuchMethodException e) {
            result="定时任务启动错误,找不到方法,方法必须是public:"+task.getBean_name()+";"+task.getMethod_name()+";"+ e.getMessage();
            logger.error(result);
        } catch (Exception e) {
            logger.error(e.getMessage());
        }
        return result;
    }
    /**
     * 启动定时任务
     * @param task
     * @param
     */
    public static void start(Task task){

        ScheduledFuture<?> scheduledFuture = threadPoolTaskScheduler.schedule(getRunnable(task),getTrigger(task));
        scheduledFutureMap.put(task.getId(),scheduledFuture);
        logger.info("启动定时任务" + task.getId() );

    }

    /**
     * 取消定时任务
     * @param task
     */
    public static void cancel(Task task){

        ScheduledFuture<?> scheduledFuture = scheduledFutureMap.get(task.getId());

        if(scheduledFuture != null && !scheduledFuture.isCancelled()){
            scheduledFuture.cancel(Boolean.FALSE);
        }

        scheduledFutureMap.remove(task.getId());
        logger.info("取消定时任务" + task.getId() );

    }

    /**
     * 编辑
     * @param task
     * @param
     */
    public static void reset(Task task){
        logger.info("修改定时任务开始" + task.getId() );
        cancel(task);
        start(task);
        logger.info("修改定时任务结束" + task.getId());
    }

}
SysTaskController.java

TaskDemo:定时任务要操作的类

import org.springframework.stereotype.Component;

import java.text.SimpleDateFormat;
import java.util.Date;

@Component
public class TaskDemo {
    SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

    //注入需要的service
    //private ContractService contractService;

    public String run(){

        try {
            /*
            使用service做一些修改的业务操作
            获取所有的合同列表
            List<Contract> list = contractService.getAllContract();
            修改合同的状态
            contractService.updateContractList(list);

            下边用打印***来代替业务进行测试定时任务
             */
            System.out.println("*********");
            
            System.out.println("TaskContract is running"+sdf.format(new Date()));
        } catch (Exception e) {
            e.printStackTrace();
        }
        return "/main";
    }
}
TaskDemo.java

其他的controller service dao类就省略了,后边会给出github地址,有demo可执行

 

演示及说明 

访问/timetask/list可以看到所有的定时任务列表  可以进行启动  停止  以及常规的增删改查

 

 特别说明 新增的时候类名必须是全路径类名,因为是通过Class.forName("com.xc.timetask········") 反射来加载要执行的类

表达式    是cron表达式  就是定时任务的执行单位  不知道的可以 戳这里 去看看

方法  是要执行的方法的名字

 

新增和修改页面  方法哪里填写 run 就可以 和 TaskDemo 里的那个方法对应

 

 这里忘记加上时间区间的datepicker日历控件 来进行开始时间 结束时间填充, 直接去数据库表里修改开始 结束时间   轻点喷我!!!!!

 

另外demo使用了LayUI   真的是惨不忍睹  弹出层会重复 -!-  不过后台代码都是好用的

 

结果  因为上边图中列表  只有一个定时任务开启的

 

 

需要demo 源码的  请戳这里

 

 

 

 

 

 

 

 

 

SysTaskController
posted @ 2020-07-14 21:23  ジ绯色月下ぎ  阅读(8973)  评论(0编辑  收藏  举报