文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

在 Spring Boot 中使用 Quartz 调度作业的示例详解

2024-04-02 19:55

关注

在本文中,我们将看看如何使用Quartz框架来调度任务。Quartz是Java应用程序调度库的事实标准。Quartz支持在特定时间运行作业、重复作业执行、将作业存储在数据库中以及Spring集成。

用于调度的Spring注解

在 Spring 应用程序中使用 Quartz 最简单的方法是使用@Scheduled注解。接下来,我们将考虑一个 Spring Boot 应用程序的示例。让我们添加必要的依赖项build.gradle

implementation 'org.springframework.boot:spring-boot-starter-quartz'

并考虑一个例子

package quartzdemo.tasks;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import java.util.Date;
@Component
public class PeriodicTask {
    
    @Scheduled(cron = "0/5 * * * * ?")
    public void everyFiveSeconds() {
        System.out.println("Periodic task: " + new Date());
    }
    
}

此外,@Scheduled要使注解起作用,您需要使用@EnableScheduling注解添加配置。

package quartzdemo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableScheduling;
@SpringBootApplication
@EnableScheduling
public class QuartzDemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(QuartzDemoApplication.class, args);
    }
}

结果将是每五秒在控制台中输出一个文本。

Periodic task: Thu Jul 07 18:24:50 EDT 2022

Periodic task: Thu Jul 07 18:24:55 EDT

2022 Periodic task: Thu Jul 07 18:25:00 EDT 2022

...

@Scheduled注解支持以下参数:

默认情况下fixedRate,fixedDelay和initialDelay以毫秒为单位设置。这可以使用timeUnit参数进行更改,将值设置NANOSECONDS为DAYS。

此外,您可以在 @Scheduled 注释中使用属性:

application.properties

cron-string=0/5 * * * * ?

PeriodicTask.java

@Component
public class PeriodicTask {
    @Scheduled(cron = "${cron-string}")
    public void everyFiveSeconds() {
        System.out.println("Periodic task: " + new Date());
    }
}

要使用属性,您可以使用fixedRateString, fixedDelayString, 和initialDelayString参数来代替 fixedRate,fixedDelay和initialDelay相应的。

使用Quartz

在前面的例子中,我们执行了定时任务,但同时我们不能动态设置作业的开始时间,也不能给它传递参数。要解决这些问题,可以直接使用 Quartz。

下面列出了主要的 Quartz 接口:

要直接使用 Quartz,无需使用@EnableScheduling注解定义配置org.springframework.boot:spring-boot-starter-quartz,您可以使用org.quartz-scheduler:quartz.

让我们定义 SimpleJob 类:

package quartzdemo.jobs;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import java.text.MessageFormat;
public class SimpleJob implements Job {
    @Override
    public void execute(JobExecutionContext context) {
        System.out.println(MessageFormat.format("Job: {0}", getClass()));
    }
}

要实现Job接口,您只需要实现一个execute接受JobExecutionContext类型参数的方法。JobExecutionContext包含有关作业实例、触发器、调度程序的信息以及有关作业执行的其他信息。

现在让我们定义一个作业实例:

JobDetail job = JobBuilder.newJob(SimpleJob.class).build();

并创建一个将在五秒后触发的触发器:

Date afterFiveSeconds = Date.from(LocalDateTime.now().plusSeconds(5)
        .atZone(ZoneId.systemDefault()).toInstant());
Trigger trigger = TriggerBuilder.newTrigger()
        .startAt(afterFiveSeconds)
        .build();

另外,创建一个调度程序:

SchedulerFactory schedulerFactory = new StdSchedulerFactory();
Scheduler scheduler = schedulerFactory.getScheduler();

在“待机”模式下Scheduler初始化,所以我们必须调用start方法:

scheduler.start();

现在我们可以安排作业的执行:

scheduler.scheduleJob(job, trigger);

更进一步,在创建时JobDetails,让我们添加额外的数据并在执行作业时使用它:

QuartzDemoApplication.java

@SpringBootApplication
public class QuartzDemoApplication {

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

    private static void onStartup() throws SchedulerException {
        JobDetail job = JobBuilder.newJob(SimpleJob.class)
                .usingJobData("param", "value") // add a parameter
                .build();

        Date afterFiveSeconds = Date.from(LocalDateTime.now().plusSeconds(5)
                .atZone(ZoneId.systemDefault()).toInstant());
        Trigger trigger = TriggerBuilder.newTrigger()
                .startAt(afterFiveSeconds)
                .build();

        SchedulerFactory schedulerFactory = new StdSchedulerFactory();
        Scheduler scheduler = schedulerFactory.getScheduler();
        scheduler.start();
        scheduler.scheduleJob(job, trigger);
    }
}

SimpleJob.java

public class SimpleJob implements Job {

    @Override
    public void execute(JobExecutionContext context) {
        JobDataMap dataMap = context.getJobDetail().getJobDataMap();
        String param = dataMap.getString("param");
        System.out.println(MessageFormat.format("Job: {0}; Param: {1}",
                getClass(), param));
    }

}

