SpringBoot系列之动态生成cron表达式执行定时程序

2022-11-06 09:38:49 浏览数 (1)

业务场景

最近需要实现一个功能,根据页面选择的星期,默认是凌晨执行,生成cron表达式,然后定时执行定时程序

环境准备

  • 开发环境
    • JDK 1.8
    • SpringBoot2.2.1
    • Maven 3.2
  • 开发工具
    • IntelliJ IDEA
    • smartGit
    • Navicat15

在IDEA里集成https://start.aliyun.com,创建一个Spring Initializr项目:

选择jdk版本,和maven打包方式,选择需要的dependencies

实现方案

可以分两步实现:

  1. 先根据选择的星期生成cron表达式,保存到数据库里
  2. 根据保存的cron表达式规则执行定时程序

生成cron表达式的可以写一些工具类,网上教程比较多,可以参考网上教程:Java生成cron表达式工具类

生成cron表达式之后,保存到数据库里即可

有了动态配置的cron表达之后,就可以实现定时程序了,可以根据模板方法设计模式,写一个抽象的类,封装一些通用的方法,给子类实现业务

代码语言:javascript复制
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();

}

子类实现抽象类,然后实现方法:

代码语言:javascript复制
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

代码语言:javascript复制
<dependency>
     <groupId>org.redisson</groupId>
     <artifactId>redisson-spring-boot-starter</artifactId>
     <version>3.16.3</version>
 </dependency>

application.yml配置Redis

代码语言:javascript复制
spring:
  redis:
    host: 127.0.0.1
    port: 6379
    password: 123456
    database: 8

修改一下抽象类:

代码语言:javascript复制
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();

}

子类可以根据需要选择锁,可以是单机锁或者分布式锁

代码语言:javascript复制
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());
    }
}

归纳总结

项目里合理使用设计模式可以提高代码复用行,代码拓展行好,看起来比较简洁

0 人点赞