使用Spring Boot的Quartz指南——实现工作调度和自动化

431 阅读14分钟

简介

时间是宝贵的而委托时间和资源来执行琐碎的任务往往会浪费资源和金钱。因此,企业努力在他们的系统中实现全面的自动化,因为它的可扩展性更强,而且成本明显降低。

随着业务流程的复杂性增加,系统中的自动化的好处也在增加。

自动化的概念在这里得到了体现。 工作调度员的概念盛行。 工作调度通常被称为任何种类的一批进程(工作),在一个特定的时间执行。由于这些作业大多不要求立即执行,它们可以被安排在不久的将来或重复的时间段内处理。

一般来说,任何一种流程的自动化都比人工方式要好。

  • 资源效率
  • 更少的错误
  • 更多的可扩展性

用于大规模Java应用的最强大和最有弹性的调度框架之一被称为 石英.

在本指南中,我们将在Spring Boot应用程序中实施Quartz及其组件,为自定义工作触发器建立我们自己的Quartz管理控制台

Quartz作业调度器

Quartz是一个用Java编写的开放源码、功能丰富的作业调度框架,被设计为与任何类型的J2EE或J2SE框架集成。它提供了巨大的灵活性而不牺牲复杂性或可扩展性。

这个名字大概来自于极其精确的手表和时钟中使用的石英晶体,在电动振荡下,手表的指针在一个固定的时间范围内移动。

如果一个应用程序被期望在预定的时间间隔或由于某些事件而执行任务,石英是一个很好的选择。

  • 触发电子邮件提醒或警报。你可以根据账户活动轻松地触发密码到期邮件或其他类型的提醒警报给不同的用户。
  • 执行文件传输或消息传递操作。可以很容易地在一定的时间间隔内安排工作,从不同的经纪人或FTP站点发布/消耗消息/数据/文件。
  • 自动生成报告。公司通常喜欢生成夜间/每周的报告,以展示业务绩效。这些工作可以轻松地生成报告,并在预定的时间触发电子邮件给员工。
  • 推动任务工作流程。大型电子商务组织可以安排一个工作,在一定的时间间隔内准确地从渠道中挑选订单,并处理其履行或表现出来。

Quartz的一些突出特点是。

  • 它可以在应用服务器或Servlet容器中实例化,并能参与XA交易。
  • 它可以作为一个独立的程序集群(具有负载平衡和故障转移能力)托管,以执行作业。
  • 作业被安排在触发器发生时运行,如一天中的某个时间,几周、几月或几年中的某几天,在节假日跳过执行,重复到某个日期或无限期地执行,等等。
  • 工作可以在内存或任何JDBC数据存储中持久化。
  • 它可以参与到JTA事务中。

Quartz调度器模型的主要组成部分

为了提高可扩展性,Quartz多线程环境下运行。这有助于该框架并发地运行工作。

整个框架的核心是Scheduler 接口。Scheduler ,为其跟踪所有的JobDetails和Triggers。它们代表了需要运行的内容(哪个Job )和时间(该作业是什么Trigger)。

因此,它们构成了框架的主要组件。其余的所有其他组件确保它的发生是尽职尽责和有效的。

让我们以鹰眼的视角来看看我们将要使用的关键组件。

  • Scheduler Factory- 工厂Bean,负责根据石英属性文件的内容,建立Scheduler 模型和连接所有的依赖组件。
  • Scheduler- 维护JobDetail/Trigger注册表。它还负责在触发器触发时执行相关作业。
  • Scheduler Thread- 负责执行触发器的工作的线程。它联系JobStore ,以获得下一组要启动的触发器。
  • Job- 一个接口,必须由要执行的任务来实现。
  • 触发器- 指示调度器应该在什么时候触发相关作业。
  • JobStore- 一个接口,由为Job和Triggers提供存储机制的类来实现。
  • ThreadPool- 一个要执行的作业被转移到线程池中,由ThreadPool 表示。
  • Worker Threads- 建立ThreadPool ,执行作业的单个线程。

Quartz Components

构建Quartz管理控制台

我们将建立我们自己的Quartz管理控制台,以获得对Quartz调度器的生命周期的理解和认识。

为此,我们将建立一个简单的UI驱动的管理控制台,可以执行两种任务。

  • 安排和管理简单的工作
  • 安排和管理Cron作业

实施后,它看起来会是这样的。

Job Management Console

项目设置

让我们创建一个Spring Boot项目并逐一实现每个Quartz组件。最简单的方法是通过Spring Initializr开始一个骨架项目。

