Quartz是OpenSymphony开源组织在Job scheduling领域又一个开源项目,完全由java语言开发,支持分布式、集群部署,且具有丰富的调度方式。
quartz主要是用于进行定时任务的执行。
实现该接口后,重写其中的execute方法,该方法就是我们需要执行的任务(业务逻辑代码)。
该对象的作用是绑定一个Job对象,然后对其进行描述,主要有如下的几个属性
该对象是一个触发器,在绑定任务后,设置任务的执行时间,结束时间,执行间隔,执行频率等。
该触发器主要有4中指定触发规则的Tigger。
该对象用于绑定JobDetail和Tigger,以此来进行任务的调度(执行)。
quartz官方提供了2种创建Scheduler对象的工厂类
quartz使用的是slf4j日志门面,但是还没有具体的实现。
所以这里引入Logback的日志实现,在pom文件中新增依赖。
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz</artifactId>
<version>2.3.0</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>
<?xml version="1.0" encoding="UTF-8"?>
<configuration scan="true" scanPeriod="60 seconds" debug="false" >
<!-- 日志级别 -->
<property name="logLevel" value="INFO"/>
<!-- 配置输出格式 -->
<property name="logPattern" value="%d{yyyy-MM-dd HH:mm:ss} [%-5level] %logger - %msg%n"/>
<!-- 控制台打印日志的相关配置 -->
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<!-- 日志格式 -->
<encoder>
<charset>UTF-8</charset>
<pattern>${logPattern}</pattern>
</encoder>
</appender>
<root level="${logLevel}">
<appender-ref ref="STDOUT" />
</root>
</configuration>
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
public class DateJob implements Job {
@Override
public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
String currentDate = LocalDateTime.now().format(dtf);
System.out.println(currentDate);
}
}
import com.it.job.DateJob;
import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;
public class QuartzTest {
public static void main(String[] args) throws SchedulerException {
// 创建任务详情JobDetail,绑定任务,并且指定任务的名称和组名
JobDetail jobDetail = JobBuilder.newJob(DateJob.class)
.withIdentity("job1","g1")
.build();
// 创建一个触发器,绑定JobDetail
// 设置了触发器的名称、组名、以及立即开始、并且指定触发规则为5秒执行一次,无限重复
Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity("t1","g1")
.forJob(jobDetail)
.startNow()
.withSchedule(SimpleScheduleBuilder.repeatSecondlyForever(5))
.build();
// 创建Scheduler对象进行任务与Trigger的协调,并开启任务调度
Scheduler scheduler = new StdSchedulerFactory().getScheduler();
scheduler.scheduleJob(jobDetail, trigger);
scheduler.start();
}
}
Scheduler每次进行调度任务时,会通过jobDetail来找到对应的Job,每次执行任务时,都会new一个新的Job实例。所以Job默认是无状态的。
Scheduler对象进行任务调度时,可以只指定一个Trigger触发器,而不指定JobDetail,前提是JobDetail已经交由Scheduler管理
@Override
public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
// 获取当前的JobDetail中的自定义数据
String JobDetailMyKey1 = jobExecutionContext.getJobDetail().getJobDataMap().getString("myKey1");
System.out.println("JobDetail中的Key1:" + JobDetailMyKey1);
// 获取当前的JobDetail中的自定义数据
String TriggerMyKey1 = jobExecutionContext.getTrigger().getJobDataMap().getString("myKey1");
System.out.println("Trigger中的Key1:" + TriggerMyKey1);
// 获取JobDetail与Trigger合并后的自定义数据,如果两者key有冲突,则会使用Trigger的自定义数据覆盖JobDetail的自定义数据
String myKey1 = jobExecutionContext.getMergedJobDataMap().getString("myKey1");
System.out.println("合并后出现重复的key的数据:" + myKey1);
}
quartz应用程序默认在启动时,会在类路径查找quartz.properties文件作为配置文件的加载。
但是如果类路径下没有该配置文件,则会默认加载在org/quartz/文件夹下的quartz.properties文件
默认的配置文件如下,我只是加了个不检查版本更新的配置。
# 如果使用集群模式,则该实例名则是区分集群的唯一标识
org.quartz.scheduler.instanceName = DefaultQuartzScheduler
# 如果希望Quartz Scheduler通过RMI作为服务器导出本身,则为true。
org.quartz.scheduler.rmi.export = false
# 如果要连接(使用)远程服务的调度程序,则为true。还必须指定RMI注册表进程的主机和端口 - 通常是“localhost”端口1099
org.quartz.scheduler.rmi.proxy = false
# 设置这项为true使我们在调用job的execute()之前能够开始一个UserTransaction。
org.quartz.scheduler.wrapJobExecutionInUserTransaction = false
# 程序运行期间,跳过quartz版本更新的检查
org.quartz.scheduler.skipUpdateCheck=true
# 指定的线程池
org.quartz.threadPool.class = org.quartz.simpl.SimpleThreadPool
# 线程的数量
org.quartz.threadPool.threadCount = 10
# 线程优先级
org.quartz.threadPool.threadPriority = 5
# 自创建父线程
org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread = true
# 最大能忍受的触发超时时间
org.quartz.jobStore.misfireThreshold = 60000
# 数据保存方式为内存中
org.quartz.jobStore.class = org.quartz.simpl.RAMJobStore
<properties>
<spring.version>5.2.3.RELEASE</spring.version>
</properties>
<dependencies>
<!-- spring ioc -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-expression</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>${spring.version}</version>
</dependency>
<!-- 注意别少了这个包 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.2</version>
</dependency>
<!-- quartz -->
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz</artifactId>
<version>2.2.1</version>
</dependency>
<!-- 提供一些quartz的支持工具,例如发送邮件的定时任务 -->
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz-jobs</artifactId>
<version>2.2.1</version>
</dependency>
</dependencies>
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.springframework.context.ApplicationContext;
public class MySimpleJob implements Job{
@Override
public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
// 获取到Spring容器
ApplicationContext ac = (ApplicationContext) jobExecutionContext.getMergedJobDataMap().get("applicationContext");
System.out.println(ac);
// 获取到自定义的参数
String key1 = jobExecutionContext.getJobDetail().getJobDataMap().getString("key1");
System.out.println("key1的值为:" + key1);
}
}
<!-- 定义JobDetail ,这里使用JobDetailFactoryBean,也可以使用MethodInvokingJobDetailFactoryBean ,配置类似-->
<bean id="jobDetail" class="org.springframework.scheduling.quartz.JobDetailFactoryBean">
<!-- 指定job的名称 -->
<property name="name" value="job1"/>
<!-- 指定job的分组 -->
<property name="group" value="group1"/>
<!-- 指定具体的job类 -->
<property name="jobClass" value="com.it.job.MySimpleJob"/>
<!-- 必须设置为true,如果为false,当没有活动的触发器与之关联时会在调度器中会删除该任务 -->
<property name="durability" value="true"/>
<!-- 指定spring容器的key,如果不设定在job中的jobDataMap中是获取不到spring容器的 -->
<property name="applicationContextJobDataKey" value="applicationContext"/>
<!-- 定义自定义的数据 -->
<property name="jobDataAsMap">
<map>
<entry key="key1" value="value1"/>
</map>
</property>
</bean>
<!-- 配置Trigger触发器 -->
<!-- 第一种 SimpleTriggerBean,只支持按照一定频度调用任务,如每隔30分钟运行一次。配置方式如下: -->
<!-- <bean id="simpleTrigger"
class="org.springframework.scheduling.quartz.SimpleTriggerFactoryBean">
<property name="jobDetail" ref="jobDetail"></property>
<property name="startDelay" value="3000"></property>
<property name="repeatInterval" value="2000"></property>
</bean> -->
<!-- 第二种 CronTriggerBean,支持到指定时间运行一次,每隔2秒执行一次。配置方式如下: -->
<bean id="cronTrigger" class="org.springframework.scheduling.quartz.CronTriggerFactoryBean">
<property name="jobDetail" ref="jobDetail" />
<!-- <!—每天12:00运行一次 —> -->
<property name="cronExpression" value="0/2 * * * * ?" />
</bean>
<!-- 配置调度工厂scheduler,进行任务的调度 -->
<bean id="schedulerFactoryBean" lazy-init="true"
class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
<property name="triggers">
<list>
<!-- <ref bean="testTrigger"></ref> -->
<ref bean="cronTrigger" />
</list>
</property>
</bean>
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 {
private static ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
if (SpringContextUtil.applicationContext == null) {
SpringContextUtil.applicationContext = applicationContext;
}
}
public static ApplicationContext getApplicationContext() {
return applicationContext;
}
/**
* 通过bean的id属性获取bean
* @param name bean的id
* @param <T> 强制转换的类型
* @return
*/
public static <T> T getBean(String name) {
return (T) getApplicationContext().getBean(name);
}
/**
* 通过类型获取到bean
* @param clazz 该类型的Class
* @param <T>
* @return
*/
public static <T> T getBean(Class<T> clazz) {
return getApplicationContext().getBean(clazz);
}
/**
* 通过bean的id以及类型获取到bean
* @param name
* @param clazz
* @param <T>
* @return
*/
public static <T> T getBean(String name, Class<T> clazz) {
return getApplicationContext().getBean(name, clazz);
}
}
- 前提是这个id为user的bean已经加入到了spring的容器中
public User user = SpringContextUtil.getBean("user");
public class MyTest {
public static void main(String[] args) {
ApplicationContext ac = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
}
}
import com.it.entity.User;
import org.quartz.Scheduler;
import org.springframework.beans.factory.annotation.Autowired;
//@Component ,由于在spring配置文件中配置过了,所以不用配置该注解
public class MySimpleJob2{
@Autowired
private User user;
@Autowired
public Scheduler scheduler;
public void show() {
// 这是自动注入的User类,我这里随便的User类为: class User{}
System.out.println("依赖注入的User对象: " + user);
// 当前项目的Scheduler对象
System.out.println("Scheduler对象:" + scheduler);
}
}
<bean id="user" class="com.it.entity.User"/>
<!-- 定义需要执行的job类 -->
<bean id="myJob" class="com.it.job.MySimpleJob2" />
<!-- 定义JobDetail 使用MethodInvokingJobDetailFactoryBean -->
<bean id="jobDetail" class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
<!-- 指定job的名称 -->
<property name="name" value="job1"/>
<!-- 指定job的分组 -->
<property name="group" value="group1"/>
<!-- 指定具体的job类 -->
<property name="targetObject" ref="myJob"/>
<!-- 指定job类中的方法为定时任务 -->
<property name="targetMethod" value="show" />
</bean>
<dependencies>
<!-- spring ioc -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-expression</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>${spring.version}</version>
</dependency>
<!-- 注意别少了这个包 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.2</version>
</dependency>
<!-- 数据库连接 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.25</version>
</dependency>
<!-- quartz -->
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz</artifactId>
<version>2.2.1</version>
</dependency>
<!-- 提供一些quartz的支持工具,例如发送邮件的定时任务 -->
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz-jobs</artifactId>
<version>2.2.1</version>
</dependency>
<!-- 非必须,如果想要查看quartz日志,可以引入,然后在类路径下配置一个logback.xml文件-->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>
</dependencies>
<!-- 引入配置文件 -->
<context:property-placeholder location="classpath:jdbc.properties"/>
<context:component-scan base-package="com.it"/>
<!-- 配置数据源 -->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
<!-- 配置调度工厂scheduler,进行任务的调度 -->
<bean name="quartzScheduler" lazy-init="false"
class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
<property name="dataSource" ref="dataSource"/>
<!-- 可以通过scheduler.getContext().get("applicationContextKey") 获取到Spring容器对象 -->
<property name="applicationContextSchedulerContextKey" value="applicationContextKey" />
<!-- 是否自动启动 -->
<property name="autoStartup" value="true" />
<!-- 其实也可以直接指定quartz的配置文件,然后在配置文件中配置,本质是一样的 -->
<property name="quartzProperties">
<props>
<prop key="org.quartz.jobStore.class">org.quartz.impl.jdbcjobstore.JobStoreTX</prop>
<prop key="org.quartz.jobStore.driverDelegateClass">org.quartz.impl.jdbcjobstore.StdJDBCDelegate</prop>
</props>
</property>
</bean>
public class MyJob1 extends QuartzJobBean {
@Override
protected void executeInternal(JobExecutionContext jobExecutionContext) throws JobExecutionException {
System.out.println("你好呀,我很好,是的");
}
}
@Component
public class JobInit {
@Autowired
private Scheduler scheduler;
@PostConstruct
public void init() throws Exception {
// 创建任务详情JobDetail,绑定任务,并且指定任务的名称和组名
JobDetail jobDetail = JobBuilder.newJob(MyJob1.class)
.withIdentity("job4","g1")
.build();
// 创建一个触发器,绑定JobDetail
// 设置了触发器的名称、组名、以及立即开始、并且指定触发规则为5秒执行一次,无限重复
Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity("t3","g1")
.forJob(jobDetail)
.startNow()
.withSchedule(SimpleScheduleBuilder.repeatSecondlyForever(5))
.build();
// 创建Scheduler对象进行任务与Trigger的协调,并开启任务调度
scheduler.scheduleJob(jobDetail, trigger);
scheduler.start();
}
}
<!-- 配置调度工厂scheduler,进行任务的调度 -->
<bean name="quartzScheduler" lazy-init="false"
class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
<property name="dataSource" ref="dataSource"/>
<!-- 可以通过scheduler.getContext().get("applicationContextKey") 获取到Spring容器对象 -->
<property name="applicationContextSchedulerContextKey" value="applicationContextKey" />
<!-- 是否自动启动 -->
<property name="autoStartup" value="true" />
<!-- 其实也可以直接指定quartz的配置文件,然后在配置文件中配置,本质是一样的 -->
<property name="quartzProperties">
<props>
<!-- 持久化配置-->
<prop key="org.quartz.jobStore.class">org.quartz.impl.jdbcjobstore.JobStoreTX</prop>
<prop key="org.quartz.jobStore.driverDelegateClass">org.quartz.impl.jdbcjobstore.StdJDBCDelegate</prop>
<!-- 集群配置 -->
<prop key="org.quartz.jobStore.isClustered">true</prop>
<!-- 注意这个相当于集群的名字,而quartz的集群是通过所连接的数据库来判定的 -->
<prop key="org.quartz.scheduler.instanceName">myCluster</prop>
<prop key="org.quartz.scheduler.instanceId">AUTO</prop>
</props>
</property>
</bean>
public class MyJob extends QuartzJobBean {
@Override
protected void executeInternal(JobExecutionContext jobExecutionContext) throws JobExecutionException {
// 获取当前系统时间
String thisDateTime = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date());
System.out.println(thisDateTime + " 我是一个任务,任务名为:" + jobExecutionContext.getJobDetail().getKey().getName());
}
}
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = { "classpath:applicationContext.xml" })
public class JTest {
@Autowired
private Scheduler scheduler;
@Test
public void addJob() throws Exception{
JobDetail job1 = JobBuilder.newJob(MyJob.class).withIdentity("job1").build();
JobDetail job2 = JobBuilder.newJob(MyJob.class).withIdentity("job2").build();
JobDetail job3 = JobBuilder.newJob(MyJob.class).withIdentity("job3").build();
Trigger trigger1 = TriggerBuilder.newTrigger().forJob(job1).withSchedule(SimpleScheduleBuilder.repeatSecondlyForever(5)).build();
Trigger trigger2 = TriggerBuilder.newTrigger().forJob(job2).withSchedule(SimpleScheduleBuilder.repeatSecondlyForever(5)).build();
Trigger trigger3 = TriggerBuilder.newTrigger().forJob(job3).withSchedule(SimpleScheduleBuilder.repeatSecondlyForever(5)).build();
scheduler.scheduleJob(job1, trigger1);
scheduler.scheduleJob(job2, trigger2);
scheduler.scheduleJob(job3, trigger3);
scheduler.start();
}
}
然后就会看到集群环境下任务的执行了。至于时间,大概是因为刚好卡点。
想一下,我们配置redis集群时,是不是需要指定集群的ip地址,而我们使用quartz却并没有指定。
import org.quartz.*;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
public class StatusJob implements Job {
@Override
public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
try {
// 演示并发问题,主要是任务执行的时间为3秒,但是间隔执行时间为2秒,这个时候会出现的问题有哪些
String nowTime = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
System.out.println(nowTime + " 我是job任务,嘿嘿");
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
import com.it.job.StatusJob;
import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;
public class MyTest {
public static void main(String[] args) throws Exception {
Scheduler scheduler = new StdSchedulerFactory().getScheduler();
scheduler.start();
JobDetail jobDetail = JobBuilder
.newJob(StatusJob.class)
.build();
Trigger trigger = TriggerBuilder
.newTrigger()
.startNow()
.forJob(jobDetail)
.withSchedule(
SimpleScheduleBuilder.repeatSecondlyForever(2)
)
.build();
scheduler.scheduleJob(jobDetail, trigger);
}
}
由于Scheduler在帮助执行我们的任务时,每一次通过Trigger触发任务时,都会初始化一个Job,所以导致Job任务之间的数据不共享。
虽然我们可以通过静态变量来解决此问题,但是quartz为我们提供了@PersistJobDataAfterExecution注解来帮我们实现了Job任务之间的数据共享。主要是通过JobDetail的JobDataMap来完成数据的共享。
在Job类上加上该注解时,JobDetail中的JobDataMap中的数据就可以传递给下一个任务,否则无法传递
注意一件事:Trigger中的JobDataMap数据并不会进行传递。
import org.quartz.*;
public class StatusJob implements Job {
@Override
public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
// 演示数据共享问题
// 获取自定义的count数据
JobDataMap jobDataMap = jobExecutionContext.getJobDetail().getJobDataMap();
int count = jobDataMap.getInt("count");
System.out.println("当前Job中的count的值为:" + count);
// 将JobDataMap中的count数据 加1
jobDataMap.put("count", ++count);
}
}
import com.it.job.StatusJob;
import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;
public class MyTest {
public static void main(String[] args) throws Exception {
Scheduler scheduler = new StdSchedulerFactory().getScheduler();
scheduler.start();
JobDetail jobDetail = JobBuilder
.newJob(StatusJob.class)
.usingJobData("count",1)
.build();
Trigger trigger = TriggerBuilder
.newTrigger()
.startNow()
.forJob(jobDetail)
.withSchedule(
SimpleScheduleBuilder.repeatSecondlyForever(2)
)
.build();
scheduler.scheduleJob(jobDetail, trigger);
}
}
# 最大能忍受的触发超时时间,单位为毫秒,默认为60000(60秒)
# 当任务执行时间到了时,由于某些原因导致的任务没有按时执行,并且超过了最大的触发超时时间,则认定为该任务失火
# 例如任务本应该在 09:00执行,但是却超过了默认的失火时间60秒都没有触发执行,也就是在09:01时还没有执行,则该任务失火
# 这里我为了演示效果改为1000毫秒 (1秒)
org.quartz.jobStore.misfireThreshold = 1000
注意一点: 如果没有超过失火时间(也就是任务没有失火),那么scheduler则会在可触发任务的同一时间直接执行未触发(多个)的任务。
public class SimpleJob implements Job {
@Override
public void execute(JobExecutionContext context) throws JobExecutionException {
String nowTime = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
System.out.println(nowTime + " 这是一条普通的任务");
}
}
package com.it.test;
import com.it.job.SimpleJob;
import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;
import java.util.Date;
import java.util.Calendar;
/**
* @author dengqixing
* @date 2021/8/28
*/
public class MyTest {
public static void main(String[] args) throws SchedulerException {
Scheduler scheduler = new StdSchedulerFactory().getScheduler();
scheduler.start();
JobDetail jobDetail = JobBuilder.newJob(SimpleJob.class).build();
// 使任务在前3秒执行。
Calendar calendar = Calendar.getInstance();
calendar.set(Calendar.SECOND,calendar.get(Calendar.SECOND) - 5);
Date date = calendar.getTime();
System.out.println(date);
System.out.println(new Date());
Trigger trigger = TriggerBuilder
.newTrigger()
.startAt(date)
// 每2秒执行一次,重复3次,所以就是执行4次,
// 由于这里每2秒执行一次,所以按照如上设 置的前5秒为开始时间,则会失火3次
.withSchedule(
SimpleScheduleBuilder.repeatSecondlyForever(2).withRepeatCount(3)
)
.forJob(jobDetail)
.build();
scheduler.scheduleJob(jobDetail, trigger);
}
}
-- 默认失火策略(不理睬任务的失火,从当前时间重新执行任务)
默认即便任务抛出异常了,那么也会当作一次正确的任务触发,不会影响接下来的任务触发
@PersistJobDataAfterExecution
public class SimpleJob implements Job {
@Override
public void execute(JobExecutionContext context) throws JobExecutionException {
String nowTime = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
try {
// 获取自定义数据boolean类型的flag
if (context.getJobDetail().getJobDataMap().getBoolean("flag")) {
System.out.println(nowTime + " 这是一条普通的任务");
} else {
throw new Exception("flag不为true");
}
} catch (Exception e) {
// 解决掉异常问题,记得别忘了修改job的状态(添加注解)
context.getJobDetail().getJobDataMap().put("flag", true);
// 重新调用一次任务
JobExecutionException jobExecutionException = new JobExecutionException(e);
// 立即执行一次
jobExecutionException.setRefireImmediately(true);
// 抛出该quartz提供的异常
throw jobExecutionException;
}
}
}
public class MyTest {
public static void main(String[] args) throws SchedulerException {
Scheduler scheduler = new StdSchedulerFactory().getScheduler();
scheduler.start();
JobDetail jobDetail = JobBuilder
.newJob(SimpleJob.class)
.usingJobData("flag",false)
.build();
Trigger trigger = TriggerBuilder
.newTrigger()
.startNow()
// 每2秒执行一次,一共4次
.withSchedule(
SimpleScheduleBuilder.repeatSecondlyForever(2).withRepeatCount(3)
)
.forJob(jobDetail)
.build();
scheduler.scheduleJob(jobDetail, trigger);
}
}
// 取消所有关联了该Job任务的Trigger的触发
jobExecutionException.setUnscheduleFiringTrigger(true);
// 取消正在触发该任务的Trigger的触发
jobExecutionException.setUnscheduleFiringTrigger(true);
public class MyInterruptJob implements InterruptableJob {
private boolean interruptFlag;
@Override
public void interrupt() throws UnableToInterruptJobException {
interruptFlag = true;
System.out.println("该任务已经被中断");
}
@Override
public void execute(JobExecutionContext context) throws JobExecutionException {
for (int i = 0; i < 5; i++) {
if (!interruptFlag) {
System.out.println("i等于: " + i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
public class InterruptTest {
public static void main(String[] args) throws SchedulerException, InterruptedException {
Scheduler scheduler = new StdSchedulerFactory().getScheduler();
scheduler.start();
JobDetail jobDetail = JobBuilder
.newJob(MyInterruptJob.class)
.build();
Trigger trigger = TriggerBuilder
.newTrigger()
.startNow()
.forJob(jobDetail)
.build();
scheduler.scheduleJob(jobDetail, trigger);
// 注意这里睡了2秒,然后中断了任务
Thread.sleep(2000);
// 在这儿的作用就是中断第一次任务触发
scheduler.interrupt(jobDetail.getKey());
}
}
为什么需要进行日期排除?因为有些时间我们不需要进行任务的触发,例如节假日不上班呐之类的。
public class MyJob implements Job {
@Override
public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
System.out.println("Job任务执行了");
}
}
记住步骤
public class QuartzTest {
public static void main(String[] args) throws SchedulerException {
// 创建Scheduler对象进行任务与Trigger的协调,并开启任务调度
Scheduler scheduler = new StdSchedulerFactory().getScheduler();
// 创建一个日历,这个是quartz提供的,包含月和日,不包含年,还有另外几个就不做阐述了
AnnualCalendar annualCalendar = new AnnualCalendar();
// 创建需要被排除的日历 第一个参数为年,其实可以随意填,因为AnnualCalendar 不包含年,这里主要是想要表示为8月28日
Calendar instance = new GregorianCalendar(2005,Calendar.AUGUST,28);
// 设置需要排除的日历
annualCalendar.setDayExcluded(instance,true);
// 将该日历添加到当前的Scheduler中
scheduler.addCalendar("holiday",annualCalendar,true,false);
// 创建任务详情JobDetail,绑定任务,并且指定任务的名称和组名
JobDetail jobDetail = JobBuilder.newJob(MyJob.class).build();
// 创建一个触发器,绑定JobDetail
// 设置了触发器的名称、组名、以及立即开始、并且指定触发规则为5秒执行一次,无限重复
Trigger trigger = TriggerBuilder.newTrigger()
.forJob(jobDetail)
.startNow()
// 每10秒执行一次
.withSchedule(SimpleScheduleBuilder.repeatSecondlyForever(10))
.modifiedByCalendar("holiday")
.build();
scheduler.scheduleJob(jobDetail, trigger);
scheduler.start();
}
}
JobListener
public void jobToBeExecuted() 在job任务触发前执行
public void jobExecutionVetoed() 在job任务被Trigger的监听器拒绝执行时触发,后续会讲到,当然,执行该方法的时候,就不会再执行该监听器的另外2个方法。
public void jobWasExecuted() Job任务执行后执行
TriggerListener
public void triggerFired()当与监听器相关联的Trigger被触发,Job上的execute()方法将被执行时,Scheduler就调用该方法。该方法在vetoJobExecution方法之后。
public boolean vetoJobExecution() 如果返回true,则当前触发任务的execute方法不执行
public void triggerMisfired() 当该触发任务失火时,调用该方法
public void triggerComplete() 当任务的execute方法执行完毕后调用该方法
SchedulerListener
jobScheduled方法:用于添加部署一个Trigger
jobUnscheduled方法:用于卸载一个Trigger触发器
triggerFinalized方法:当一个 Trigger 来到了再也不会触发的状态时调用这个方法。除非这个 Job 已设置成了持久性,否则它就会从 Scheduler 中移除。
triggersPaused方法:Scheduler 调用这个方法是发生在一个 Trigger 或 Trigger 组被暂停时。假如是 Trigger 组的话,triggerName 参数将为 null。
triggersResumed方法:Scheduler 调用这个方法是发生成一个 Trigger 或 Trigger 组从暂停中恢复时。假如是 Trigger 组的话,假如是 Trigger 组的话,triggerName 参数将为 null。参数将为 null。
jobsPaused方法:当一个或一组 JobDetail 暂停时调用这个方法。
jobsResumed方法:当一个或一组 Job 从暂停上恢复时调用这个方法。假如是一个 Job 组,jobName 参数将为 null。
schedulerError方法:在 Scheduler 的正常运行期间产生一个严重错误时调用这个方法。
schedulerStarted方法:当Scheduler 开启时,调用该方法
schedulerInStandbyMode方法: 当Scheduler处于StandBy模式时,调用该方法
schedulerShutdown方法:当Scheduler停止时,调用该方法
schedulingDataCleared方法:当Scheduler中的数据被清除时,调用该方法。
public class MyJob implements Job {
@Override
public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
System.out.println("Job任务执行了");
}
}
public class MyJobListener implements JobListener {
@Override
public String getName() {
return "我是个假的监听器哦";
}
@Override
public void jobToBeExecuted(JobExecutionContext context) {
System.out.println("job执行前");
}
@Override
public void jobExecutionVetoed(JobExecutionContext context) {
System.out.println("job被listener拒绝执行时执行");
}
@Override
public void jobWasExecuted(JobExecutionContext context, JobExecutionException jobException) {
System.out.println("job执行后");
}
}
public class QuartzTest {
public static void main(String[] args) throws SchedulerException {
// 创建Scheduler对象进行任务与Trigger的协调,并开启任务调度
Scheduler scheduler = new StdSchedulerFactory().getScheduler();
JobDetail jobDetail = JobBuilder.newJob(MyJob.class)
.withIdentity("job2","group1").build();
Trigger trigger = TriggerBuilder.newTrigger()
.forJob(jobDetail)
.startNow()
// 每10秒执行一次
.withSchedule(SimpleScheduleBuilder.repeatSecondlyForever(10))
.build();
// 添加监听器
JobListener jobListener = new MyJobListener();
scheduler.getListenerManager().addJobListener(jobListener);
// 部署到scheduler中
scheduler.scheduleJob(jobDetail, trigger);
// 开启任务调度
scheduler.start();
}
}
例如:
NameMatcher 根据名称匹配,其中有根据job的名称,或是trigger的名称
GroupMacher 根据组进行匹配,同样有job和trigger
EverythingMatcher 匹配全部的job或是匹配全部的trigger
AndMatcher 将两个Matcher相结合,并且是and条件
OrMatcher 将两个Matcher相结合,并且是or条件
NotMatcher 表示 非 的意思,也就是要求匹配不满足该条件的
在配置文件中配置插件即可,配置的插件必须是实现了SchedulerPlugin接口的类
quartz为我们提供了几个
在quartz的配置文件中进行如下配置,这个插件是quartz提供的,用job的日志
# 配置插件,这个myJobHistory名称是自定义的
org.quartz.plugin.myJobHistory.class=org.quartz.plugins.history.LoggingJobHistoryPlugin
# 配置该插件的其他属性,至于配置嘛,可以在具体的插件类中找到(成员属性)
org.quartz.plugin.triggerHistory.name=heiheihei
对了,有一个XMLSchedulingDataProcessorPlugin插件,可以通过读取配置文件完成Trigger和JobDetail的装载。
3个监控任务肯定是要定时执行的。
但是如果其他的正常任务占用了所有的线程,或者是执行时间过长。可能会导致监控任务的失火。
那么这个时候,就可以创建2个Scheduler。每个Scheduler使用一个配置文件
1、必须在持久化的方式下运行。
2、效果其实就是如果本次任务的执行,并没有执行完毕,而服务器宕机了,那么当服务器启动后,是否继续执行之前没有执行完毕的任务。
想一个场景,如果只有3个线程,而同一时间有5个任务需要被触发。那么quartz该如何选择先执行谁。
答:按照当前Trigger的优先级。
源码中有一句话:
比较触发器的下一次触发时间的比较器,换句话说,根据最早的下一次触发时间对它们进行排序。 如果触发次数相同,则触发器按优先级排序(最高值在前),如果优先级相同,则按key排序。
服务端:负责执行任务
添加如下配置
# 开启远程方法调用
org.quartz.scheduler.rmi.export: true
# 暴露的地址
org.quartz.scheduler.rmi.registryHost:localhost
# 暴露的端口
org.quartz.scheduler.rmi.registryPort: 1099
# 是否要注册
org.quartz.scheduler.rmi.createRegistry: true
public class ServerTest {
public static void main(String[] args) throws SchedulerException {
// 创建Scheduler对象进行任务与Trigger的协调,并开启任务调度
Scheduler scheduler = new StdSchedulerFactory("server.properties").getScheduler();
// 开启任务调度
scheduler.start();
}
}
客户端:负责分配任务
添加如下配置
# 开启远程方法代理
org.quartz.scheduler.rmi.proxy: true
# 需要注册到的远程地址
org.quartz.scheduler.rmi.registryHost:localhost
# 需要注册到的远程地址中暴露的端口
org.quartz.scheduler.rmi.registryPort: 1099
public class ClientTest {
public static void main(String[] args) throws SchedulerException {
JobDetail jobDetail = JobBuilder.newJob(MyJob.class).build();
Trigger trigger = TriggerBuilder.newTrigger()
.forJob(jobDetail)
.startNow()
.withSchedule(SimpleScheduleBuilder.repeatSecondlyForever(5))
.build();
Scheduler scheduler = new StdSchedulerFactory("client.properties").getScheduler();
scheduler.scheduleJob(jobDetail, trigger);
scheduler.start();
}
}
原文:https://www.cnblogs.com/itdqx/p/15165729.html