业务场景
最近需要实现一个功能,根据页面选择的星期,默认是凌晨执行,生成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());
}
}
归纳总结
项目里合理使用设计模式可以提高代码复用行,代码拓展行好,看起来比较简洁