需要注意的是,添加到的所有值都JobDataMap必须是可序列化的。

工作商店

Quartz 将有关JobDetail、Trigger的数据和其他信息存储在JobStore. 默认情况下,JobStore使用内存。这意味着如果我们在它们被触发之前已经安排了任务并关闭了应用程序(例如,在重新启动或崩溃时),那么它们将永远不会再次执行。Quartz 还支持 JDBC-JobStore 在数据库中存储信息。

在使用 JDBC-JobStore 之前,需要在 Quartz 将使用的数据库中创建表。默认情况下,这些表的前缀为QRTZ_.

Quartz 源代码包含用于为各种数据库(如 Oracle、Postgres、MS SQL Server、MySQL 等)创建表的SQL 脚本,并且还有一个用于 Liquibase 的现成 XML 文件。

spring.quartz.jdbc.initialize-schema=always此外,通过指定属性,可以在启动应用程序时自动创建 QRTZ 表。

为简单起见,我们将使用第二种方法和 H2 数据库。让我们配置一个数据源,使用 JDBCJobStore 并在 application.properties 中创建 QRTZ 表:

spring.datasource.url=jdbc:h2:mem:testdb spring.datasource.driverClassName=org.h2.Driver spring.datasource.username=sa spring.datasource.password= spring.quartz.job-store-type=jdbc spring.quartz.jdbc.initialize-schema=always

要考虑这些设置,必须将调度程序创建为 Spring bean:

package quartzdemo;

import org.quartz.*;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.quartz.SchedulerFactoryBean;
import quartzdemo.jobs.SimpleJob;

import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.Date;

@SpringBootApplication
@EnableScheduling
public class QuartzDemoApplication {

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

    @Bean()
    public Scheduler scheduler(SchedulerFactoryBean factory) throws SchedulerException {
        Scheduler scheduler = factory.getScheduler();
        scheduler.start();
        return scheduler;
    }

    @Bean
    public CommandLineRunner run(Scheduler scheduler) {
        return (String[] args) -> {
            JobDetail job = JobBuilder.newJob(SimpleJob.class)
                    .usingJobData("param", "value") // add a parameter
                    .build();

            Date afterFiveSeconds = Date.from(LocalDateTime.now().plusSeconds(5)
                    .atZone(ZoneId.systemDefault()).toInstant());
            Trigger trigger = TriggerBuilder.newTrigger()
                    .startAt(afterFiveSeconds)
                    .build();

            scheduler.scheduleJob(job, trigger);
        };
    }

}

线程池配置

Quartz 在单独的线程中运行每个任务,您可以为调度程序配置线程池。还需要注意的是,默认情况下,通过@Scheduled注解和直接通过 Quartz 启动的任务是在不同的线程池中启动的。我们可以确保这一点:

PeriodicTask.java

@Component
public class PeriodicTask {

    @Scheduled(cron = "${cron-string}")
    public void everyFiveSeconds() {
        System.out.println(MessageFormat.format("Periodic task: {0}; Thread: {1}",
                new Date().toString(), Thread.currentThread().getName()));
    }

}

SimpleJob.java

public class SimpleJob implements Job {

    @Override
    public void execute(JobExecutionContext context) {
        JobDataMap dataMap = context.getJobDetail().getJobDataMap();
        String param = dataMap.getString("param");
        System.out.println(MessageFormat.format("Job: {0}; Param: {1}; Thread: {2}",
                getClass(), param, Thread.currentThread().getName()));
    }

}

输出将是:

Periodic task: Thu Jul 07 19:22:45 EDT 2022; Thread: scheduling-1 Job: class quartzdemo.jobs.SimpleJob; Param: value; Thread: quartzScheduler_Worker-1 Periodic task: Thu Jul 07 19:22:50 EDT 2022; Thread: scheduling-1 Periodic task: Thu Jul 07 19:22:55 EDT 2022; Thread: scheduling-1 Periodic task: Thu Jul 07 19:23:00 EDT 2022; Thread: scheduling-1

任务的线程池@Scheduled只包含一个线程。

让我们更改 @Scheduled 任务的调度程序设置:

package quartzdemo;

import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.SchedulingConfigurer;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.scheduling.config.ScheduledTaskRegistrar;

@Configuration
public class SchedulingConfiguration implements SchedulingConfigurer {

    @Override
    public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
        ThreadPoolTaskScheduler threadPoolTaskScheduler = new ThreadPoolTaskScheduler();

        threadPoolTaskScheduler.setPoolSize(10);
        threadPoolTaskScheduler.setThreadNamePrefix("my-scheduled-task-pool-");
        threadPoolTaskScheduler.initialize();

        taskRegistrar.setTaskScheduler(threadPoolTaskScheduler);
    }

}

输出现在将是这样的:

