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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM