首页 > 编程笔记 > Java笔记 阅读:1

SpringBoot @Scheduled实现定时任务(附带实例)

使用 Spring Boot 内置的 @Scheduled 注解实现后台定时任务,虽然其开发简单、执行效率高,但是只适合处理简单的计划任务,不能处理分布式计划任务。在任务数量多的情况下,可能出现阻塞、延迟启动等问题。

Quartz 是 OpenSymphony 开源组织在任务调度(Job Scheduling,也称为作业调度)领域下的开源项目,它是 Java 开发的开源任务调度管理系统,具有使用灵活、配置简单的特点,能够实现复杂应用场景下的任务调度管理。

当定时任务愈加复杂时,使用 Spring Boot 注解 @Scheduled 已经不能满足业务需要。相比之下,Quartz 灵活而又不失简单,能够创建简单或复杂的调度任务,其主要具有如下功能:

SpringBoot Quartz的基本概念

想要明白 Quartz 怎么用,首先要了解 Job(任务)、JobDetail(任务信息)、Trigger(触发器)和 Scheduler(调度器)这 4 个基本概念。

1) Job(任务)

接口,只有 execute(JobExecutionContext context) 方法,在实现接口的 execute() 方法中编写需要定时执行的任务,JobExecutionContext 类提供调度任务执行的上下文信息,任务运行时的信息保存在 JobDataMap 实例中。

2) JobDetail(任务信息)

Quartz 每次调度任务时都重新创建一个 Job 实例,因此它不接收一个任务的实例,而是接收任务实现类(JobDetail,描述任务的实现类及其他相关的静态信息,如任务名字、描述、关联监听器等信息),以便运行时通过调用 newInstance() 方法的反射机制实例化任务。

3) Trigger(触发器)

一个类,描述触发任务执行的时间触发规则,主要有 SimpleTrigger 和 CronTrigger 这两个类:

4) Scheduler(调度器)

调度器就相当于一个容器,装载着任务和触发器,该类是一个接口,代表一个 Quartz 的独立运行容器。

Trigger 和 JobDetail 可以注册到 Scheduler 中,两者在 Scheduler 中拥有各自的组和名称,组和名称是 Scheduler 查找、定位容器中某个对象的依据,Trigger 的组和名称必须唯一,JobDetail 的组和名称也必须唯一(但可以与 Trigger 的组和名称相同,因为它们是不同类型的)。Scheduler 定义了多个接口方法,允许外部通过组和名称访问控制容器中的 Trigger 与 JobDetail。

总而言之,Scheduler 相当于一个容器,其中包含各种 Job 和 Trigger,四者之间的关系如下图所示。


图 1 四者之间的关系图

Quartz 通过 Scheduler 触发 Trigger 规则实现任务的管理和调度。除此之外,Quartz 还提供了 TriggerBuilder 和 JobBuilder 类来构建 Trigger 实例和 Job 实例。

Spring Boot 对 Quartz 做了很好的支持,它提供 spring-boot-starter-quartz 组件集成 Quartz,开箱即用,让我们在项目中使用 Quartz 变得更加简单。

SpringBoot简单定时任务

Quartz 主要有简单定时调度器(SimpleSchedule)和 Cron表达式调度器(CronSchedule)来调度触发的定时任务。下面通过示例演示这两种调度器的用法。

1) 添加Quartz依赖

在 pom.xml 中配置 Quartz 的依赖包 spring-boot-starter-quartz,具体配置如下:
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-quartz</artifactId>
</dependency>

2) 创建任务

创建定时任务的实现类 SampleJob,并继承 QuartzJobBean,示例代码如下:
public class SampleJob extends QuartzJobBean {
    private static final Logger logger = LoggerFactory.getLogger(SchedulerTask.class);

    private String name;
    public void setName(String name) {
        this.name = name;
    }
    @Override
    protected void executeInternal(JobExecutionContext context)
            throws JobExecutionException {
        logger.info(String.format("Hello %s!", this.name));
    }
}
在上面的示例中,SampleJob 实现了 executeInternal() 方法执行后台任务,同时定义的 Name 变量用于触发任务时传入参数。

3) 构建JobDetail、CronTrigger

接下来构建 JobDetail 和 Trigger 实例。首先使用 SimpleScheduleBuilder 创建 Scheduler 实例,然后关联 JobDetail 和 Trigger 实例。示例代码如下:
@Configuration
public class SampleScheduler {
    @Bean
    public JobDetail sampleJobDetail() {
        return JobBuilder.newJob(SampleJob.class).withIdentity("sampleJob")
                .usingJobData("name", "weiz test1")
                .storeDurably()
                .build();
    }

    @Bean
    public Trigger sampleJobTrigger() {
        SimpleScheduleBuilder scheduleBuilder = SimpleScheduleBuilder.simpleSchedule()
                .withIntervalInSeconds(10).repeatForever();
        return TriggerBuilder
                .newTrigger()
                .forJob(sampleJobDetail())
                .withIdentity("sampleTrigger")
                .withSchedule(scheduleBuilder).build();
    }
}
在上面的示例中,定义的 SampleScheduler 调度器类包含 JobDetail 和 Trigger 两个对象,分别用于控制任务启动时的参数传入和任务触发规则。

示例参数说明如下:

4) 运行任务

启动项目,验证任务是否能正常运行。如下图所示,SampleJob 后台任务成功运行,每隔 10 秒执行一次,这说明使用 SimpleSchedule 创建简单的定时任务运行成功。


图 2 简单任务运行日志

SpringBoot Cron定时任务

如果需要定义更复杂的应用场景可以使用 CronSchedule,它可以设置更灵活的使用方式,定时设置可以参考上面的 Cron 表达式。其实两种定时任务大体一致,只是 SimpleSchedule 换成了 CronSchedule。

1) 定义Job

public class CronJob extends QuartzJobBean {
    private static final Logger logger = LoggerFactory.getLogger(SchedulerTask.class);

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

    @Override
    protected void executeInternal(JobExecutionContext context)
            throws JobExecutionException {
        logger.info(String.format("Hello %s!", this.name));
    }
}
在上面的示例中,定义 CronJob 后台任务,继承自 QuartzJobBean 类,然后实现 executeInternal() 方法,与之前的任务一样。

2) 构建JobDetail、CronTrigger

使用 Scheduler 关联 JobDetail 和 CronTrigger,并设置每隔 10 秒执行一次。
@Configuration
public class CronScheduler {
    @Bean
    public JobDetail cronJobDetail(){
        return JobBuilder.newJob(CronJob.class)
                .withIdentity("cronJob")
                .usingJobData("name","weiz cronJob")
                .storeDurably()
                .build();
    }

    @Bean
    public Trigger cronJobTrigger(){
        CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule("0/10 * * * * ?");
        return TriggerBuilder.newTrigger()
                .withIdentity("CronSchedule")
                .forJob(cronJobDetail())
                .withSchedule(scheduleBuilder)
                .build();
    }
}
在上面的示例中,也是创建 JobDetail 和 JobTrigger,不同的是,这里使用 CronScheduleBuilder 创建调度器,传入“0/10 * * * * ?”Cron表达式,设置任务每隔 10 秒执行一次。

3) 运行任务

启动项目,验证任务是否能正常运行,如下图所示,CronJob 后台任务成功运行,每隔 10 秒执行一次。这说明使用 CronSchedule 创建复杂的 Cron 定时任务运行成功。


图 3 Cron定时任务运行日志

相关文章