Spring Boot Schedule 定时任务

Spring Boot Schedule

Spring Boot 定时任务,常用的几种任务调度:

  1. Timer,简单无门槛,一般也没人用。
  2. spring @Scheduled 注解,一般集成于项目中,小任务很方便。
  3. 开源工具 Quartz,分布式集群开源工具,以下两个分布式任务应该都是基于Quartz实现的,可以说是中小型公司必选,当然也视自身需求而定。
  4. 分布式任务 XXL-JOB,是一个轻量级分布式任务调度框架,支持通过 Web 页面对任务进行 CRUD 操作,支持动态修改任务状态、暂停/恢复任务,以及终止运行中任务,支持在线配置调度任务入参和在线查看调度结果。

Timer

  1. 按照固定的频率去执行一个任务,不能指定时间
public class TestTimer {
    public static void main(String[] args) {
        TimerTask timerTask = new TimerTask() {
            @Override
            public void run() {
                System.out.println("task  run:"+ new Date());
            }
        };
        Timer timer = new Timer();
        // 安排指定的任务在指定的时间开始进行重复的固定延迟(10毫秒)执行。这里是每3秒执行一次
        timer.schedule(timerTask, 10, 3000);
    }
}

ScheduledExecutorService

和 timer 类似

public class TestScheduledExecutorService {
    public static void main(String[] args) {
        ScheduledExecutorService service = Executors.newSingleThreadScheduledExecutor();
        // 参数:1、任务体 2、首次执行的延时时间
        //      3、任务执行间隔 4、间隔时间单位
        service.scheduleAtFixedRate(()->System.out.println("task ScheduledExecutorService "+new Date()), 0, 3, TimeUnit.SECONDS);
    }
}

@Sechedule 注解标记的定时任务

  1. @EnableScheduling 在配置类上使用,开启计划任务的支持(类上)
  2. @Scheduled 来声明这是一个任务,包括 cron, fixDelay, fixRate 等类型(方法上,需先开启计划任务的支持)
  • fixedRate ( 开始 - 开始 )是配置上一次任务执行开始到下一次执行开始的时间间隔,不会等待上一次任务执行完成就会调度下一次任务,将其放入等待队列中。
  • fixedDelay ( 结束 - 开始 )是配置的上一次任务执行结束到下一次执行开始的间隔时间,也就是说会等待上一次任务执行结束后,延迟间隔时间,再执行下一次任务。
  • cron 任务执行的cron表达式 0/1 * * * * ?
  • zone cron表达式解析使用的时区,默认为服务器的本地时区,使用java.util.TimeZone#getTimeZone(String)方法解析 GMT-8:00
  • fixedDelay 上一次任务执行结束到下一次执行开始的间隔时间,单位为ms 1000
  • fixedDelayString 上一次任务执行结束到下一次执行开始的间隔时间,使用java.time.Duration#parse解析 PT15M
  • fixedRate 以固定间隔执行任务,即上一次任务执行开始到下一次执行开始的间隔时间,单位为ms,若在调度任务执行时,上一次任务还未执行完毕,会加入worker队列,等待上一次执行完成后立即执行下一次任务 2000
  • fixedRateString 与fixedRate逻辑一致,只是使用java.time.Duration#parse解析 PT15M
  • initialDelay 首先任务执行的延迟时间 1000
  • initialDelayString 首次任务执行的延迟时间,使用java.time.Duration#parse解析 PT15M
  1. @Sechedule(cron = "", zone="")
  2. @Sechedule(fixedDelay = "", zone="")
  3. @Sechedule(fixedDelayString = "", zone="")
  4. @Sechedule(fixedRate = "", zone="")
  5. @Sechedule(fixedRateString = "", zone="")
@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Repeatable(Schedules.class)
public @interface Scheduled {
   String CRON_DISABLED = "-";
   String cron() default "";  //类似于corn表达式,可以指定定时任务执行的延迟及周期规则
   
   String zone() default "";  //指明解析cron表达式的时区。

   long fixedDelay() default -1;  // end - start 在最后一次调用结束和下一次调用开始之间以固定周期(以毫秒为单位)执行带注解的方法。(要等待上次任务完成后)
   String fixedDelayString() default "";  //同上面作用一样,只是String类型