Spring Initializr

我们添加了Spring Web的MVC功能,Spring Data JPA将数据存储到数据存储中,H2作为一个内存数据库。 Lombok(可选的减少锅炉板的库)和 Thymeleaf(Spring/MVC应用的模板引擎)。我们还包括了spring-boot-starter-quartz 包,以使石英进入我们的项目。

数据库连接和初始化

Quartz带来了它自己的内置JobStores。在Spring Boot中,我们可以选择。

  • 内存中的JobStores:将所有数据保存在RAM中,这样当应用程序停止或崩溃时,所有数据都会被转储,所有调度信息都会丢失。为此,我们使用一个RAMJobStore
  • JDBC JobStores:将所有的数据保存在数据存储中,这样数据就不会丢失。配置比内存(RAM)作业存储要多一点。

**注意:**无论你的数据库是什么,你都可以选择这些JobStore 类型。

我们将使用H2作为我们的数据存储,并设置Quartz来持久化数据。

让我们开始开发我们的管理应用程序,为H2定义数据库连接参数。在你的application.properties 文件中,让我们定义init.schema (初始设置脚本),以及datasource 参数。

server.port=8080

spring.sql.init.schema-locations=classpath:db/quartz_tables_h2.sql
spring.h2.console.enabled=true
spring.datasource.url=jdbc:h2:mem:testdb
spring.datasource.username=sa
spring.datasource.password=

logging.level.org.hibernate.SQL=debug

quartz_tables_h2.sql 脚本由一组冗长的SQL命令组成,用于初始设置。

-- Note, Quartz depends on row-level locking which means you must use the MVC=TRUE
-- setting on your H2 database, or you will experience dead-locks
--
-- In your Quartz properties file, you'll need to set
-- org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.StdJDBCDelegate

CREATE TABLE QRTZ_CALENDARS (
  SCHED_NAME VARCHAR(120) NOT NULL,
  CALENDAR_NAME VARCHAR (200)  NOT NULL ,
  CALENDAR IMAGE NOT NULL
);

...
-- Download the entire script from our GitHub repository
...

COMMIT;

石英属性

一旦数据库连接建立起来,并且我们已经准备好了我们的初始化脚本--我们就想设置一下 石英调度器和它的组件。

大多数方面和组件在一定程度上是可定制的,例如JobStores应该使用哪些驱动程序,ThreadPool 中有多少个线程以及它们的优先级,等等。

所有这些都在quartz.properties 文件中定义,该文件应位于/src/resources/ 。这是QuartzProperties 类在默认情况下寻找所需属性的目录。

**注意:**如果你想在另一个属性文件中定义它,你必须将org.quartz.properties 系统属性指向该文件。

现在让我们来定义一些属性。

#============================================================================
# Configure Main Scheduler Properties
#============================================================================
org.quartz.scheduler.instanceName=spring-boot-quartz
org.quartz.scheduler.instanceId=AUTO 

#============================================================================
# Configure ThreadPool
#============================================================================
org.quartz.threadPool.class = org.quartz.simpl.SimpleThreadPool
org.quartz.threadPool.threadCount = 25
org.quartz.threadPool.threadPriority = 5

#============================================================================
# Configure JobStore
#============================================================================
org.quartz.jobStore.misfireThreshold=1000
org.quartz.jobStore.class=org.quartz.impl.jdbcjobstore.JobStoreTX
org.quartz.jobStore.driverDelegateClass=org.quartz.impl.jdbcjobstore.StdJDBCDelegate
org.quartz.jobStore.useProperties=true
org.quartz.jobStore.tablePrefix=QRTZ_


#============================================================================
# Configure Cluster properties
#============================================================================
org.quartz.jobStore.isClustered=true
org.quartz.jobStore.clusterCheckinInterval=1000

定义一个Scheduler Job Factory Bean

如果我们不在@Configuration 类中使用这些属性来定制Quartz的工作方式,所有这些属性都没有什么意义。让我们把quartz.properties 中的属性注入到SchedulerConfig 类中,在那里我们将初始化SchedulerJobFactoryBean 类,把这些属性传进去。

我们将实现我们自己的SchedulerJobFactoryBean ,作为Quartz项目的SpringBeanJobFactory

@Configuration
public class SchedulerConfig {

    @Autowired
    private DataSource dataSource;

    @Autowired
    private ApplicationContext applicationContext;

    @Autowired
    private QuartzProperties quartzProperties;

    @Bean
    public SchedulerFactoryBean schedulerFactoryBean() {

        SchedulerJobFactory jobFactory = new SchedulerJobFactory();
        jobFactory.setApplicationContext(applicationContext);

        Properties properties = new Properties();
        properties.putAll(quartzProperties.getProperties());

        SchedulerFactoryBean factory = new SchedulerFactoryBean();
        factory.setOverwriteExistingJobs(true);
        factory.setDataSource(dataSource);
        factory.setQuartzProperties(properties);
        factory.setJobFactory(jobFactory);
        return factory;
    }
}

QuartzProperties 类包含在quartz.properties 文件中定义的属性。我们可以通过getProperties() ,把它们添加到SchedulerFactoryBean ,与DataSource ,和SchedulerJobFactory 一起检索。

SchedulerJobFactory 是我们对Quartz为我们提供的SpringBeanJobFactory 的自定义实现。让我们来扩展它。

public class SchedulerJobFactory extends SpringBeanJobFactory implements ApplicationContextAware {

    private AutowireCapableBeanFactory beanFactory;

    @Override
    public void setApplicationContext(final ApplicationContext context) {
        beanFactory = context.getAutowireCapableBeanFactory();
    }

    @Override
    protected Object createJobInstance(final TriggerFiredBundle bundle) throws Exception {
        final Object job = super.createJobInstance(bundle);
        beanFactory.autowireBean(job);
        return job;
    }
}

现在,我们可以通过我们的工厂创建工作,并在需要时自动连接。在这个阶段--我们可以调出一个正在运行的Quartz调度器实例。如果我们运行我们的应用程序,我们就会看到如下内容。

Spring Quartz Log

定义一个通用的工作调度器创造者

在Quartz中有两种类型的触发器 -CronTriggerSimpleTrigger 。它们分别对应于CronSchedulerSimpleScheduler ,我们可以使用它们各自的工厂来创建触发器。

一个CronTrigger ,基于一个 cron表达式而一个SimpleTrigger ,则是根据一个时间间隔触发。

为了创建工作触发器,让我们定义几个方便的方法,通过它们各自的工厂来实例化和返回它们。这些方法将位于一个JobSchedulerCreator - 一个我们将用来创建作业和触发器的@Component

@Component
public class JobScheduleCreator {
    // Creation methods
}

让我们从一个CronTrigger 创建者方法开始。

public CronTrigger createCronTrigger(String triggerName, Date startTime, String cronExpression, int misFireInstruction) {
    CronTriggerFactoryBean factoryBean = new CronTriggerFactoryBean();
    factoryBean.setName(triggerName);
    factoryBean.setStartTime(startTime);
    factoryBean.setCronExpression(cronExpression);
    factoryBean.setMisfireInstruction(misFireInstruction);
    try {
      factoryBean.afterPropertiesSet();
    } catch (ParseException e) {
      log.error(e.getMessage(), e);
    }
    return factoryBean.getObject();
}

使用CronTriggerFactoryBean ,我们传入关于一个Trigger 的必要信息,比如它的名字、开始时间以及cron表达式和误报指令。一旦生成 - 对象就会被返回。

同样的过程也适用于创建SimpleTrigger 对象。

public SimpleTrigger createSimpleTrigger(String triggerName, Date startTime, Long repeatTime, int misFireInstruction) {
    SimpleTriggerFactoryBean factoryBean = new SimpleTriggerFactoryBean();
    factoryBean.setName(triggerName);
    factoryBean.setStartTime(startTime);
    factoryBean.setRepeatInterval(repeatTime);
    factoryBean.setRepeatCount(SimpleTrigger.REPEAT_INDEFINITELY);
    factoryBean.setMisfireInstruction(misFireInstruction);
    factoryBean.afterPropertiesSet();
    return factoryBean.getObject();
}

有了建立触发器的直接方法,我们也可以建立一个建立作业的凸显方法--依靠JobDetailFactoryBean

public JobDetail createJob(Class<? extends QuartzJobBean> jobClass, boolean isDurable,
                           ApplicationContext context, String jobName, String jobGroup) {
    JobDetailFactoryBean factoryBean = new JobDetailFactoryBean();
    factoryBean.setJobClass(jobClass);
    factoryBean.setDurability(isDurable);
    factoryBean.setApplicationContext(context);
    factoryBean.setName(jobName);
    factoryBean.setGroup(jobGroup);

    // Set job data map
    JobDataMap jobDataMap = new JobDataMap();
    jobDataMap.put(jobName + jobGroup, jobClass.getName());
    factoryBean.setJobDataMap(jobDataMap);
    factoryBean.afterPropertiesSet();
    return factoryBean.getObject();
}

