欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 教育 > 高考 > Quartz定时任务

Quartz定时任务

2025/2/26 0:08:14 来源:https://blog.csdn.net/GuoShao_/article/details/140628605  浏览:    关键词:Quartz定时任务

Qquartz定时任务

  • 一、任务调度概念
    • 1.1 什么是任务调度
    • 1.2 为什么要使用分布式调度
  • 二、Quartz快速上手
  • 三、CronExpression
  • 四、传入变量,依赖注入
    • 4.1 传入变量
    • 4.2 依赖注入
  • 五、Quartz配置
  • 六、SpringBoot整合Quartz
    • 6.1 定义Job和trigger--第一种方式
    • 6.1 定义Job和trigger--第二种方式
  • 七、Quartz持久化

一、任务调度概念

1.1 什么是任务调度

任务调度是为了自动完成特定任务,在约定时刻去执行任务的过程。

如:

  • 每个月初对用户进行缴费提醒
  • 每天定时12:00给用户发放优惠券

1.2 为什么要使用分布式调度

在spring中提供了注解@Scheduled,也能够实现任务调度的功能。具体步骤:

  • 在业务类的方法上加上注解,写上cron表达式:
@Scheduled(cron = "0/20 * * * * ?")
public void method(){//do task
}
  • 在启动类上加上@EnableScheduling注解开启顶定时调度功能

spring提供了单机版的定时任务调度,那为什么还要使用分布式定时任务Quartz呢?

  1. 高可用:
    单机版的定时任务调度只能在一台机器上运行,如果程序或者系统出现异常就会导致功能不可用。
  2. 方式重复执行:
    当我们部署了多台服务器,同时又每台都有定时任务,若不进行合理的控制确保只有一个定时任务启动执行,定时执行的结果就会存在混乱和错误。(如积分的重复累加)
  3. 单机处理极限:
    单机能力有限(CPU、内存和磁盘),会存在单机处理不过来的情况