   long fixedRate() default -1;  // start - start 在调用之间以固定的周期(以毫秒为单位)执行带注解的方法。(不需要等待上次任务完成)
   String fixedRateString() default "";  //同上面作用一样,只是String类型

   long initialDelay() default -1;  //第一次执行fixedRate()或fixedDelay()任务之前延迟的毫秒数 。
   String initialDelayString() default "";  //同上面作用一样,只是String类型
}
cron 表达式格式:
    {秒数}    {分钟}     {小时}    {日期}     {月份}                      {星期}            {年份(可为空)}
    {0-59}   {0-59}    {0-23}   {1-31}     {1-12 or JAN-DEC}         {1-7 or SUN-SAT}   {empty,1970-2099}
    {, - * /}                   {, - * ? / L W C}                    {, - * ? / L C #}  {, - * /}

# 每隔5秒执行一次
@Scheduled(cron = "*/5 * * * * ?")
# 每隔1分钟执行一次
@Scheduled(cron = "0 */1 * * * ?")
# 每天23点执行一次
@Scheduled(cron = "0 0 23 * * ?")
# 每天凌晨1点执行一次
@Scheduled(cron = "0 0 1 * * ?")
# 每月1号凌晨1点执行一次
@Scheduled(cron = "0 0 1 1 * ?")
# 每月最后一天23点执行一次;
@Scheduled(cron = "0 0 23 L * ?")
# 每周星期天凌晨1点执行一次
@Scheduled(cron = "0 0 1 ? * L")
# 在26分、29分、33分执行一次
@Scheduled(cron = "0 26,29,33 * * * ?")
# 每天的0点、13点、18点、21点都执行一次
@Scheduled(crom = "0 0 0,13,18,21 * * ?")
  • , :表示列出枚举值。 例如在 分 使用5,20,则意味着在5和20分 每分钟触发一次。
  • - :表示范围。 例如在 分 使用5-20,表示从5分到20分钟每分钟触发一次。
  • * :表示匹配该域的任意值。 例如在 分 使用*,即表示每分钟都会触发事件。
  • / :表示起始时间开始触发,然后每隔固定时间触发一次。 例如在分域使用5/20,则意味着5分钟触发一次,而25,45等分别触发一次。
  • ? :只能用在周和日。它也匹配域的任意值,但是实际不会,因为周和日会相互影响。 例如在每月的20日触发调度,不管20日到底是星期几,则只能使用如下写法:13 13 15 20 * ?,其中最后一位只能用?,而不能使用*,如果使用 *表示不管星期几都会触发,实际上并不是这样。
  • L :表示最后,只能出现日和周。 例如在 日 使用 5L,意味着在最后的一个星期四触发。
  • W :表示有效工作日(周一到周五),只能出现在周域,系统将在离指定日期的最近的有效工作日除法事件。 例如:在 日 使用 5W,如果5日是星期六,则将在最近的工作日:星期五,即4日触发。如果5日是星期天,则在6日触发;如果5日在星期一到星期五中的一天,则就在5日触发。另外一点,W的最近寻找不会跨过月份。
  • # :用于确定每个月第几个星期几,只能出现在周。 例如在4#2,表示某月的第二个星期三。
  • LW :这两个字符可以连用,表示在某个月最后一个工作日,即最后一个星期五。

Spring 动态修改 Cron

优点:能够动态修改
缺点:单一定时任务

  1. 实现动态定时任务,就需要借助 Spring 的 SchedulingConfigurer 接口,该方式可以实现动态时间,定时任务。
  2. 在 configureTasks 方法中,ScheduledTaskRegistrar 通过 addTriggerTask 来添加触发器任务,从而去执行。
  3. addTriggerTask 方法有两个参数,一个是要执行得任务,一个是触发器。
@Component
public class CronSchedule implements SchedulingConfigurer {
 
  @Override
  public void configureTasks(ScheduledTaskRegistrar scheduledTaskRegistrar) {
    // 通过数据库获取所有任务,所有任务通过
    List<ScheduleSetting> scheduleList = xxxxxxMapper.getScheduleList();
    System.out.println(scheduleList.size());
    if (CollectionUtils.isNotEmpty(scheduleList)){
      for (ScheduleSetting s : scheduleList){
        scheduledTaskRegistrar.addTriggerTask(getRunnable(s), getTrigger(s));
      }
    }
  }
 
  /**
   * 转换首字母小写
   *
   * @param str
   * @return
   */
  public static String lowerFirstCapse(String str) {
    char[] chars = str.toCharArray();
    chars[0] += 32;
    return String.valueOf(chars);
  }
 
  private Runnable getRunnable(ScheduleSetting scheduleSetting){
    return new Runnable() {
      @Override
      public void run() {
        Class<?> clazz;
        try {
          clazz = Class.forName(scheduleSetting.getBeanName());
          String className = lowerFirstCapse(clazz.getSimpleName());
          Object bean = SpringContext.getBean(className);
          Method method = ReflectionUtils.findMethod(bean.getClass(), scheduleSetting.getMethodName());
          if (StringUtils.isNotEmpty(scheduleSetting.getMethodParams())) {
            ReflectionUtils.invokeMethod(method, bean, scheduleSetting.getMethodParams());
          } else {
            ReflectionUtils.invokeMethod(method, bean);
          }
        } catch (ClassNotFoundException e) {
          e.printStackTrace();
        }
      }
    };
  }
 
  private Trigger getTrigger(ScheduleSetting scheduleSetting){
    return new Trigger() {
      @Override
      public Date nextExecutionTime(TriggerContext triggerContext) {
        CronTrigger trigger = new CronTrigger(scheduleSetting.getCronExpression());
        Date nextExec = trigger.nextExecutionTime(triggerContext);
        return nextExec;
      }
    };
  }
}
# task-config.ini:
printTime.cron=0/10 * * * * ?
@Data
@Slf4j
@Component
@PropertySource("classpath:/task-config.ini")
public class ScheduleTask implements SchedulingConfigurer {
  
    @Value("${printTime.cron}")
    private String cron;
  
    @Override
    public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
        // 动态使用cron表达式设置循环间隔
        taskRegistrar.addTriggerTask(new Runnable() {
            @Override
            public void run() {
                log.info("Current time: {}", LocalDateTime.now());
            }
        }, new Trigger() {
            @Override
            public Date nextExecutionTime(TriggerContext triggerContext) {
              // 任务触发,可修改任务的执行周期.
              // 每一次任务触发,都会执行这里的方法一次,重新获取下一次的执行时间
                // 使用CronTrigger触发器,可动态修改cron表达式来操作循环规则
//                CronTrigger cronTrigger = new CronTrigger(cron);
//                Date nextExecutionTime = cronTrigger.nextExecutionTime(triggerContext);
  
                // 使用不同的触发器,为设置循环时间的关键,区别于CronTrigger触发器,该触发器可随意设置循环间隔时间,单位为毫秒
                PeriodicTrigger periodicTrigger = new PeriodicTrigger(timer);
                Date nextExecutionTime = periodicTrigger.nextExecutionTime(triggerContext);
                return nextExecutionTime;
            }
        });
    }
}
@Slf4j
@RestController
@RequestMapping("/test")
public class TestController {
  