定义一个Scheduler作业信息实体

为了跟踪工作的细节和信息--我们可以使用JobDetails 类。这就是它的作用。然而,我们可以从为我们自己的类定义一个代理中获益。

我们不建议直接写入Quartz表,所以工作和他们的细节虽然是持久的,但却是固定的。我们可以定义一个新的实体,在一个单独的表中跟踪这些工作,并随心所欲地处理它们--同时将这些对象作为数据传输对象(DTO)

这允许我们对传入的数据进行验证,并允许我们对作业在数据库中的持久化方式进行更精细的控制。

虽然是可选的,但建议使用这样的代理--我们将其命名为SchedulerJobInfo

// Lombok annotations for getters, setters and constructor
public class SchedulerJobInfo {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private String jobId;
    private String jobName;
    private String jobGroup;
    private String jobStatus;
    private String jobClass;
    private String cronExpression;
    private String desc;    
    private String interfaceName;
    private Long repeatTime;
    private Boolean cronJob;
}

对于简单的CRUD功能--我们将为这个实体创建一个简单的JpaRepository

@Repository
public interface SchedulerRepository extends JpaRepository<SchedulerJobInfo, Long> {
    SchedulerJobInfo findByJobName(String jobName);
}

在Quartz中实现工作--工作和QuartzJobBean

每个工作都必须扩展QuartzJobBean 类或实现Job 接口。

QuartzJobBean 实现 ,唯一的区别是, 将传递的 和 作为bean的属性值,而 则不。Job QuartzJobBean JobDataMap SchedulerContext Job

此外,Job 需要你实现execute() 方法,而QuartzJobBean 需要你实现executeInternal() 方法。

让我们创建一个SimpleJob ,该extends QuartzJobBean ,并打印出05的整数。

public class SimpleJob extends QuartzJobBean {
    @Override
    protected void executeInternal(JobExecutionContext context) throws JobExecutionException {
        log.info("SimpleJob Start................");
        IntStream.range(0, 5).forEach(i -> {
            log.info("Counting - {}", i);
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                log.error(e.getMessage(), e);
            }
        });
        log.info("SimpleJob End................");
    }
}

同样,我们可以创建一个SimpleCronJob ,在一个特定的cron表达式上启动。

@DisallowConcurrentExecution
public class SimpleCronJob extends QuartzJobBean {
    @Override
    protected void executeInternal(JobExecutionContext context) throws JobExecutionException {
        log.info("SimpleCronJob Start................");
        IntStream.range(0, 10).forEach(i -> {
            log.info("Counting - {}", i);
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                log.error(e.getMessage(), e);
            }
        });
        log.info("SimpleCronJob End................");
    }
}

**注意:**我们应用@DisallowConcurrentExecution ,这样在集群设置中这项工作就不会被多个调度器同时执行。

定义行动的自定义工具

Quartz管理控制台,我们将有几个作业的选项。

  • 创建
  • 编辑
  • 运行一次
  • 暂停
  • 恢复
  • 删除

这些允许我们创建、编辑、暂停、恢复、删除和运行一次任务。为了使其更吸引人--我们将为每个任务编写方法,使我们能够从一个非常简单和直观的用户界面上控制工作。

为了把这一切联系起来,我们将建立一个新的类-- SchedulerJobService来执行这些动作。

@Transactional
@Service
public class SchedulerJobService {

	@Autowired
	private Scheduler scheduler;

	@Autowired
	private SchedulerFactoryBean schedulerFactoryBean;

	@Autowired
	private SchedulerRepository schedulerRepository;

	@Autowired
	private ApplicationContext context;

	@Autowired
	private JobScheduleCreator scheduleCreator;
	
