SpringBoot 定时任务 实现方式

要实现定时任务,主要有以下两种方案:

  1. timer
  2. 使用 Spring 自带的定时任务处理器 @Scheduled 注解
  3. 使用第三方框架 Quartz

一、 timer

使用Timer创建简单的定时任务

public class TimerDemo {
    
    
   public static void main(String[] args) {
    
    
       Timer mytimer = new Timer();
       mytimer.schedule(new TimerTask() {
    
    
           @Override
           public void run() {
    
    
               System.out.println(String.format("This is my TimerTask1: %s", System.currentTimeMillis()));
           }
       }, 1000, 5000);//延时1s,之后每隔5s运行一次
   }
}

有两点问题需要注意:

  1. scheduleAtFixedRate和schedule的区别:scheduleAtFixedRate会尽量减少漏掉调度的情况,如果前一次执行时间过长,导致一个或几个任务漏掉了,那么会补回来,而schedule过去的不会补,直接加上间隔时间执行下一次任务。

  2. 同一个Timer下添加多个TimerTask,如果其中一个没有捕获抛出的异常,则全部任务都会终止运行。但是多个Timer是互不影响

在这里插入图片描述

二、 @Scheduled

使用 @Scheduled 非常容易,直接创建一个 Spring Boot 项目,并且添加 web 依赖 spring-boot-starter-web。
在这里插入图片描述

项目创建成功后,添加 @EnableScheduling 注解,开启定时任务:

@SpringBootApplication
@EnableScheduling
public class SpringBootScheduledQuartzApplication {
    
    

    public static void main(String[] args) {
    
    
        SpringApplication.run(SpringBootScheduledQuartzApplication.class, args);
    }

}

配置定时任务:

@Service
public class ScheduledService {
    
    
    /**
     * 上一任务的结束时间 与 下一任务的开始时间之间 间隔3s
     */
    @Scheduled(fixedDelay = 3000)
    public void fixedDelay() {
    
    
        System.out.println(String.format("fixedDelay: %s", System.currentTimeMillis()));
    }

    /**
     * 两次定时任务开始的间隔时间为3s
     */
    @Scheduled(fixedRate = 3000)
    public void fixedRate() {
    
    
        System.out.println(String.format("fixedRate: %s", System.currentTimeMillis()));
    }
    
	/**
     * 第一次延迟 3秒后执行,之后按 fixedRate 的规则每 4 秒执行一次
     */
    //@Scheduled(initialDelay = 3000, fixedDelay = 4000)
    public void initialDelay() {
    
    
        System.out.println(String.format("initialDelay: %s", System.currentTimeMillis()));
    }

}

测试结果:
在这里插入图片描述

(1)首先使用 @Scheduled 注解开启一个定时任务。

(2)fixedDelay 表示任务执行之间的时间间隔,具体是指本次任务结束到下次任务开始之间的时间间隔。
(3)fixedRate 表示任务执行之间的时间间隔,具体是指两次任务的开始时间间隔,即第二次任务开始时,第一次任务可能还没结束。

(4)initialDelay 表示首次任务启动的延迟时间。

(5)所有时间的单位都是毫秒。

上面这是一个基本用法,除了这几个基本属性之外,@Scheduled 注解也支持 cron 表达式,使用 cron 表达式,可以非常丰富的描述定时任务的时间。cron 表达式格式如下:

  1. cron 表达式结构
    corn从左到右(用空格隔开):秒 分 小时 月份中的日期 月份 星期中的日期 年份

cron一定有七位数,最后一位是年,SpringBoot定时方案只需要设置六位即可:

  • 第一位, 表示秒, 取值是0 ~ 59
  • 第二位, 表示分. 取值是0 ~ 59
  • 第三位, 表示小时, 取值是0 ~ 23
  • 第四位, 表示天/日, 取值是0 ~ 31
  • 第五位, 表示月份, 取值是1 ~ 12
  • 第六位, 表示星期, 取值是1 ~ 7, 星期一,星期二…, 还有 1 表示星期日
  • 第七位, 年份, 可以留空, 取值是1970 ~ 2099
  1. cron 表达式各字段的含义
字段 允许值 允许的特殊字符
秒(Seconds) 0~59的整数 , - * / 四个字符
分(Minutes) 0~59的整数 , - * / 四个字符
小时(Hours) 0~23的整数 , - * / 四个字符
日期(DayofMonth) 1~31的整数(但是你需要考虑你月的天数) ,- * ? / L W C 八个字符
月份(Month) 1~12的整数或者 JAN-DEC , - * / 四个字符
星期(DayofWeek) 1~7的整数或者SUN-SAT (1=SUN) , - * ? / L C # 八个字符
年(可选,留空)(Year) 1970~2099 , - * / 四个字符