    private final ScheduleTask scheduleTask;
  
    @Autowired
    public TestController(ScheduleTask scheduleTask) {
        this.scheduleTask = scheduleTask;
    }
  
    @GetMapping("/updateCron")
    public String updateCron(String cron) {
        log.info("new cron :{}", cron);
        scheduleTask.setCron(cron);
        return "ok";
    }
  
    @GetMapping("/updateTimer")
    public String updateTimer(Long timer) {
        log.info("new timer :{}", timer);
        scheduleTask.setTimer(timer);
        return "ok";
    }
}

Spring Boot 集成 Quartz

SQL 文件路径:quartz-2.3.2.jar!\org\quartz\impl\jdbcjobstore\tables_mysql_innodb.sql

基本概念

  • 任务 Job

    • 工作任务调度的接口,每一个job必须实现org.quartz.job接口,且只需实现接口定义的execute()方法
    • 实例在Quartz中的生命周期中
      • 每次调度器执行job时它在调用execute方法前,会创建一个新的job实例
      • 当调用完成后,关联的job对象实例会被是释放,释放的实例会被垃圾回收机制回收
  • 触发器 Trigger

    • 负责触发任务
    • 主要包含两种:SimpleTriggerCronTriggerr
  • 调度器 Schedule

    • Scheduler是任务的调度器,会将任务job和触发器Trigger结合,负责基于Trigger设定的时间执行job

