在开发过程中经常遇到需要定时处理的一些任务, 例如每天的消费提醒、打卡等等. 简单的操作时直接在代码中写一个周期循环的方法, 例如:工作日每天上午10点整提醒用户打卡,
但是这样做有个缺陷, 我们并没有办法去控制该任务调度的开启和关闭, 以下代码提供一种可控制的定时任务调度.
2.定义任务调度的提醒信息实体类org.springframework.boot spring-boot-starter-web org.springframework.boot spring-boot-starter org.projectlombok lombok true org.springframework.boot spring-boot-starter-test test com.alibaba fastjson 1.2.44
@EqualsAndHashCode(callSuper = true)
@Entity
@Table(name = "remind_info")
@TableName("remind_info")
@Data
@NoArgsConstructor
@Accessors(chain = true)
public class RemindInfoEntity extends BaseTenantEntity {
private static final long serialVersionUID = -8763550312275818392L;
@Column(columnDefinition = "varchar(64) COMMENT '提醒类型'")
private String remindType;
@Column(columnDefinition = "text COMMENT '提醒内容内容'")
private String remindContent;
@Column(columnDefinition = "varchar(100) COMMENT '时间表达式, 格式:0 * * * * ? (每分钟的00秒)'")
private String cron;
@Column(columnDefinition = "tinyint(0) COMMENT '启用状态,0-关闭,1-启动'")
private int status;
}
3.定义controller层接口
3.1 启动定时任务的接口
@Autowired
private RemindInfoService remindInfoService;
@Autowired
private TaskScheduledService raskScheduledService;
@RequestMapping("/start")
@Transactional
public String start(Long id) {
try {
int i = remindInfoService.updateStatusById(id, 1);
//当提醒任务信息存在时,再来创建定时任务
if(i != 0) {
raskScheduledService.start(id);
return "启动成功";
}else {
return "当前任务不存在";
}
} catch (Exception e) {
//主动回滚事务,因为在检查型异常中,事务回滚不生效
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
e.printStackTrace();
}
return "启动异常,请联系管理员进行处理";
}
3.2 关闭定时任务的接口
@RequestMapping("/stop")
@Transactional
public String stop(Long id) {
try {
int i = remindInfoService.updateStatusById(id, 0);
if(i != 0) {
raskScheduledService.stop(id);
return "停止成功";
}else {
return "当前任务不存在";
}
} catch (Exception e) {
//主动回滚事务,因为在检查型异常中,事务回滚不生效
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
e.printStackTrace();
}
return "停止失败,请联系管理员";
}
4. 构建service层
@Service public class RemindInfoService extends BaseServiceAdapter5. 编写定时任务实现类 5.1 编写上线文工具类{ @Autowired private RemindInfoMapper remindInfoMapper; public List getList(Map param) { LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); queryWrapper.eq(StringUtils.isNotBlank((String)param.get("remindType")),RemindInfoEntity::getRemindType,param.get("remindType")); queryWrapper.eq(StringUtils.isNotBlank(param.get("status")+""),RemindInfoEntity::getStatus,param.get("status")); return remindInfoMapper.selectList(queryWrapper); } public int updateStatusById(Long id ,Integer status) { RemindInfoEntity remindInfoEntity =getById(id); if (remindInfoEntity != null ) { remindInfoEntity.setStatus(status); return remindInfoMapper.updateById(remindInfoEntity); } return 0; } }
@Component
public class SpringContextUtils implements ApplicationContextAware {
private static ApplicationContext context;
private static ApplicationContext applicationContext = null;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
SpringContextUtils.applicationContext = applicationContext;
}
public static ApplicationContext getApplicationContext() {
return applicationContext;
}
@SuppressWarnings("unchecked")
public static T getBean(String name) {
return (T) applicationContext.getBean(name);
}
public static T getBean(Class requiredType) {
return applicationContext.getBean(requiredType);
}
}
5.2 定义一个线程类,用于实现定时任务具体执行的操作逻辑
@Getter
@Setter
public class TaskScheduledThread implements Runnable{
private static final Logger LOGGER = LoggerFactory.getLogger(TaskScheduledThread.class);
public static final RemindInfoService remindInfoService = SpringContextUtils.getBean(RemindInfoService.class);
private Long taskId;
private String taskContent;
private String taskTyle;
@Override
public void run() {
//判断当前线程对象的类型
//此处逻辑原来是提醒业务相关的,现在统一修改为控制台打印信息
switch (this.taskTyle) {
//根据不同的操作类型,实现不同的操作逻辑
case "类型1":
//执行相关逻辑1
LOGGER.info("当前定时任务为:{}任务 , 调度内容为:{}",this.taskTyle,this.taskContent);
break;
case "类型2":
//执行相关逻辑2
LOGGER.info("当前定时任务为:{}任务 , 调度内容为:{}",this.taskTyle,this.taskContent);
break;
default:
LOGGER.info("当前定时任务类型为异常:{},请联系管理员",this.taskTyle);
break;
}
LOGGER.info("remindInfoService对象地址为:{}", JSONArray.toJSON(remindInfoService.getById(this.taskId)));
}
}
5.3 实现动态创建定时任务
- 创建定时任务类(TaskScheduledService )
@Service
public class TaskScheduledService {
public static final String 定时锁="dingshi";
@Resource
private RedisUtil redisUtil;
private static final Logger LOGGER = LoggerFactory.getLogger(TaskScheduledService.class);
@Resource
private ThreadPoolTaskScheduler threadPoolTaskScheduler;
private Map scheduledFutureMap = new ConcurrentHashMap<>();
@Autowired
private RemindInfoService remindInfoService;
- 根据任务id 启动定时任务
public void start(Long id) {
LOGGER.info("准备启动任务:{}", id);
boolean lock = redisUtil.lock(定时锁, 10);
try {
if(lock) {
RemindInfoEntity remindInfo = remindInfoService.getById(id);
//校验是否已经启动
if (this.isStart(id)) {
LOGGER.info("当前任务已在启动列表,请不要重复启动!");
} else {
//启动任务
this.doStart(remindInfo);
}
}
}catch (Exception e) {
e.printStackTrace();
} finally {
// 释放锁
redisUtil.deleteLock(定时锁);
}
}
- 根据任务id 判断定时任务是否启动
public Boolean isStart(Long id) {
//首先检查scheduledFutureMap是否存在该任务,如果不存在,则确定当前任务并没有启动
if (scheduledFutureMap.containsKey(id)) {
//当该任务存在时,需要检查scheduledFuture对象是否被取消,如果为false,说明当前线程在启动,否则当前线程处于关闭状态
if (!scheduledFutureMap.get(id).isCancelled()) {
return true;
}
}
return false;
}
- 根据任务id 停止定时任务
public void stop(Long id) {
LOGGER.info("进入关闭定时任务 :{}", id);
//首先检查当前任务实例是否存在
if (scheduledFutureMap.containsKey(id)) {
boolean lock = redisUtil.lock(定时锁, 10);
try {
if (lock) {
//获取任务实例
ScheduledFuture scheduledFuture = scheduledFutureMap.get(id);
//关闭定时任务
scheduledFuture.cancel(true);
//避免内存泄露
scheduledFutureMap.remove(id);
LOGGER.info("任务{}已成功关闭", id);
}
}catch (Exception e) {
e.printStackTrace();
}finally {
// 释放锁
redisUtil.deleteLock(定时锁);
}
}else {
LOGGER.info("当前任务{}不存在,请重试!", id);
}
}
- 初始化任务调度程序,重新启动所有任务调度程序
public void init(ListremindInfoList){ LOGGER.info("定时任务开始初始化 ,总共:size={}个", remindInfoList.size()); //如果集合为空,则直接退出当前方法 if (CollectionUtils.isEmpty(remindInfoList)) { return; } //遍历所有提醒基础信息列表 for(RemindInfoEntity remindInfo : remindInfoList) { //将提醒信息的主键作为线程唯一标识 Long id = remindInfo.getId(); //首先检查当前定时任务是否已经启动,如果已经启动,则跳出当次循环,继续检查下一个定时任务 if (this.isStart(id)) { continue; } //启动定时任务 this.doStart(remindInfo); } }
- 启动定时任务
private void doStart(RemindInfoEntity remindInfo) {
TaskScheduledThread scheduledThread = new TaskScheduledThread();
scheduledThread.setTaskId(remindInfo.getId());
scheduledThread.setTaskTyle(remindInfo.getRemindType());
scheduledThread.setTaskContent(remindInfo.getRemindContent());
LOGGER.info("正在启动任务类型:{} ,内容:{},时间表达式:{}", remindInfo.getRemindType(), remindInfo.getRemindContent(),remindInfo.getCron());
ScheduledFuture> scheduledFuture = threadPoolTaskScheduler.schedule(scheduledThread,
triggerContext -> {
CronTrigger cronTrigger = new CronTrigger(remindInfo.getCron());
return cronTrigger.nextExecutionTime(triggerContext);
});
//将已经启动的定时任务实例放入scheduledFutureMap进行统一管理
scheduledFutureMap.put(remindInfo.getId(), scheduledFuture);
LOGGER.info("启动任务:{} 成功!",remindInfo.getId());
}
}
5.4 创建定时任务启动类。此处主要是重写ApplicationRunner接口的run方法,就可以做到在项目启动时自动创建与启动定时任务
//@Order(value = 1)//该注解用于标识当前作用范围的执行优先级别, 默认是最低优先级,值越小优先级越高
@Component
public class TaskScheduledRunner implements ApplicationRunner {
private static final Logger LOGGER = LoggerFactory.getLogger(TaskScheduledRunner.class);
@Autowired
private RemindInfoService remindInfoService;
@Autowired
private TaskScheduledService taskScheduledService;
@Override
public void run(ApplicationArguments applicationArguments) {
LOGGER.info("系统重启中,正在重新启动定时任务程序!");
//查询状态为“启动”的提醒任务列表
Map param = new HashMap<>();
param.put("status", 1);
//此处没有分页结构,一旦数据比较多时,有可能会造成内存溢出
List remindInfoList = remindInfoService.getList(param);
//初始化任务调度程序,重新启动所有任务调度程序
taskScheduledService.init(remindInfoList);
LOGGER.info("定时任务程序启动完成!");
}
}
运行截图