这一块需要大家注意的是,月份中的日期和星期可能会起冲突,因此在配置时这两个得有一个是 ?

通配符含义:

(1) ? 表示不指定值,即不关心某个字段的取值时使用。需要注意的是,月份中的日期和星期可能会起冲突,因此在配置时这两个得有一个是 ?
(2)* 表示所有值,例如:在秒的字段上设置 *,表示每一秒都会触发
(3), 用来分开多个值,例如在周字段上设置 “MON,WED,FRI” 表示周一,周三和周五触发
(4)- 表示区间,例如在秒上设置 “10-12”,表示 10,11,12秒都会触发
(5)/ 用于递增触发,如在秒上面设置”5/15” 表示从5秒开始,每增15秒触发(5,20,35,50)
(6)# 序号(表示每月的第几个周几),例如在周字段上设置”6#3”表示在每月的第三个周六,(用 在母亲节和父亲节再合适不过了)
(7)周字段的设置,若使用英文字母是不区分大小写的 ,即 MON 与mon相同
(8)L 表示最后的意思。在日字段设置上,表示当月的最后一天(依据当前月份,如果是二月还会自动判断是否是润年), 在周字段上表示星期六,相当于”7”或”SAT”(注意周日算是第一天)。如果在”L”前加上数字,则表示该数据的最后一个。例如在周字段上设置”6L”这样的格式,则表示”本月最后一个星期五”
(9)W 表示离指定日期的最近工作日(周一至周五),例如在日字段上设置”15W”,表示离每月15号最近的那个工作日触发。如果15号正好是周六,则找最近的周五(14号)触发, 如果15号是周未,则找最近的下周一(16号)触发,如果15号正好在工作日(周一至周五),则就在该天触发。如果指定格式为 “1W”,它则表示每月1号往后最近的工作日触发。如果1号正是周六,则将在3号下周一触发。(注,”W”前只能设置具体的数字,不允许区间”-“)
(10)L 和 W 可以一组合使用。如果在日字段上设置”LW”,则表示在本月的最后一个工作日触发(一般指发工资 )

举几个例子:

0 0 3 * * ? :每天 3 点执行;
0 5 3 * * ?:每天 35 分执行;
0 5 3 ? * *:每天 35 分执行,与上面作用相同;
0 5/10 3 * * ?:每天 3 点的 5 分、15 分、25 分、35 分、45 分、55分这几个时间点执行;
0 10 3 ? * 1:每周星期天,310 分执行,注,1 表示星期天;
0 10 3 ? * 1#3:每个月的第三个星期,星期天执行,# 号只能出现在星期的位置。

在 @Scheduled 注解中来一个简单的 cron 表达式,每隔5秒触发一次

@Scheduled(cron = "0/5 * * * * ?")
public void cron() {
    
    
     System.out.println(String.format("cron : %s", System.currentTimeMillis()));
 }

测试结果:
在这里插入图片描述

三、Quartz

一般在项目中,除非定时任务涉及到的业务实在是太简单,使用 @Scheduled 注解来解决定时任务,否则大部分情况可能都是使用 Quartz 来做定时任务。
Quartz有四个核心概念:

  • Job:是一个接口,只定义一个方法 execute(JobExecutionContext context),在实现接口的 execute 方法中编写所需要定时执行的 Job(任务),JobExecutionContext 类提供了调度应用的一些信息;Job 运行时的信息保存在 JobDataMap 实例中。
  • JobDetail:Quartz 每次调度 Job 时,都重新创建一个 Job 实例,因此它不接受一个 Job 的实例,相反它接收一个 Job 实现类(JobDetail,描述 Job 的实现类及其他相关的静态信息,如 Job 名字、描述、关联监听器等信息),以便运行时通过 newInstance() 的反射机制实例化 Job。
  • rigger:是一个类,描述触发 Job 执行的时间触发规则,主要有 SimpleTrigger 和 CronTrigger 这两个子类。当且仅当需调度一次或者以固定时间间隔周期执行调度,SimpleTrigger 是最适合的选择;而CronTrigger 则可以通过 Cron 表达式定义出各种复杂时间规则的调度方案:如工作日周一到周五的 15:00 ~ 16:00 执行调度等。
  • Scheduler:调度器就相当于一个容器,装载着任务和触发器,该类是一个接口,代表一个 Quartz 的独立运行容器,Trigger 和 JobDetail 可以注册到 Scheduler 中,两者在 Scheduler 中拥有各自的组及名称,组及名称是 Scheduler 查找定位容器中某一对象的依据,Trigger 的组及名称必须唯一,JobDetail 的组和名称也必须唯一(但可以和 Trigger 的组和名称相同,因为它们是不同类型的)。Scheduler 定义了多个接口方法,允许外部通过组及名称访问和控制容器中 Trigger 和 JobDetail。
    quartz
    Job 为作业的接口,为任务调度的对象;JobDetail 用来描述 Job 的实现类及其他相关的静态信息;Trigger 做为作业的定时管理工具,一个 Trigger 只能对应一个作业实例,而一个作业实例可对应多个触发器;Scheduler 做为定时任务容器,是 Quartz 最上层的东西,它提携了所有触发器和作业,使它们协调工作,每个 Scheduler 都存有 JobDetail 和 Trigger 的注册,一个 Scheduler 中可以注册多个 JobDetail 和多个 Trigger。