集成步骤

  • 引入依赖
<!-- quartz定时任务 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-quartz</artifactId>
</dependency>
  • 新建Job任务
    • 这里需要注意使用的是 Job 类,不能使用自定义方法,需要后边修改。
@Slf4j
public class CustomJob implements Job {

    @Override
    public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
        log.info("Hello Job执行时间: " + new Date());
    }
}
  • 定义一个定时任务
public class QuartzTest {

    public static void main(String[] args) throws Exception {
        // 1. 创建一个JobDetail,把实现了Job接口的类邦定到JobDetail 构建者模式 绑定job withIdentity这里起一个唯一的名字
        JobDetail jobDetail = JobBuilder.newJob(CustomJob.class)
                .withIdentity("job1") // 唯一标识
                .build();
        // 2.创建一个Trigger触发器的实例,定义该job立即执行,并且每2秒执行一次,一直执行 repeatForever重复
        SimpleTrigger trigger = TriggerBuilder
            .newTrigger()
            .withIdentity("trigger1") // 唯一标识
            .startNow() // 立即生效
            .withSchedule(SimpleScheduleBuilder.simpleSchedule()
            .withIntervalInSeconds(2) // 每2秒执行一次
            .repeatForever()) // 一直重复执行
            .build();

		// SimpleTrigger 简单触发器
		Date startTime = DateBuilder.nextGivenSecondDate(new Date( ),15);  // 在当前时间15秒后运行
		// 创建一个SimpleTrigger实例,指定该Trigger在Scheduler中所属组及名称。
		// 接着设置调度的时间规则.当前时间15秒后运行,每10秒运行一次,共运行15次
        SimpleTrigger trigger = (SimpleTrigger) TriggerBuilder.newTrigger().withIdentity("trigger1", "group1")
            .startAt(startTime).withSchedule(SimpleScheduleBuilder.simpleSchedule()
                    .withIntervalInSeconds(10)
                    .withRepeatCount(15)
            ).build();
    
        // CronTrigger - Cron触发器
        // 每两秒执行
        CronTrigger trigger = TriggerBuilder.newTrigger().withIdentity("trigger1", "group1").withSchedule(
            CronScheduleBuilder.cronSchedule("/2 * * * * ?")
        ).build();
        
        // 日期触发
        // 每一分钟执行
        DailyTimeIntervalTrigger trigger = TriggerBuilder.newTrigger().withIdentity("trigger1", "group1").withSchedule(
            DailyTimeIntervalScheduleBuilder.dailyTimeIntervalSchedule().withInterval(1, DateBuilder.IntervalUnit.MINUTE)
        ).build();
        
        // 日历触发
        // 每两秒执行
        CalendarIntervalTrigger trigger = TriggerBuilder.newTrigger().withIdentity("trigger1", "group1").withSchedule(
            CalendarIntervalScheduleBuilder.calendarIntervalSchedule().withInterval(2, DateBuilder.IntervalUnit.SECOND)
        ).build();

        // 3.创建schedule调度器并执行   StdSchedulerFactory 工厂模式
        StdSchedulerFactory factory = new StdSchedulerFactory();
        // 获取调度器实例
        Scheduler scheduler = factory.getScheduler();
        // 开启调度器
        scheduler.start();
        // 把SimpleTrigger和JobDetail注册给调度器,通过触发器开始每2秒一次重复执行。
        scheduler.scheduleJob(jobDetail, trigger);
        // 关闭调度器
        // scheduler.shutdown();
    }
}
  • 面向对象,定时任务对象
    • CustomJob.java jobClassName 对应执行的方法(面前是类名/类路径,后续增加 Bean)
    • 任务 JobDetail
      • name 任务名称 jobName
      • group 任务分组 jobGroup
      • JobBuilder 是⽣成 JobDetail 的唯⼀标识 JobKey,暂时保存在 JobBuilder 中
    • 触发器 Trigger
      • name 任务名称 jobName
      • group 任务分组 jobGroup
      • TriggerBuilder ⽣成 Trigger 的唯⼀标识 TriggerKey,暂时保存在 TriggerBuilder 中
      • startNow() 从当前时间开始
      • startAt() 传⼊的时间早于cron表达式设置的时间,则将会触发⼀次,但不管cron表达式是否会多次执⾏,都只会触发⼀次
      • endAt() 结束
      • withSchedule
        • SimpleScheduleBuilder 简单的触发器
          • RepeatForever:指定触发器将无限期重复
          • WithRepeatCount:指定重复次数
        • CalendarIntervalScheduleBuilder 日期触发器
          • WithInterval:指定要生成触发器的时间单位和间隔
          • WithIntervalInHours:指定要生成触发器的间隔按小时来
          • WithIntervalInMinutes:指定要生成触发器的间隔按分钟来
          • WithIntervalInSeconds:指定要生成触发器的间隔按秒来
          • WithIntervalInDays:指定要生成触发器的间隔按日来
          • WithIntervalInWeeks:指定要生成触发器的间隔按周来
          • WithIntervalInMonths:指定要生成触发器的间隔按月来
          • WithIntervalInYears:指定要生成触发器的间隔按年来
        • DailyTimeIntervalScheduleBuilder 日期触发类(日)
        • CronScheduleBuilder 基于Cron表达式的Trigger,使⽤最多、功能最强⼤的Trigger。所以对象 cronException CRON表达式
      • WithDescription(“remark”) 添加说明
      • UsingJobData:添加Job数据,底层是key-vaule的JobDataMap,和JobBuilder里面的一样
      • ForJob:指定对应关系的JobKey,四个重载方法,底层都是指定一个JobKey
