SpringBoot系列之动态定时程序
Posted smileNicky
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了SpringBoot系列之动态定时程序相关的知识,希望对你有一定的参考价值。
业务场景
最近需要实现一个功能,根据页面选择的星期,默认是凌晨执行,生成cron表达式,然后定时执行定时程序
环境准备
-
开发环境
- JDK 1.8
- SpringBoot2.2.1
- Maven 3.2+
-
开发工具
- IntelliJ IDEA
- smartGit
- Navicat15
在IDEA里集成阿里的https://start.aliyun.com
,创建一个Spring Initializr
项目:
选择jdk版本,和maven打包方式,选择需要的dependencies
实现方案
可以分两步实现:
- 先根据选择的星期生成cron表达式,保存到数据库里
- 根据保存的cron表达式规则执行定时程序
生成cron表达式的可以写一些工具类,网上教程比较多,可以参考网上教程:Java生成cron表达式工具类
生成cron表达式之后,保存到数据库里即可
有了动态配置的cron表达之后,就可以实现定时程序了,可以根据模板方法设计模式,写一个抽象的类,封装一些通用的方法,给子类实现业务
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.SchedulingConfigurer;
import org.springframework.scheduling.config.ScheduledTaskRegistrar;
import org.springframework.scheduling.support.CronTrigger;
@Slf4j
public abstract class AbstractScheduler implements SchedulingConfigurer
@Override
public void configureTasks(ScheduledTaskRegistrar registrar)
registrar.addTriggerTask(()->
// 执行业务
doBusiness();
, triggerContext ->
String cron = this.getCronString();
CronTrigger trigger;
try
trigger = new CronTrigger(cron);
return trigger.nextExecutionTime(triggerContext);
catch (Exception e)
log.error("cron表达式异常,已经启用默认配置");
// 配置cron表达式异常,执行默认的表达式
trigger = new CronTrigger(getDefaultCron());
return trigger.nextExecutionTime(triggerContext);
);
// 获取cron表达式方法,抽象方法,给子类实现
protected abstract String getCronString();
// 执行业务操作
protected abstract void doBusiness();
// cron表达式报错获取默认的cron表达式
protected abstract String getDefaultCron();
子类实现抽象类,然后实现方法:
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
@Service
@Slf4j
public class ItemSyncScheduler extends AbstractScheduler
// 默认的cron表达式
@Value("$configtask.default.itemsync")
private String defaultCron ;
@Override
protected String getCronString()
// 获取数据库里的cron表达式
String cronString = "0 0/1 * * * ?";
return cronString ;
@Override
protected void doBusiness()
// 执行业务操作
@Override
protected String getDefaultCron()
return defaultCron;
看起来是没多大问题,不过在定时程序,分布式环境,可能会出现重复执行业务的情况,所以需要加上分布式锁,可以直接使用redission的分布式锁
加上redisson-spring-boot-starter
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
<version>3.16.3</version>
</dependency>
application.yml配置Redis
spring:
redis:
host: 127.0.0.1
port: 6379
password: 123456
database: 8
修改一下抽象类:
import cn.hutool.core.thread.NamedThreadFactory;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.scheduling.annotation.SchedulingConfigurer;
import org.springframework.scheduling.config.ScheduledTaskRegistrar;
import org.springframework.scheduling.support.CronTrigger;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.locks.Lock;
@Slf4j
public abstract class AbstractScheduler implements SchedulingConfigurer, InitializingBean
private Lock rlock;
@Override
public void afterPropertiesSet()
rlock = getLock();
@Override
public void configureTasks(ScheduledTaskRegistrar registrar)
registrar.addTriggerTask(()->
try
// 加分布式锁
rlock.lock();
// 执行业务
doBusiness();
finally
// 释放锁
rlock.unlock();
, triggerContext ->
String cron = this.getCronString();
CronTrigger trigger;
try
trigger = new CronTrigger(cron);
return trigger.nextExecutionTime(triggerContext);
catch (Exception e)
log.error("cron表达式异常,已经启用默认配置");
// 配置cron表达式异常,执行默认的表达式
trigger = new CronTrigger(getDefaultCron());
return trigger.nextExecutionTime(triggerContext);
);
protected abstract String getCronString();
protected abstract void doBusiness();
protected abstract String getDefaultCron();
protected abstract Lock getLock();
子类可以根据需要选择锁,可以是单机锁或者分布式锁
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
@Service
@Slf4j
public class ItemSyncScheduler extends AbstractScheduler
@Autowired
private RedissonClient redissonClient;
// 默认的cron表达式
@Value("$configtask.default.itemsync")
private String defaultCron ;
@Override
protected String getCronString()
// 获取数据库里的cron表达式
String cronString = "0 0/1 * * * ?";
return cronString ;
@Override
protected void doBusiness()
// 执行业务操作
@Override
protected String getDefaultCron()
return defaultCron;
@Override
protected Lock getLock()
return redissonClient.getLock(this.getClass().getSimpleName());
ps:这个定时程序在下次执行的时候会加载新的时间,所以如果改了定时程序不能马上触发,所以可以进行改进,比如可以使用canal等中间件监听数据表,如果改了就再设置cron表达式,然后触发程序,或者在保存定时程序的时候,调一下重新加载
基于这版定时程序,改进版可以参考博客,这版定时程序,是在点击保存定时程序时候触发定时程序重新加载
归纳总结
项目里合理使用设计模式可以提高代码复用行,代码拓展行好,看起来比较简洁
以上是关于SpringBoot系列之动态定时程序的主要内容,如果未能解决你的问题,请参考以下文章