在 Spring Boot 中使用 Quartz ,只需要在创建项目时,添加 Quartz 依赖即可:
在这里插入图片描述

项目创建完成后,也需要添加开启定时任务的注解:

@SpringBootApplication
@EnableScheduling
public class SpringBootScheduledQuartzApplication {
    
    

    public static void main(String[] args) {
    
    
        SpringApplication.run(SpringBootScheduledQuartzApplication.class, args);
    }

}

quartz 在使用过程中,有两个关键概念,一个是JobDetail(要做的事情),另一个是触发器(什么时候做),要定义 JobDetail,需要先定义 Job,Job 的定义有两种方式:

继承 QuartzJobBean 并实现默认的方法executeInternal , 任务启动时,executeInternal 方法将会被执行:

public class CustomQuartzJob extends QuartzJobBean {
    
    
    private String name;

    public void setName(String name) {
    
    
        this.name = name;
    }

    @Override
    protected void executeInternal(JobExecutionContext jobExecutionContext) throws JobExecutionException {
    
    
        System.out.println(String.format("This is my QuartJob JobDetail name: %s,: %s", name ,System.currentTimeMillis()));
    }
}

Job 有了之后,接下来创建类,配置 JobDetail 和 Trigger 触发器,如下:

@Configuration
public class QuartzConfig {
    
    
   @Bean
    public JobDetail CustomJobDetail(){
    
    
        JobDetail jobDetail = JobBuilder.newJob(CustomQuartzJob.class)
                .withIdentity("customJob")
                //JobDataMap可以给任务execute传递参数
                .usingJobData("name","ouyang")
                .storeDurably()
                .build();
        return jobDetail;
    }

    @Bean
    public Trigger sampleJobTrigger() {
    
    
        // 每隔两秒执行一次
        SimpleScheduleBuilder scheduleBuilder = SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(2).repeatForever();
        return TriggerBuilder.newTrigger().forJob(CustomJobDetail()).withIdentity("sampleTrigger")
                .withSchedule(scheduleBuilder).build();
    }
   @Bean
    public Trigger myTrigger(){
    
    
        Trigger trigger = TriggerBuilder.newTrigger()
                .forJob(CustomJobDetail())
                .withIdentity("sampleTrigger")
                .startNow()
                //.withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(5).repeatForever())
                .withSchedule(CronScheduleBuilder.cronSchedule("0/5 * * * * ?"))
                .build();
        return trigger;
    }
}

在这里插入图片描述

在这里插入图片描述

说明:

(1)JobDetail 的配置有两种方式:MethodInvokingJobDetailFactoryBean 和 JobDetailFactoryBean 。
(2)使用 MethodInvokingJobDetailFactoryBean 可以配置目标 Bean 的名字和目标方法的名字,这种方式不支持传参。
(3)使用 JobDetailFactoryBean 可以配置 JobDetail ,任务类继承自 QuartzJobBean ,这种方式支持传参,将参数封装在 JobDataMap 中进行传递。
(4)Trigger 是指触发器,Quartz 中定义了多个触发器,例如:SimpleTrigger 和 CronTrigger 。
(5)SimpleTrigger 有点类似于前面说的 @Scheduled 的基本用法。
(6)CronTrigger 则有点类似于@Scheduled 中 cron 表达式的用法。

猜你喜欢

转载自blog.csdn.net/u014553029/article/details/113737295