前提:在做业务平台的时候我们经常会遇到,某些跟时间打交道的需要修改状态,比如说在时间区间之前,属于未生效状态,区间之内属于有效期,区间之后,属于过期,或者需要每天 每周 每月,甚至是年为单位的做一些固定的操作。通过定时任务可以通过开启定时任务来完成这些需求。
我做合同管理模块,合同有未生效,已生效,已过期,三个状态,不可能每次用户登录的时候去判断这个状态,然后修改,这样做会在登录的逻辑里边耦合了合同业务逻辑,同时消耗了登录时间,不太可取。
下便是代码:
依赖:
<?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