1. scheduled 方案
- 创建 spring boot 工程
- 在需要定时执行的类上添加注解 @Component
- 在类中定义方法, 编写定时业务逻辑, 并在方法上定义注解 @Scheduled , 并配置定时执行的周期
在 spring boot 引导类添加注解 @EnableScheduling
@Componentpublic class SchedulerTest{@Scheduled(fixedRate=1000)public void showTime(){System.out.println(new Date().toLocaleString());}}
@Scheduled 介绍
fixedRate 按照指定周期执行, 单位毫秒
- @Scheduled(fixedRate=1000)
- fixedRateString 和 fixedRate 类似, 只是是一个字符串, 时间也是毫秒值
- fixedDelay 固定延迟执行. 方法执行结束之后, 在固定延迟执行.
- fiexdDelayString 和 fixedDelay 类似
- Cron 表达式
Cron 表达式是字符串, 实际是有七段值分别去描述时间,这些值是通过空白 区分
- Seconds 秒
- Minutes 分
- Hours 小时
- Day-of-Month 每月的第几天
- Month 第几月
- Day-of-Week 一周第几天
- Year (可选字段) 年
2. SchedulingConfigurer
业务场景描述:定时任务的任务名称,cron(定时任务表达式),定时任务开关,存储在数据库表中。在不重启项目的情况下,修改定时任务表达式,可以实时获取新的定时任务执行时间规则;修改定时任务执行状态,可以随时开关定时任务。
使用技术:基于接口 SchedulingConfigurer
DROP TABLE IF EXISTS `scheduled`;CREATE TABLE `scheduled` (`name` varchar(20) DEFAULT NULL,`cron` varchar(30) DEFAULT NULL,`open` tinyint(1) DEFAULT NULL COMMENT '1开启, 其他为关闭') ENGINE=InnoDB DEFAULT CHARSET=utf8;-- ------------------------------ Records of scheduled-- ----------------------------INSERT INTO `scheduled` VALUES ('demo1', '0/5 * * * * ?', '1');INSERT INTO `scheduled` VALUES ('demo2', '0/6 * * * * ?', '1');
代码实现
定时任务配置类
package com.example.demo.scheduled;import com.google.common.util.concurrent.ThreadFactoryBuilder;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.scheduling.annotation.EnableScheduling;import org.springframework.scheduling.annotation.SchedulingConfigurer;import org.springframework.scheduling.config.ScheduledTaskRegistrar;import org.springframework.scheduling.support.CronTrigger;import java.util.concurrent.Executor;import java.util.concurrent.Executors;import java.util.concurrent.ThreadFactory;/*** @Description: 基于接口SchedulingConfigurer的动态定时任务* @Author:*/@Configuration@EnableSchedulingpublic abstract class ConfigurerSchedulingDemo implements SchedulingConfigurer {//定时任务周期表达式private String cron;/*** @Description: 重写配置定时任务的方法* @param: scheduledTaskRegistrar* @return: void* @Author:*/@Overridepublic void configureTasks(ScheduledTaskRegistrar scheduledTaskRegistrar) {scheduledTaskRegistrar.setScheduler(taskScheduler());scheduledTaskRegistrar.addTriggerTask(//执行定时任务() ->{taskService();},//设置触发器triggerContext -> {cron = getCron();//获取定时任务周期表达式CronTrigger trigger = new CronTrigger(cron);return trigger.nextExecutionTime(triggerContext);});}@Beanpublic Executor taskScheduler() {//设置线程名称ThreadFactory namedThreadFactory = new ThreadFactoryBuilder().setNameFormat("demo-pool-%d").build();//创建线程池return Executors.newScheduledThreadPool(5, namedThreadFactory);}/*** @Description: 执行定时任务* @param:* @return: void* @Author:*/public abstract void taskService();/*** @Description: 获取定时任务周期表达式* @param:* @return: java.lang.String* @Author:* @Date: 2020/8/28*/public abstract String getCron();}
定时任务1
package com.example.demo.scheduled;import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;import com.example.demo.dao.CronMapper;import com.example.demo.entity.Scheduled;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.context.annotation.Configuration;import java.time.LocalDateTime;/*** @Description:* @Author:*/@Configurationpublic class TaskDemo extends ConfigurerSchedulingDemo {@Autowired //注入mapper@SuppressWarnings("all")CronMapper cronMapper;@Overridepublic void taskService() {Integer open = getOpen();if (1== open){System.out.println("定时任务demo1:"+ LocalDateTime.now()+",线程名称:"+Thread.currentThread().getName()+ " 线程id:"+Thread.currentThread().getId());}}@Overridepublic String getCron() {QueryWrapper<Scheduled> queryWrapper = new QueryWrapper<>();queryWrapper.eq("name","demo1");String cron = cronMapper.selectOne(queryWrapper).getCron();return cron;}public Integer getOpen() {QueryWrapper<Scheduled> queryWrapper = new QueryWrapper<>();queryWrapper.eq("name", "demo1");Integer open = cronMapper.selectOne(queryWrapper).getOpen();return open;}}
定时任务2
package com.example.demo.scheduled;import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;import com.example.demo.dao.CronMapper;import com.example.demo.entity.Scheduled;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.context.annotation.Configuration;import java.time.LocalDateTime;/*** @Description:* @Author:*/@Configurationpublic class TaskDemo1 extends ConfigurerSchedulingDemo {@Autowired //注入mapper@SuppressWarnings("all")CronMapper cronMapper;@Overridepublic void taskService() {Integer open = getOpen();if (1== open){System.out.println("定时任务demo2:"+ LocalDateTime.now()+",线程名称:"+Thread.currentThread().getName()+ " 线程id:"+Thread.currentThread().getId());}}@Overridepublic String getCron() {QueryWrapper<Scheduled> queryWrapper = new QueryWrapper<>();queryWrapper.eq("name","demo2");String cron = cronMapper.selectOne(queryWrapper).getCron();return cron;}public Integer getOpen() {QueryWrapper<Scheduled> queryWrapper = new QueryWrapper<>();queryWrapper.eq("name", "demo2");Integer open = cronMapper.selectOne(queryWrapper).getOpen();return open;}}
2. Hutool 定时器实现
1. 定义接口
public interface TimerTaskRunner {/*** 所有定时器业务类必须实现此接口*/public void action();}
所有定时器类必须实现此接口
2. 定义定时器实现类
/*** 本接口用来,屏蔽定时任务的多样性* <p>* 目前用hutool,不排除以后用别的*/public interface TimerExeService {/*** 启动一个定时器* <p>* 定时任务表达式书写规范:0/2 * * * * ** <p>* 六位数,分别是:秒 分 小时 日 月 年** @param taskId 任务id* @param cron cron表达式* @param className 类的全名,必须是TimerTaskRunner的子类*/void startTimer(String taskId, String cron, String className);/*** 停止一个定时器** @param taskId 定时任务Id*/void stopTimer(String taskId);}
3. 定义监听器
/*** 定时任务启动的 listener*/@Componentpublic class TimerTaskRunListener implements ApplicationListener<ApplicationStartedEvent>, Ordered {@Overridepublic void onApplicationEvent(ApplicationStartedEvent event) {TimersService timersService = SpringUtil.getBean(TimersService.class);TimerExeService timerExeService = SpringUtil.getBean(TimerExeService.class);// 获取所有开启状态的任务List<Timers> list = timersService.list(new LambdaQueryWrapper<Timers>().eq(Timers::getStatus, 1));// 添加定时任务到调度器for (Timers timer : list) {timerExeService.startTimer(String.valueOf(timer.getId()), timer.getCron(), timer.getActionClass());}// 设置秒级别的启用CronUtil.setMatchSecond(true);try {// TODO: 热启动的时候定时器不会重新启动,需要手动关闭,在启动一次, 生成环境可以将 try cathc 去掉就不会有此bug// 启动定时器执行器CronUtil.start();} catch (UtilException e) {CronUtil.restart();}}@Overridepublic int getOrder() {return LOWEST_PRECEDENCE;}}
4. Hutool 定时器实现类
@Slf4j@Servicepublic class HutoolTimerExeServiceImpl implements TimerExeService {@Overridepublic void startTimer(String taskId, String cron, String className) {log.error("定时任务开始执行...{}", className);if (ObjectUtil.hasEmpty(taskId, cron, className)) {log.error("定时任务ID,cron,className为空 ");throw new BusinessException(3, "请检查定时器的id,定时器cron表达式,定时任务是否为空!");}// 预加载类看是否存在此定时任务类try {Class.forName(className);} catch (ClassNotFoundException e) {log.error("定时任务指定类不存在。");throw new BusinessException(2, "定时任务执行类不存在");}// 定义hutool的任务Task task = () -> {try {TimerTaskRunner timerTaskRunner = (TimerTaskRunner) SpringUtil.getBean(Class.forName(className));timerTaskRunner.action();} catch (ClassNotFoundException e) {log.error(">>> 任务执行异常:{}", e.getMessage());}};// 移除定时任务CronUtil.remove(taskId);// 开始执行任务CronUtil.schedule(taskId, cron, task);}@Overridepublic void stopTimer(String taskId) {CronUtil.remove(taskId);}}
5. 定义测试类
/*** 这是一个定时任务的示例程序**/@Componentpublic class SystemOutTaskRunner implements TimerTaskRunner {@Overridepublic void action() {System.out.println("改革吹风吹....." + DateUtil.date());}}
6. spring boot启动类添加注解
@EnableScheduling