	// Create, edit, pause jobs, etc...
创建一个Quartz工作

为了创建工作,一个专有的saveOrUpdate() 方法决定是否将通过特定的SchedulerJobInfo DTO创建的实例保存到现有的实体中,或者是否应该创建一个新工作。根据有效载荷中的参数,我们将创建一个SimpleCronJobSimpleJob

public void saveOrUpdate(SchedulerJobInfo scheduleJob) throws Exception {
	if (scheduleJob.getCronExpression().length() > 0) {
		scheduleJob.setJobClass(SimpleCronJob.class.getName());
		scheduleJob.setCronJob(true);
	} else {
		scheduleJob.setJobClass(SimpleJob.class.getName());
		scheduleJob.setCronJob(false);
		scheduleJob.setRepeatTime((long) 1);
	}
	if (StringUtils.isEmpty(scheduleJob.getJobId())) {
		log.info("Job Info: {}", scheduleJob);
		scheduleNewJob(scheduleJob);
	} else {
		updateScheduleJob(scheduleJob);
	}
	scheduleJob.setDesc("i am job number " + scheduleJob.getJobId());
	scheduleJob.setInterfaceName("interface_" + scheduleJob.getJobId());
	log.info(">>>>> jobName = [" + scheduleJob.getJobName() + "]" + " created.");
}

如果工作不存在--我们就调用scheduleNewJob() ,它将使用我们之前的自动连接的JobScheduleCreator 组件来安排一个新的工作。

private void scheduleNewJob(SchedulerJobInfo jobInfo) {
	try {
		Scheduler scheduler = schedulerFactoryBean.getScheduler();

		JobDetail jobDetail = JobBuilder
				.newJob((Class<? extends QuartzJobBean>) Class.forName(jobInfo.getJobClass()))
				.withIdentity(jobInfo.getJobName(), jobInfo.getJobGroup()).build();
		if (!scheduler.checkExists(jobDetail.getKey())) {

			jobDetail = scheduleCreator.createJob(
					(Class<? extends QuartzJobBean>) Class.forName(jobInfo.getJobClass()), false, context,
					jobInfo.getJobName(), jobInfo.getJobGroup());

			Trigger trigger;
			if (jobInfo.getCronJob()) {
				trigger = scheduleCreator.createCronTrigger(
				        jobInfo.getJobName(), 
				        new Date(),
						jobInfo.getCronExpression(),
						SimpleTrigger.MISFIRE_INSTRUCTION_FIRE_NOW);
			} else {
				trigger = scheduleCreator.createSimpleTrigger(
				        jobInfo.getJobName(), 
				        new Date(),
				        jobInfo.getRepeatTime(), 
				    
    SimpleTrigger.MISFIRE_INSTRUCTION_FIRE_NOW);
			}
			scheduler.scheduleJob(jobDetail, trigger);
			jobInfo.setJobStatus("SCHEDULED");
			schedulerRepository.save(jobInfo);
			log.info(">>>>> jobName = [" + jobInfo.getJobName() + "]" + " scheduled.");
		} else {
			log.error("scheduleNewJobRequest.jobAlreadyExist");
		}
	} catch (ClassNotFoundException e) {
		log.error("Class Not Found - {}", jobInfo.getJobClass(), e);
	} catch (SchedulerException e) {
		log.error(e.getMessage(), e);
	}
}

当创建一个触发器时,我们传入一个MISFIRE_INSTRUCTION 。 有时,Quartz可能会错过发射某个工作。这可能发生在工人线程很忙,调度器停机,或者某项工作在过去被安排启动,以及类似的问题。

我们把我们的触发器设置为MISFIRE_INSTRUCTION_FIRE_NOW - 如果发生误射,会再次发射。如果没有定义MISFIRE_INSTRUCTION ,Quartz会采用一个智能策略--MISFIRE_INSTRUCTION__SMART_POLICY

编辑一个Quartz作业

要编辑作业,我们可以使用与创建作业相同的方法--然而,我们必须通知Scheduler,一旦作业的指令被更新,就重新安排

private void updateScheduleJob(SchedulerJobInfo jobInfo) {
	Trigger newTrigger;
	if (jobInfo.getCronJob()) {
	
		newTrigger = scheduleCreator.createCronTrigger(
		        jobInfo.getJobName(), 
		        new Date(), 
		        jobInfo.getCronExpression(), 
		        simpleTrigger.MISFIRE_INSTRUCTION_FIRE_NOW);
	} else {
	
		newTrigger = scheduleCreator.createSimpleTrigger(
		        jobInfo.getJobName(), 
		        new Date(), 
		        jobInfo.getRepeatTime(),
			    SimpleTrigger.MISFIRE_INSTRUCTION_FIRE_NOW);
	}
	try {
		schedulerFactoryBean.getScheduler().rescheduleJob(TriggerKey.triggerKey(jobInfo.getJobName()), newTrigger);
		jobInfo.setJobStatus("EDITED & SCHEDULED");
		schedulerRepository.save(jobInfo);
		log.info(">>>>> jobName = [" + jobInfo.getJobName() + "]" + " updated and scheduled.");
	} catch (SchedulerException e) {
		log.error(e.getMessage(), e);
	}
}
运行一次石英作业

有时你想在临时情况下启动一个触发器。你也可能想在提交到日程表之前启动一个作业来测试它是否工作正常。

我们可以使用triggerJob() 方法来立即启动它,而不需要等待预定的cron或时间。这使我们可以创建一个测试热键。

public boolean startJobNow(SchedulerJobInfo jobInfo) {
    try {
        SchedulerJobInfo getJobInfo = schedulerRepository.findByJobName(jobInfo.getJobName());
        getJobInfo.setJobStatus("SCHEDULED & STARTED");
        schedulerRepository.save(getJobInfo);
        schedulerFactoryBean.getScheduler().triggerJob(new JobKey(jobInfo.getJobName(), jobInfo.getJobGroup()));
        log.info(">>>>> jobName = [" + jobInfo.getJobName() + "]" + " scheduled and started now.");
        return true;
    } catch (SchedulerException e) {
        log.error("Failed to start new job - {}", jobInfo.getJobName(), e);
        return false;
    }
}
暂停一个石英作业

如果你想暂停一个正在运行的Cron作业或简单作业,我们可以使用pauseJob() 方法,这将暂停一个作业,直到你恢复它。

public boolean pauseJob(SchedulerJobInfo jobInfo) {
    try {
        SchedulerJobInfo getJobInfo = schedulerRepository.findByJobName(jobInfo.getJobName());
     	getJobInfo.setJobStatus("PAUSED");
        schedulerRepository.save(getJobInfo);
        schedulerFactoryBean.getScheduler().pauseJob(new JobKey(jobInfo.getJobName(), jobInfo.getJobGroup()));
      log.info(">>>>> jobName = [" + jobInfo.getJobName() + "]" + " paused.");
        return true;
    } catch (SchedulerException e) {
        log.error("Failed to pause job - {}", jobInfo.getJobName(), e);
        return false;
    }
}
恢复一个石英工作

当然,我们可以通过简单地使用resumeJob() 方法来恢复一个暂停的工作。

public boolean resumeJob(SchedulerJobInfo jobInfo) {
    try {
      SchedulerJobInfo getJobInfo = schedulerRepository.findByJobName(jobInfo.getJobName());
      getJobInfo.setJobStatus("RESUMED");
      schedulerRepository.save(getJobInfo);
      schedulerFactoryBean.getScheduler().resumeJob(new JobKey(jobInfo.getJobName(), jobInfo.getJobGroup()));
      log.info(">>>>> jobName = [" + jobInfo.getJobName() + "]" + " resumed.");
      return true;
    } catch (SchedulerException e) {
      log.error("Failed to resume job - {}", jobInfo.getJobName(), e);
      return false;
    }
}
删除一个石英工作

最后,我们可以通过调用deleteJob() 方法来删除一个作业。

public boolean deleteJob(SchedulerJobInfo jobInfo) {
    try {
        SchedulerJobInfo getJobInfo = schedulerRepository.findByJobName(jobInfo.getJobName());
        schedulerRepository.delete(getJobInfo);
        log.info(">>>>> jobName = [" + jobInfo.getJobName() + "]" + " deleted.");
        return schedulerFactoryBean.getScheduler().deleteJob(new JobKey(jobInfo.getJobName(), jobInfo.getJobGroup()));
    } catch (SchedulerException e) {
      log.error("Failed to delete job - {}", jobInfo.getJobName(), e);
      return false;
    }
}

石英管理控制台用户界面

现在,我们已经有了所有的功能,可以把我们的Quartz管理控制台与网络应用程序的用户界面结合起来,我们可以测试这些功能。

任何能够触发我们之前定义的方法的REST API都可以正常工作。我们的实现使用Thymeleaf作为渲染引擎。

首先,让我们选择 "创建"按钮来创建一个新作业。它将打开一个弹出窗口,提示我们填写工作细节。

让我们创建一个Cron Job和一个Simple Job。

Simple Job

Cron Job

我们可以在JobStore中看到所有添加的作业。

Job Management Console

你还可以查看工作日志--当工作被触发时,根据他们的触发标准,一个一个地被触发。

总结

在本指南中,我们介绍了Quartz--一个强大的调度器,并在Spring Boot应用程序中实施它。

我们研究了Quartz调度器的整体生命周期管理,并在一个简单的用户界面上做了演示。我们使用了一些最低限度的工作,但你也可以尝试定义复杂的工作,如发射电子邮件警报或异步消息处理等,并使用Quartz调度。