(一)定时任务方式介绍
日常项目开发中难免会用到定时任务,如果定时任务数量少,需求变更不频繁,代码耦合度低,那皆大欢喜。否则定时任务的维护也是让人头疼的事情,下边就介绍一下我了解到的几种实现定时任务的方式,技术没有强弱,根据实际情况选择,合适的才是最好的。
- JDK 的Timer类: 这是Java自带的java.util.Timer类,这个类允许你调度一个java.util.TimerTask任务。有两种方式,一种是使任务在指定时间被执行一次,另一种是从某一指定时间开始周期性地执行任务,一般用的较少。
- Quartz: Quartz是一个任务调度框架,简单理解就是:在某一个有规律的时间点干某件事。并且时间的触发的条件可以非常复杂,你给它一个触发条件的定义,它负责到指定时间点执行任务。
- SpringTask : SpringTask上手比较简单,实现有两种实现方式:一种基于xml配置,一种基于注解。这里主要介绍一下这种方式。
(二)SpringBoot实现定时任务
方式一:
搭建好SpringBoot项目,在启动类上添加
@EnableScheduling
注解,开启对定时任务的支持,然后编写定时任务就可以了。
编写定时任务类
/**
* @ClassName SyncAlarmSourceTask
* @description: 定时任务
* @author: MYY
* @create: 2021-11-11 11:11
* @Version 1.0
**/
@Component
public class TestTask{
/**
* 日志
*/
private Logger logger = LoggerFactory.getLogger(SyncAlarmSourceTask.class);
//这里表示5秒执行一次,对cron表达式不熟悉的可以去百度一下
@Scheduled(cron = "*/5 * * * * ?")
public void execute() {
logger.info("任务调度成功!");
}
}
这种方法虽然简单,但是cron表达式写死在代码之后不能动态改变运行周期,不利于修改维护。所以建议使用第二种方式,比较优雅的实现定时任务。
方式二:
(1)、创建数据库表
CREATE TABLE `tbl_scheduled_task` (
`task_id` varchar(32) NOT NULL COMMENT '主键id',
`task_classname` varchar(255) DEFAULT NULL COMMENT '定时任务完整类名',
`task_expression` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT 'cron表达式',
`task_explain` varchar(128) DEFAULT NULL COMMENT '任务描述',
`status` tinyint(1) DEFAULT NULL COMMENT '状态 0 正常 1 停用',
`isdel` tinyint(1) DEFAULT '0' COMMENT '状态 0 存在 1 已删除',
`createTime` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`updateTime` datetime DEFAULT NULL COMMENT '上次运行时间',
PRIMARY KEY (`task_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COMMENT='定时任务表';
INSERT INTO `tbl_scheduled_task`(`task_id`, `task_classname`, `task_expression`, `task_explain`, `status`, `isdel`, `createTime`, `updateTime`) VALUES ('23ba0825cd0e4a2aa9c305fbf63d929a', 'com.myy.task.TestTask', '*/5 * * * * ?', '测试', 0, 0, '2021-11-11 11:11:11', '2021-11-11 16:32:20');
(2)、编写Spring工具类
package com.myy.utils;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
/**
* @ClassName SpringUtil
* @description: 创建SpringUtils,自定义获取对象的具体方法实现
* @author: MYY
* @create: 2021-11-11 11:11
* @Version 1.0
**/
@Component
public class SpringUtil implements ApplicationContextAware {
private static ApplicationContext context;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
context = applicationContext;
}
public static void set(ApplicationContext applicationContext) {
context = applicationContext;
}
/**
* 通过字节码获取
* @param beanClass
* @param <T>
* @return
*/
public static <T> T getBean(Class<T> beanClass) {
return context.getBean(beanClass);
}
/**
* 通过BeanName获取
* @param beanName
* @param <T>
* @return
*/
public static <T> T getBean(String beanName) {
return (T) context.getBean(beanName);
}
/**
* 通过beanName和字节码获取
* @param name
* @param beanClass
* @param <T>
* @return
*/
public static <T> T getBean(String name, Class<T> beanClass) {
return context.getBean(name, beanClass);
}
}
在主启动类中将获取到的上下文对象添加到容器中,供SpringUtils使用
@EnableScheduling //springboot注解支持
@SpringBootApplication
public class RoadblockApplication extends SpringBootServletInitializer {
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(RoadblockApplication.class, args);
//添加到容器
SpringUtil.set(context);
}
}
(3)、自定义ScheduledTask接口,方便在页面灵活调用
package com.myy.enumUtils;
import lombok.Getter;
/**
* @ClassName StatusEnum
* @description: 状态枚举
* @author: MYY
* @create: 2021-11-11 11:11
* @Version 1.0
**/
@Getter
public enum StatusEnum {
ENABLE("开启",0),
DISABLE("关闭",1);
private String name;
private Integer code;
// 构造方法
private StatusEnum(String name, Integer code) {
this.name = name;
this.code = code;
}
}
package com.myy.scheduled;
import com.myy.entity.ScheduledCron;
import com.myy.enumUtils.StatusEnum;
import com.myy.service.ScheduledCronService;
import com.myy.utils.SpringUtil;
/**
* @ClassName ScheduledTask
* @description:
* @author: MYY
* @create: 2021-11-08 16:56
* @Version 1.0
**/
public interface ScheduledTask extends Runnable{
/**
* 定时任务
*/
void execute();
/**
* 实现控制定时任务启用或禁用的功能
*/
default void run(){
ScheduledCronService scheduledCronService = SpringUtil.getBean(ScheduledCronService.class);
ScheduledCron scheduledCron = repositor.findByTaskClassName(this.getClass().getName());
if(StatusEnum.DISABLE.getCode().equals(scheduledCron.getStatus()))
{
return ;
}
execute();
}
}
所有定时任务类只需要实现这个接口,并在相应的数据库表种插入一条记录,那么在项目启动的时候,就会被自动注册到Spring的定时任务里,然后根据cron表达式去执行。
(4)、编写配置类
package com.myy.scheduled;
import com.myy.entity.ScheduledCron;
import com.myy.service.ScheduledCronService;
import com.myy.utils.SpringUtil;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.SchedulingConfigurer;
import org.springframework.scheduling.config.ScheduledTaskRegistrar;
import org.springframework.scheduling.support.CronTrigger;
import org.springframework.util.Assert;
import java.util.List;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
/**
* @ClassName ScheduledConfig
* @description: 定时任务配置类
* @author: MYY
* @create: 2021-11-11 11:11
* @Version 1.0
**/
@Configuration
public class ScheduledConfig implements SchedulingConfigurer {
/**
*定时任务业务层接口
*/
@Autowired
private ScheduledCronService scheduledCronService;
@Override
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
for (ScheduledCron schedulecron: scheduledCronService.findAllScheduleTask())
{
Class<?> clazz;
Object task;
try {
//返回与具有给定字符串名称的类或接口关联的Class对象
clazz = Class.forName(schedulecron.getTaskClassName());
//返回与给定对象类型唯一匹配的 bean 实例(如果有)
task = SpringUtil.getBean(clazz);
}catch (ClassNotFoundException e){
throw new IllegalArgumentException("获取"+schedulecron.getTaskClassname()+"错误",e);
}catch (BeansException e){
throw new IllegalArgumentException(schedulecron.getTaskClassName()+"未交给Spring管理!",e);
}
//确定此Class对象表示的类或接口是否与指定的Class参数表示的类或接口相同,或者是其超类或超接口。
// 如果是,则返回true 否则返回false 。
// 如果此Class对象表示原始类型,则如果指定的Class参数正是此Class对象,则此方法返回true 否则返回false 。
Assert.isAssignable(ScheduledTask.class,task.getClass());
// 可以通过改变数据库数据进而实现动态改变执行周期
taskRegistrar.addTriggerTask(((Runnable) task),
triggerContext -> {
//获取定时触发器,这里可以每次从数据库获取最新记录,更新触发器,实现定时间隔的动态调整
String taskExpression= scheduledCronService.findByTaskClassName(schedulecron.getTaskClassName()).getTaskExpression();
return new CronTrigger(taskExpression).nextExecutionTime(triggerContext);
}
);
}
}
@Bean
public Executor taskExecutor() {
//创建一个线程池,可以安排命令在给定的延迟后运行,或定期执行
return Executors.newScheduledThreadPool(10);
}
}
(5)、编写定时任务
package com.myy.task;
import com.myy.scheduled.ScheduledTask;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
/**
* @ClassName TestTask
* @description: 测试定时任务
* @author: MYY
* @create: 2021-11-11 11:11
* @Version 1.0
**/
@Component
public class TestTask implements ScheduledTask {
/**
* 日志
*/
private Logger logger = LoggerFactory.getLogger(SyncAlarmSourceTask.class);
@Override
public void execute() {
logger.info("任务调度成功!");
}
}
(6)、启动服务
项目跑起来之后就会根据数据库配置的Cron表达式执行定时任务。
(7)、动态改变执行周期
做了个简单的页面,可以做到查看任务列表,修改任务cron表达式(也就实现了动态改变定时任务执行周期),暂停、启用定时任务、以及直接执行定时任务,这样后期正式环境维护开发就会很方便了。
/**
* 执行定时任务
*/
@ResponseBody
@PostMapping(value = "/runTaskCron")
public Result runTaskCron(String taskClassName){
if (StringUtils.isNotEmpty(taskClassName))
{
try {
((ScheduledTask)SpringUtil.getBean(Class.forName(taskClassName))).execute();
}catch (Exception e){
logger.error("执行定时任务失败",e);
}
}
return Result.ok("执行成功");
}
文章仅用作记录分享,若有不当,还望指正。