前提:在做業務平台的時候我們經常會遇到,某些跟時間打交道的需要修改狀態,比如說在時間區間之前,屬於未生效狀態,區間之內屬於有效期,區間之后,屬於過期,或者需要每天 每周 每月,甚至是年為單位的做一些固定的操作。通過定時任務可以通過開啟定時任務來完成這些需求。
我做合同管理模塊,合同有未生效,已生效,已過期,三個狀態,不可能每次用戶登錄的時候去判斷這個狀態,然后修改,這樣做會在登錄的邏輯里邊耦合了合同業務邏輯,同時消耗了登錄時間,不太可取。
下便是代碼:
依賴:

<?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>
主啟動類開啟定時任務注解
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); } }
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()); } }
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"; } }
其他的controller service dao類就省略了,后邊會給出github地址,有demo可執行
演示及說明
訪問/timetask/list可以看到所有的定時任務列表 可以進行啟動 停止 以及常規的增刪改查
特別說明 新增的時候類名必須是全路徑類名,因為是通過Class.forName("com.xc.timetask········") 反射來加載要執行的類
表達式 是cron表達式 就是定時任務的執行單位 不知道的可以 戳這里 去看看
方法 是要執行的方法的名字
新增和修改頁面 方法哪里填寫 run 就可以 和 TaskDemo 里的那個方法對應
這里忘記加上時間區間的datepicker日歷控件 來進行開始時間 結束時間填充, 直接去數據庫表里修改開始 結束時間 輕點噴我!!!!!
另外demo使用了LayUI 真的是慘不忍睹 彈出層會重復 -!- 不過后台代碼都是好用的
結果 因為上邊圖中列表 只有一個定時任務開啟的
需要demo 源碼的 請戳這里
SysTaskController