Periodic task: Thu Jul 07 19:44:10 EDT 2022; Thread: my-scheduled-task-pool-1 Job: class quartzdemo.jobs.SimpleJob; Param: value; Thread: quartzScheduler_Worker-1 Periodic task: Thu Jul 07 19:44:15 EDT 2022; Thread: my-scheduled-task-pool-1 Periodic task: Thu Jul 07 19:44:20 EDT 2022; Thread: my-scheduled-task-pool-2

如您所见,这些设置仅影响使用注释设置的任务。

现在让我们更改直接使用 Quartz 的调度程序的设置。这可以通过两种方式完成:通过属性文件或通过创建 bean SchedulerFactoryBeanCustomizer。

让我们使用第一种方法。如果我们没有通过 Spring 初始化 Quartz,我们将不得不在 quartz.properties 文件中注册属性。在我们的例子中,我们需要在 application.properties 中注册属性,并spring.quartz.properties.为其添加前缀。

application.properties

spring.quartz.properties.org.quartz.threadPool.threadNamePrefix=my-scheduler_Worker spring.quartz.properties.org.quartz.threadPool.threadCount=25

让我们启动应用程序。现在输出将是这样的:

Periodic task: Sat Jul 23 10:45:55 MSK 2022; Thread: my-scheduled-task-pool-1 Job: class quartzdemo.jobs.SimpleJob; Param: value; Thread: my-scheduler_Worker-1

现在调用启动任务的线程my-scheduler_Worker-1。

多个调度器

如果您需要创建多个具有不同参数的调度程序,则必须定义多个SchedulerFactoryBeans. 让我们看一个例子。

package quartzdemo;

import quartzdemo.jobs.SimpleJob;
import org.quartz.*;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.quartz.SchedulerFactoryBean;

import javax.sql.DataSource;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.Date;
import java.util.Properties;

@SpringBootApplication
@EnableScheduling
public class QuartzDemoApplication {

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

    @Bean("customSchedulerFactoryBean1")
    public SchedulerFactoryBean customSchedulerFactoryBean1(DataSource dataSource) {
        SchedulerFactoryBean factory = new SchedulerFactoryBean();
        Properties properties = new Properties();
        properties.setProperty("org.quartz.threadPool.threadNamePrefix", "my-custom-scheduler1_Worker");
        factory.setQuartzProperties(properties);
        factory.setDataSource(dataSource);
        return factory;
    }

    @Bean("customSchedulerFactoryBean2")
    public SchedulerFactoryBean customSchedulerFactoryBean2(DataSource dataSource) {
        SchedulerFactoryBean factory = new SchedulerFactoryBean();
        Properties properties = new Properties();
        properties.setProperty("org.quartz.threadPool.threadNamePrefix", "my-custom-scheduler2_Worker");
        factory.setQuartzProperties(properties);
        factory.setDataSource(dataSource);
        return factory;
    }

    @Bean("customScheduler1")
    public Scheduler customScheduler1(@Qualifier("customSchedulerFactoryBean1") SchedulerFactoryBean factory) throws SchedulerException {
        Scheduler scheduler = factory.getScheduler();
        scheduler.start();
        return scheduler;
    }

    @Bean("customScheduler2")
    public Scheduler customScheduler2(@Qualifier("customSchedulerFactoryBean2") SchedulerFactoryBean factory) throws SchedulerException {
        Scheduler scheduler = factory.getScheduler();
        scheduler.start();
        return scheduler;
    }

    @Bean
    public CommandLineRunner run(@Qualifier("customScheduler1") Scheduler customScheduler1,
                                 @Qualifier("customScheduler2") Scheduler customScheduler2) {
        return (String[] args) -> {
            Date afterFiveSeconds = Date.from(LocalDateTime.now().plusSeconds(5).atZone(ZoneId.systemDefault()).toInstant());

            JobDetail jobDetail1 = JobBuilder.newJob(SimpleJob.class).usingJobData("param", "value1").build();
            Trigger trigger1 = TriggerBuilder.newTrigger().startAt(afterFiveSeconds).build();
            customScheduler1.scheduleJob(jobDetail1, trigger1);

            JobDetail jobDetail2 = JobBuilder.newJob(SimpleJob.class).usingJobData("param", "value2").build();
            Trigger trigger2 = TriggerBuilder.newTrigger().startAt(afterFiveSeconds).build();
            customScheduler2.scheduleJob(jobDetail2, trigger2);
        };
    }

}

输出:

Job: class quartzdemo.jobs.SimpleJob; Param: value2; Thread: my-custom-scheduler2_Worker-1 Job: class quartzdemo.jobs.SimpleJob; Param: value1; Thread: my-custom-scheduler1_Worker-1

结论

Quartz 是一个用于自动执行计划任务的强大框架。它既可以在简单直观的 Spring 注解的帮助下使用,也可以通过精细的定制和调整来使用,从而为复杂的问题提供解决方案。

到此这篇关于在SpringBoot中使用Quartz调度作业的示例详解的文章就介绍到这了,更多相关SpringBoot调度作业内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

阅读原文内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