@Data
public class JobInfo{

    private String cronExpression;
    private String jobClassName;
    private String triggerGroupName;
    private String triggerName;
    private String jobGroupName;
    private String jobName;
    private Date nextFireTime;
    private Date previousFireTime;
    private Date startTime;
    private String timeZone;
    private String status;
    private String remark;
}
// 创建一个触发器
var trigger = TriggerBuilder.Create().Build();
// Trigger立即触发
TriggerBuilder.newTrigger().startNow().build();
// Trigger指定时间触发
TriggerBuilder.newTrigger().startAt(time).build();
// Trigger指定周期时间触发,即corn方式
TriggerBuilder.newTrigger().startNow().endAt(endTime)
    .withSchedule(dailyAtHourAndMinute(start.getHours(), start.getMinutes()))
    .build();

Spring Boot 集成 XXL-JOB

部署项目 XXL-JOB

XXL-JOB 分布式任务调度中心
项目源码

    1. 执行/xxl-job/doc/db目录下的数据库初始化脚本
    1. 修改数据库配置
    1. 启动运行
    1. 访问 http://127.0.0.1:9080/xxl-job-admin
    1. 用户密码分别为:admin/123456

Spring Boot 集成

  • 引入依赖
<!-- xxl-job-core 分布式任务定时调度工具 -->
<dependency>
    <groupId>com.xuxueli</groupId>
    <artifactId>xxl-job-core</artifactId>
    <version>2.3.1</version>
</dependency>
  • 启动类上添加注解@EnableScheduling

  • 添加配置类

@Configuration
@RequiredArgsConstructor
public class BeanConfig {

    @Bean
    public XxlJobSpringExecutor xxlJobExecutor() {
        XxlJobSpringExecutor xxlJobSpringExecutor = new XxlJobSpringExecutor();
        // 管理系统地址
        xxlJobSpringExecutor.setAdminAddresses("http://127.0.0.1:8080/xxl-job-admin");
        // 执行器的名称
        xxlJobSpringExecutor.setAppname("xxlJob");
        // 地址
//        xxlJobSpringExecutor.setAddress(address);
//        xxlJobSpringExecutor.setIp(ip);
        // 内部连接使用的端口  通过 38801 与 XXL-JOB 管理系统关联
        xxlJobSpringExecutor.setPort(38801);
//        xxlJobSpringExecutor.setAccessToken(accessToken);
        // 日志文件路径
        xxlJobSpringExecutor.setLogPath("logs/xxl-job/first-study");
        // 日志保留 7 天 
        xxlJobSpringExecutor.setLogRetentionDays(7);
        return xxlJobSpringExecutor;
    }
}
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值