二、Quartz快速上手

  1. 引入包
        <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-quartz</artifactId></dependency>
  1. 测试案例
  import org.quartz.Scheduler;import org.quartz.SchedulerException;import org.quartz.impl.StdSchedulerFactory;import static org.quartz.JobBuilder.*;import static org.quartz.TriggerBuilder.*;import static org.quartz.SimpleScheduleBuilder.*;public class QuartzTest {public static void main(String[] args) {try {// 使用 StdSchedulerFactory.getDefaultScheduler() 方法从标准工厂中获取默认的调度器实例Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();// 使用 scheduler.start() 方法启动调度器,使其能够调度和执行任务scheduler.start();//使用 scheduler.shutdown() 方法关闭调度器,停止所有正在进行的任务调度scheduler.shutdown();} catch (SchedulerException se) {se.printStackTrace();}}}
  1. 输出:
    在这里插入图片描述
    到此,运行环境配置完成。

  2. 添加调度任务

任务类:

package com.example.quartz_task.job;import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;public class HelloJob implements Job {@Overridepublic void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {System.out.println("hello world");}
}
package com.guo.quartztest;import org.quartz.JobDetail;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.Trigger;
import org.quartz.impl.StdSchedulerFactory;
import static org.quartz.JobBuilder.*;
import static org.quartz.TriggerBuilder.*;
import static org.quartz.SimpleScheduleBuilder.*;public class QuartzTest {public static void main(String[] args) {try {// Grab the Scheduler instance from the FactoryScheduler scheduler = StdSchedulerFactory.getDefaultScheduler();// and start it offscheduler.start();// 定义一个任务并将其与HelloJob类关联JobDetail job = newJob(HelloJob.class)  // 使用newJob方法创建一个JobDetail实例,指定任务类为HelloJob.withIdentity("job1", "group1") // 设置任务的名称为"job1",组名为"group1".build();  // 构建JobDetail实例// 定义一个触发器,立即运行任务,然后每40秒重复一次Trigger trigger = newTrigger()  // 使用newTrigger方法创建一个Trigger实例.withIdentity("trigger1", "group1") // 设置触发器的名称为"trigger1",组名为"group1".startNow()  // 设置触发器立即生效.withSchedule(simpleSchedule()  // 设置触发器的调度计划.withIntervalInSeconds(40) // 设置任务执行的时间间隔为40秒.repeatForever()) // 设置任务无限次重复执行.build();  // 构建Trigger实例// 告诉Quartz使用我们的触发器调度任务scheduler.scheduleJob(job, trigger);  // 使用scheduleJob方法将任务和触发器注册到调度器scheduler.shutdown();} catch (SchedulerException se) {se.printStackTrace();}}
}

输出:
在这里插入图片描述


上面的任务调度流程是:定义任务和触发器;然后通过调度器关联任务和触发器。

我们也可以不通过调度器关联任务和触发器,在定义触发器的时候就指定触发的任务。

任务调度流程变成:

  • 定义任务:
    代码种添加.storeDurably()。将Job 设置为“持久的”,不会因为没有 Trigger 而从调度器中移除。
    在没有立即调度的情况下添加一个 Job,必须将Job标记为持久的
  • 定义触发器并关联任务
    代码中添加.forJob("job","group1")
  • 使用调度器启动触发器。
    代码添加:scheduler.addJob(job, false); 将一个 JobDetail对象添加到调度器中。如果不这样做,调度器不会记录该 Job,因此在你试图调度或执行这个 Job时,调度器将无法找到它,并可能抛出异常。

代码如下:

			// Grab the Scheduler instance from the FactoryScheduler scheduler = StdSchedulerFactory.getDefaultScheduler();// and start it offscheduler.start();// 定义一个任务并将其与HelloJob类关联JobDetail job = newJob(HelloJob.class)  // 使用newJob方法创建一个JobDetail实例,指定任务类为HelloJob.storeDurably()//设置为持久的.withIdentity("job", "group1") // 设置任务的名称为"job1",组名为"group1".build();  // 构建JobDetail实例// 定义一个触发器,立即运行任务,然后每40秒重复一次Trigger trigger = newTrigger()  // 使用newTrigger方法创建一个Trigger实例.withIdentity("trigger1", "group1") // 设置触发器的名称为"trigger1",组名为"group1".forJob("job","group1").startNow()  // 设置触发器立即生效.withSchedule(simpleSchedule()  // 设置触发器的调度计划.withIntervalInSeconds(40) // 设置任务执行的时间间隔为40秒.repeatForever()) // 设置任务无限次重复执行.build();  // 构建Trigger实例//将job告诉调度器scheduler.addJob(job,false);// 告诉Quartz使用我们的触发器调度任务scheduler.scheduleJob(trigger); Thread.sleep(20);scheduler.shutdown();

上面涉及到的任务、调度器、触发器的逻辑框架如下图:

在这里插入图片描述

  1. 创建任务调度器,去启动触发器
  2. 触发器通过相应的规则,去调度任务
  3. 任务的一些属性包装在JobDetail里

调度器、触发器、任务详情之间的关系:

  • 调度器可以调度多个触发器
  • 触发器只能调度一个任务
  • 一个JobDetail可以被多个触发器调度
  • 一个Job可以关联多个JobDetail

三、CronExpression

在定义触发器的时候,可以使用Cron表达式来替代函数式的定义触发规则:

// 定义一个触发器,立即运行任务,然后每40秒重复一次Trigger trigger = newTrigger()  // 使用newTrigger方法创建一个Trigger实例.withIdentity("trigger1", "group1") // 设置触发器的名称为"trigger1",组名为"group1".startNow()  // 设置触发器立即生效.withSchedule(CronScheduleBuilder.cronSchedule("* * * * * ? *")) // 设置任务无限次重复执行.build();  // 构建Trigger实例

* * * * * ? *解读:
在这里插入图片描述
符合解释:
在这里插入图片描述


四、传入变量,依赖注入

4.1 传入变量

场景:需要在任务调度的时候,给任务Job传递一些参数

  1. 使用usingJobData
    • JobDetail的usingJobData
    • 也可在trigger上使用usingJobData
// 定义一个任务并将其与HelloJob类关联
JobDetail job = newJob(HelloJob.class)  // 使用newJob方法创建一个JobDetail实例,指定任务类为HelloJob.withIdentity("job1", "group1") // 设置任务的名称为"job1",组名为"group1".usingJobData("key1","value1")//传递参数.build();  // 构建JobDetail实例
// 定义一个触发器,立即运行任务,然后每40秒重复一次Trigger trigger = newTrigger()  // 使用newTrigger方法创建一个Trigger实例.withIdentity("trigger1", "group1") // 设置触发器的名称为"trigger1",组名为"group1".usingJobData("key2","value2").startNow()  // 设置触发器立即生效.withSchedule(simpleSchedule()  // 设置触发器的调度计划.withIntervalInSeconds(40) // 设置任务执行的时间间隔为40秒.repeatForever()) // 设置任务无限次重复执行.build();  // 构建Trigger实例
  1. 任务类
package com.example.quartz_task.job;import org.quartz.*;public class HelloJob implements Job {@Overridepublic void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {//通过上下文获取任务详情JobDetail jobDetail = jobExecutionContext.getJobDetail();//通过上下文获取触发器详情Trigger trigger = jobExecutionContext.getTrigger();//获取数据System.out.println(jobDetail.getJobDataMap().get("key1"));System.out.println(trigger.getJobDataMap().get("key2"));}
}

输出:
在这里插入图片描述

此外也可以通过jobExecutionContext.getMergedJobDataMap().get("key");来获取相应的键值对数据。

如果detailJob和trigger存在相同的key呢?
jobExecutionContext.getMergedJobDataMap()会获取哪一个的?
这里我们将两者的key都改成"key",最后输出的是“value2”,因此在detailJob和trigger都存在相同的键,会优先获取trigger的键值对值。
在这里插入图片描述

如果某个key是经常使用的,可以将其作为Job类的属性。而不用通过get方法去获取了。

4.2 依赖注入

以上的方式都是固定死的传值方式。在实际应用中,我们通常会结合spring以获取一个动态变化的值。

如,下面存在一个service类,在实际应用中,希望将这个service的返回结果作为参数传入。

package service;import org.springframework.stereotype.Service;@Service
public class HelloService {public String hello(){return "say hello";}
}

在任务类:

@Component
public class HelloJob implements Job {@Autowiredprivate HelloService helloService;@Overridepublic void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {System.out.println(helloService);}
}

但是发现在启动Spring项目后,输出的却是null:
在这里插入图片描述

这是因为,触发器在触发任务的时候,quartz会对Job进行实例化;因为实例化是由quartz实例化的,因此Spring注解这些quartz是不能够识别的,因此不存在依赖注入。

我们可以通过Spring的上下文去获取的这个服务的输出:

  1. 先构建一个能够让外部获取上下文的类
package com.example.quartz_task.util;import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;@Component
public class SpringContextUtil implements ApplicationContextAware {public static ApplicationContext applicationContext;@Overridepublic void setApplicationContext(ApplicationContext applicationContext) throws BeansException {this.applicationContext = applicationContext;}
}
  1. 通过类获取Spring的bean对象
package com.example.quartz_task.job;import com.example.quartz_task.util.SpringContextUtil;
import org.quartz.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import com.example.quartz_task.service.HelloService;
@Component
public class HelloJob implements Job {@Autowiredprivate HelloService helloService;@Overridepublic void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {//通过Spring上下文获取Bean对象helloService = SpringContextUtil.applicationContext.getBean(HelloService.class);System.out.println(helloService.hello());}
}

输出:
在这里插入图片描述


五、Quartz配置

quartz存在一个配置文件quartz.properties,如果自己不定义就会采取默认值。quartz.properties内容如下:

# Default Properties file for use by StdSchedulerFactory
# to create a Quartz Scheduler Instance, if a different
# properties file is not explicitly specified.
##指定调度器实例的名称,默认值为 DefaultQuartzScheduler。
org.quartz.scheduler.instanceName: DefaultQuartzSchedulerorg.quartz.scheduler.rmi.export: false
org.quartz.scheduler.rmi.proxy: false
org.quartz.scheduler.wrapJobExecutionInUserTransaction: falseorg.quartz.threadPool.class: org.quartz.simpl.SimpleThreadPool#指定线程池中的线程数,默认值为 10。
org.quartz.threadPool.threadCount: 10#指定线程池中线程的优先级,范围是 1(最低)到 10(最高)。这里的优先级设置为 5。
org.quartz.threadPool.threadPriority: 5
org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread: true#指定 Quartz 处理触发器错失(misfire)时间的阈值,单位为毫秒。这里设置为 60000 毫秒(即 60 秒)。
org.quartz.jobStore.misfireThreshold: 60000#指定 Quartz 的作业存储类型,这里使用 RAMJobStore,即将所有调度数据保存在内存中,而不是持久化到数据库。
org.quartz.jobStore.class: org.quartz.simpl.RAMJobStore

配置参数详细信息可以看官网配置信息介绍

六、SpringBoot整合Quartz

官网文档

在这里插入图片描述

根据官网,Spring Boot会自动为我们配置调度器,所以我们不需要通过工厂的方式去定义一个调度器了。

接下来只需要我们手动的定义JobDetail和Trigger的Bean,Spring Boot会自动获取并与Scheduler关联。

6.1 定义Job和trigger–第一种方式

class MySJob extends QuartzJobBean {@Overrideprotected void executeInternal(JobExecutionContext context) throws JobExecutionException {}
}

QuartzJobBean是实现了Job的一个抽象类,里面对原来的execute进行了修改(添加了Spring的一些东西),然后再调用executeInternal。源码如下,因此我们从原来的方式改成:继承QuartzJobBean并重新executeInternal

public abstract class QuartzJobBean implements Job {
public QuartzJobBean() {
}public final void execute(JobExecutionContext context) throws JobExecutionException {try {BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);MutablePropertyValues pvs = new MutablePropertyValues();pvs.addPropertyValues(context.getScheduler().getContext());pvs.addPropertyValues(context.getMergedJobDataMap());bw.setPropertyValues(pvs, true);} catch (SchedulerException var4) {throw new JobExecutionException(var4);}this.executeInternal(context);
}protected abstract void executeInternal(JobExecutionContext context) throws JobExecutionException;
}

详细步骤:

  1. 定义Job
package com.example.quartz_task.job;import com.example.quartz_task.service.HelloService;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.quartz.QuartzJobBean;public class MyJob extends QuartzJobBean {@Autowiredprivate HelloService helloService;@Overrideprotected void executeInternal(JobExecutionContext context) throws JobExecutionException {System.out.println(helloService.hello());}
}
  1. 定义JobDetail和Trigger
package com.example.quartz_task.config;import com.example.quartz_task.job.MyJob;
import jakarta.annotation.PostConstruct;
import org.quartz.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;@Component
public class QuartzConfig {@Autowiredprivate Scheduler scheduler;@PostConstructpublic void initJob() throws SchedulerException {JobDetail jobDetail = JobBuilder.newJob(MyJob.class).build();Trigger trigger = TriggerBuilder.newTrigger().startNow().build();scheduler.scheduleJob(jobDetail,trigger);}
}
  1. Spring的服务类
package com.example.quartz_task.service;import org.springframework.stereotype.Service;@Service
public class HelloService {public String hello(){return "say hello";}
}
  1. 启动服务,输出:
    在这里插入图片描述

官网中,提到了:
在这里插入图片描述

6.1 定义Job和trigger–第二种方式

所以提供了第二种编写JobDtail和Trigger的方式:

package com.example.quartz_task.config;import com.example.quartz_task.job.MyJob;
import jakarta.annotation.PostConstruct;
import org.quartz.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;@Configuration
public class QuartzConfig {@Beanpublic JobDetail SpringJobDetail(){return JobBuilder.newJob(MyJob.class).withIdentity("springJobDetail").storeDurably().build();}@Beanpublic Trigger springJobTrigger(){return TriggerBuilder.newTrigger().forJob("springJobDetail").startNow().build();}
}

七、Quartz持久化

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com

热搜词