2.1.12 定时任务

Spring 框架自带任务调度功能,好比一个轻量级的Quartz,使用简单、方便,不需要依赖其他JAR包。

只需要在项目主程序启动类上添加@EnableScheduling开启任务调度功能即可

@SpringBootApplication
@EnableScheduling
public class LearnApplication {
    public static void main(String[] args) {
        SpringApplication.run(LearnApplication.class, args);
    }
}
2.1.12.1 简单定时任务
@Component
public class TestTask {

    @Scheduled(cron = "0/10 * * * * *")
    public void testTask1() {
        System.out.println("【任务一】测试定时任务" + LocalDateTime.now());
    }

}

如上述,配置一个简单的定时任务只需要在调度方法上添加@Shceduled注解即可,就可以使用定时任务。

2.1.12.2 异步定时任务
@Component
// 开启异步支持
@EnableAsync
public class TestTask {

    @Scheduled(cron = "0/10 * * * * *")
    // 方法使用异步执行,每次任务创建一个线程执行任务
    @Async
    public void testTask1() {
        System.out.println("【任务一】测试定时任务" + LocalDateTime.now() + "   " + Thread.currentThread().getName());
        for (int i = 0; i < 20; i++) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            System.out.println("【任务一】休眠" + (i + 1) + "秒测试定时任务" + LocalDateTime.now() + "   " + Thread.currentThread().getName());
        }

    }

}

往往在我们的项目调度任务中,有的场景是需要在当前任务还没有执行完毕时,就需要执行下一个定时调度任务,在这种情况下需要使用异步的方式来执行定时任务。

@EnableAsync开启异步支持

@Async标记任务使用异步执行(下次任务将在下一个配置时间开始,不等待当前任务执行完毕)

2.1.12.3 动态定时任务

当我们编写定时任务是,流程大致为:编码->配置执行周期->启动服务。

当前我们配置的执行周期是每天早上8点执行,当我们有天,需求变更,需要每天晚上8点执行,我们的操作流程为:修改执行周期->新版打包->停服->启动新版服务。整个流程线步骤多,存在不可控因素。

那么我们怎么做到不停服更新我们的执行周期呢??

那么下面我们模拟将cron表达式存储在MySQL。

1)定义cron相关service

// 表达式相关接口
public interface SwitchService {

    /**
     * 获取最新 cron 表达式
     *
     * @param taskId 任务ID
     * @return 最新 cron表达式
     */
    String getCron(String taskId);

    /**
     * 修改 cron 表达式
     */
    void modify();

}
// 表达式相关接口实现
@Service
public class SwitchServiceImpl implements SwitchService {

    private static String DB_CRON = "";

    @Override
    public String getCron(String taskId) {
        System.out.println("执行数据库查询 DB_CRON " + LocalDateTime.now());
        return DB_CRON;
    }

    @Override
    public void modify() {
        DB_CRON = "0/20 * * * * *";
        System.out.println("修改数据库中 DB_CRON " + LocalDateTime.now());
    }
}

此处模拟修改以及查询

2)创建具体任务执行

@Component
public class DynamicCronTask implements SchedulingConfigurer {
    // 模拟当前任务ID
    private String TASK_ID = "5001";

    @Autowired
    private SwitchService switchService;

    private String SpringDynamicCronTask() {
        // 默认为 每5秒执行
        String cron = "0/5 * * * * ?";
        //从数据库获得配置的corn表达式
        String dbCron = switchService.getCron(TASK_ID);
        // 当查询为空时,使用默认的表达式
        if (StringUtils.isNotBlank(dbCron)) {
            return dbCron;
        }
        return cron;
    }

    @Override
    public void configureTasks(ScheduledTaskRegistrar scheduledTaskRegistrar) {
        scheduledTaskRegistrar.addTriggerTask(new Runnable() {
            @Override
            public void run() {
                // 任务逻辑
                System.out.println("执行任务逻辑...." + LocalDateTime.now());
            }
        }, new Trigger() {
            @Override
            public Date nextExecutionTime(TriggerContext triggerContext) {
                String s = SpringDynamicCronTask();
                // 任务触发,可修改任务的执行周期
                CronTrigger trigger = new CronTrigger(s);
                Date nextExec = trigger.nextExecutionTime(triggerContext);
                return nextExec;
            }
        });
    }

}

3)启动服务

查看执行日志

执行数据库查询 DB_CRON 2020-06-09T10:33:30.001
执行任务逻辑....2020-06-09T10:33:35.002
执行数据库查询 DB_CRON 2020-06-09T10:33:35.002
执行任务逻辑....2020-06-09T10:33:40.001
执行数据库查询 DB_CRON 2020-06-09T10:33:40.001

修改数据库中 DB_CRON 2020-06-09T10:33:42.085

执行任务逻辑....2020-06-09T10:33:45
执行数据库查询 DB_CRON 2020-06-09T10:33:45

执行任务逻辑....2020-06-09T10:34:00.001
执行数据库查询 DB_CRON 2020-06-09T10:34:00.001
执行任务逻辑....2020-06-09T10:34:20.002
执行数据库查询 DB_CRON 2020-06-09T10:34:20.002

通过日志可以看出,在应用启动时,会首先从数据库中查询配置的执行周期,然后执行定时任务,执行完毕后会再次查询执行周期,下一个执行时间结束后就会按照修改的执行时间执行。

生效时间为下一个执行时间结束后,做不到立即生效!!!