第三章 quartz
1.Quartz是什么
- quartz官网:
http://quartz-scheduler.org
- Quartz是一个功能丰富的开源任务调度框架,几乎可以集成任何java应用,小到单体应用,大到大型电子商务系统;
- Quartz可以用来执行成千上万的简单或者复杂的调度任务;
- Quartz的任务被定义为一组标准的java组件,几乎可以执行任何编程任务;
- Quartz包含了很多企业级功能,包括JTA事务和集群等;
2.Quartz能做什么
- 定时发送邮件、短信;
- 定时数据同步等;
3.Quartz的特点
- 具备强大调度功能,具有灵活的使用方式;
- 支持集群;
4.Quartz的核心API
- Job:创建任务必须要实现的接口
- Trigger:定义了任务的执行规则
SimpleTrigger:可以定义任务何时执行,执行频率、何时停止
CronTrigger:使用Cron表达式灵活的定义调度时间 - Scheduler:调度器,使用Trigger定义的规则去调度Job
- JobDetail:定义任务的细节(任务名称、所属任务组)
- JobBuilder:用户创建任务
- TriggerBuilder:创建Trigger
- JobDataMap:JobDetail和Trigger向任务类传参
- JobExecutionContext:任务执行上下文;
其它分布式调度框架:当当网Elastic-Job、XXL-Job
5.使用代码测试SimpleTrigger
maven坐标
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz</artifactId>
<version>2.3.0</version>
</dependency>
测试任务
- 每隔5秒打印一次Hello
- 任务三秒后执行,之后每隔5秒打印一次Hello,重复执行10次
- 任务三秒后执行,之后每隔5秒打印一次Hello,20秒后停止执行
开发步骤
- 创建HelloJob任务
- 创建JobDetail
- 创建Trigger
- 创建Scheduler
- 执行调度
HelloJob.java
package com.etoak.job;
public class HelloJob implements Job {
// 测试JobDetail给成员变量传参
private String name;
@Override
public void execute(JobExecutionContext context) throws JobExecutionException {
LocalDateTime now = LocalDateTime.now();
System.out.println("Hello " + now.toString() + " - " + this.name);
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
测试一:
package com.etoak.simple;
public class FirstMain {
public static void main(String[] args) throws SchedulerException {
// 1. 创建JobDetail
JobKey jobKey = new JobKey("hello", "hello");
JobDetail jobDetail = JobBuilder.newJob(HelloJob.class) //
.withIdentity(jobKey)//
.build();
// 2. 创建Trigger
TriggerKey triggerKey = new TriggerKey("hello", "hello");
SimpleTrigger trigger = TriggerBuilder.newTrigger()//
.withIdentity(triggerKey)// 唯一标识
.startNow()// 立即启动
.withSchedule(SimpleScheduleBuilder//
.simpleSchedule()//
.withIntervalInSeconds(5)//
.repeatForever()//
).build();
// 3. 创建Scheduler,并启动调度
Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
scheduler.scheduleJob(jobDetail, trigger);
scheduler.start();
}
}
测试二:
package com.etoak.simple;
public class SecondMain {
public static void main(String[] args) throws SchedulerException {
// 创建JobDetail
JobDetail jobDetail = JobBuilder.newJob(HelloJob.class)//
.withIdentity("hello", "hello")//
// 直接给成员变量传参,key就是成员变量名称
.usingJobData("name", "zhangsan") //
.build();
System.out.println("当前时间:" + LocalDateTime.now());
// 获取当前时间的毫秒数
long currentTimeMillis = System.currentTimeMillis();
// 3秒后的时间
Date triggerStartTime = new Date(currentTimeMillis + 3000L);
// 创建Trigger
SimpleTrigger trigger = TriggerBuilder//
.newTrigger().withIdentity("hello", "hello")//
.startAt(triggerStartTime) // 3秒的Date
.withSchedule(SimpleScheduleBuilder//
.simpleSchedule()//
.withIntervalInSeconds(5)// 每隔5秒钟
.withRepeatCount(10) // 重复10次
).build();
// 创建Scheduler
Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
scheduler.scheduleJob(jobDetail, trigger);
scheduler.start();
}
}
测试三:
package com.etoak.simple;
public class ThirdMain {
public static void main(String[] args) throws SchedulerException {
// 创建JobDetail
JobDetail jobDetail = JobBuilder //
.newJob(HelloJob.class) //
.withIdentity("hello", "hello") //
.build();
System.out.println(LocalDateTime.now());
// 分别获取3秒后和20秒后的时间
long current = System.currentTimeMillis();
Date three = new Date(current + 3000L);
Date twenty = new Date(current + 1000L * 20);
// 创建Trigger
SimpleTrigger trigger = TriggerBuilder.newTrigger()//
.withIdentity("hello", "hello")//
.usingJobData("name", "lisi")// 给job任务的成员变量传参
.startAt(three)// 3秒后开始
.endAt(twenty) // 20秒后停止
.withSchedule(SimpleScheduleBuilder//
.simpleSchedule()//
.withIntervalInSeconds(5)//
.repeatForever()//
).build();
// 创建Scheduler
Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
scheduler.scheduleJob(jobDetail, trigger);
scheduler.start();
}
}
6.Cron表达式
- Quartz Cron表达式用于配置CronTrigger实例
- Quartz Cron表达式由7个子表达式组成,用于描述时间的各个细节,这些表达式用空 格分隔
[秒] [分] [小时] [天、日期] [月] [周] [年](可选)
seconds Minutes Hours Day-of-Month Month Day-of-Week YEAR
时间字段 | 是否必填 | 取值范围 | 允许的特殊字符 |
秒 | 是 | 0-59 | , * - / |
分 | 是 | 0-59 | , * - / |
小时 | 是 | 0-59 | , * - / |
【天】每月的第几号 | 是 | 1-31 | , * - / ? L W |
月 | 是 | 1-12 | , * - / |
周几 | 是 | 1-7【对应周日到周六】 | , * - / ? L # |
年 | 否 | 空或1970-2099 | , * - / |
特殊字符 | 解释 |
, | 表示或的关系。如用在分钟:1,5 - 表示在第一分钟和第5分钟执行 |
* | 每秒、每分、每小时、每天、每月等。在秒上写*,表示每秒钟都要执行 |
- | 表示范围。如用在秒上:1-20 - 表示在1-20秒都要执行 |
/ | 表示每隔多长时间执行。用在秒上:*/5或0/5 - 表示每隔5秒执行一次 |
? | 只能用到天和周上 1.如果[天]字段上用了或确切的某一天,那么[周]只能用? 2.如果[周]字段上用了或确切的某一个周几,那么[天]只能用? |
L | 表示每个月的最后一天或者每个月的最后星期几,[周]写6L - 表示每个月 最后一个星期5 |
W | 只能用在[天、日期]的字段,表示离这个日期最近的一个工作日 比如: 1. 25W:表示离这个月25号最近的一个工作日 如果25号是一个工作日,那么就是这天执行 如果25号是周六,那么应该是24号周五执行 如果25号是周天,那么应该是下周一26号执行 2.日期不能跨月 1W:离1号最近的工作日,如果1号是周六,那么离1号最近的工作日 应该是当月3号,而不是上个月的最后一天; 3.只能指定单一日期,不能用范围 |
# | 用在[周]上,表示每个月第几个周几, 例如在[周上]写SUN#2 - 表示每个 月第二个周天 母亲节的例子:母亲节当天8点执行一个短信发送任务 0 0 8 ? 5 SUN#2 |
CronTest.java
package com.etoak.cron;
public class CronTest {
public static void main(String[] args) throws SchedulerException {
CronTrigger trigger = TriggerBuilder.newTrigger()//
.withIdentity("hello")//
.usingJobData("name", "lisi")//
.withSchedule(CronScheduleBuilder//
.cronSchedule("0/5 * * * * ?")//
).build();
JobDetail jobDetail = JobBuilder.newJob(HelloJob.class)//
.withIdentity("hello")//
.usingJobData("name", "zs")//
.build();
Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
scheduler.scheduleJob(jobDetail, trigger);
scheduler.start();
}
}
7.整合spring mvc
7.1整合单机版
pom.xml
<?xml version="1.0"?>
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"
xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.etoak.et1912.quartz</groupId>
<artifactId>quartz-et1912</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<artifactId>quartz-springmvc</artifactId>
<packaging>war</packaging>
<dependencies>
<!-- quartz -->
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz</artifactId>
</dependency>
<!-- servlet-api -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
</dependency>
<!-- spring-webmvc -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
</dependency>
<!-- spring-context-support -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
</dependency>
<!-- thymeleaf -->
<dependency>
<groupId>org.thymeleaf</groupId>
<artifactId>thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.thymeleaf</groupId>
<artifactId>thymeleaf-spring5</artifactId>
</dependency>
<!-- jackson-databind -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<!-- spring-jdbc -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
</dependency>
<!-- spring-tx -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
</dependency>
</dependencies>
<build>
<finalName>quartz-springmvc</finalName>
</build>
</project>
web.xml
<!DOCTYPE web-app PUBLIC
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd" >
<web-app>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring-root.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<filter>
<filter-name>encoding</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>encoding</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<servlet>
<servlet-name>dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring-mvc.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>dispatcher</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
spring-mvc.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context" xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.2.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.2.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc-4.2.xsd">
<!-- 注解扫描 -->
<context:component-scan base-package="com.etoak">
<context:include-filter type="annotation"
expression="org.springframework.stereotype.Controller" />
<context:include-filter type="annotation"
expression="org.springframework.web.bind.annotation.RestController" />
<context:include-filter type="annotation"
expression="org.springframework.web.bind.annotation.ControllerAdvice" />
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Service" />
<context:exclude-filter type="annotation"
expression="org.springframework.stereotype.Repository" />
</context:component-scan>
<!-- 开启mvc配置 -->
<mvc:annotation-driven></mvc:annotation-driven>
<!-- 静态文件交给Servlet容器处理 -->
<mvc:default-servlet-handler />
<!-- Thymeleaf的三个Bean -->
<bean id="templateResolver" class="org.thymeleaf.spring5.templateresolver.SpringResourceTemplateResolver">
<property name="prefix" value="classpath:/templates/" />
<property name="suffix" value=".html" />
<property name="templateMode" value="HTML" />
<property name="characterEncoding" value="UTF-8" />
<property name="cacheable" value="false" />
</bean>
<bean id="templateEngine" class="org.thymeleaf.spring5.SpringTemplateEngine">
<property name="templateResolver" ref="templateResolver" />
</bean>
<bean class="org.thymeleaf.spring5.view.ThymeleafViewResolver">
<property name="templateEngine" ref="templateEngine" />
<property name="characterEncoding" value="UTF-8" />
</bean>
<mvc:view-controller path="/" view-name="index" />
</beans>
spring-root.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.2.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.2.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-4.2.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-4.2.xsd">
<!-- 注解扫描 -->
<context:component-scan base-package="com.etoak">
<context:include-filter type="annotation" expression="org.springframework.stereotype.Service" />
<context:include-filter type="annotation"
expression="org.springframework.stereotype.Repository" />
<context:exclude-filter type="annotation"
expression="org.springframework.stereotype.Controller" />
<context:exclude-filter type="annotation"
expression="org.springframework.web.bind.annotation.RestController" />
<context:exclude-filter type="annotation"
expression="org.springframework.web.bind.annotation.ControllerAdvice" />
</context:component-scan>
<!-- 导入单机版quartz配置 -->
<import resource="classpath:standalone.xml" />
</beans>
standalone.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.2.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.2.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-4.2.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-4.2.xsd">
<!-- 任务类 -->
<bean id="emailJob" class="com.etoak.job.EmailJob" />
<!-- jobDetail -->
<bean id="jobDetail" class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
<property name="name" value="email" />
<property name="group" value="email" />
<!-- 任务类对象 -->
<property name="targetObject" ref="emailJob" />
<!-- 任务类中要执行的方法名称 -->
<property name="targetMethod" value="send" />
</bean>
<!-- Crontrigger -->
<bean id="trigger" class="org.springframework.scheduling.quartz.CronTriggerFactoryBean">
<property name="name" value="email" />
<property name="group" value="email" />
<property name="jobDetail" ref="jobDetail" />
<property name="cronExpression" value="0/5 * * * * ?" />
</bean>
<!-- SchedulerFactoryBean -->
<bean class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
<property name="triggers">
<list>
<ref bean="trigger" />
</list>
</property>
</bean>
</beans>
EmailJob.java
package com.etoak.job;
public class EmailJob {
//开启服务器执行以下方法
public void send() {
System.out.println("send email");
}
}
7.2集群
quartz任务集群:通过JDBC方式传播任务状态
- 在quartz集群中,同一时刻只有一个任务执行
- 当一个任务宕机之后,其它服务会接管这个任务
1. 创建数据库和表
数据库
:使用et1912数据库
数据表
:quartz已经提供
2. 拷贝quartz.properties文件到src/main/resources下
文件中包含集群名称、集群实例ID如何产生、任务存储、scheduler实例键入数据时间等等
3. 配置Quartz
- 数据源
这里使用HikariCP - 事务管理器:DataSourceTransactionManager
- 配置JobDetailFactoryBean
特点:要求任务类要继承QuartzJobBean
属性:name、group、jobClass(Class,不是spring bean)、durability - 配置CronTriggerFactoryBean
属性:name、group、jobDetail、cronExpression - 配置SchedulerFactoryBean
属性:数据源、事务管理器、configLocation、triggers
cluster.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.2.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.2.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-4.2.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-4.2.xsd">
<!-- 1. 数据源 这里使用HikariCP 2. 事务管理器:DataSourceTransactionManager 3. 配置JobDetailFactoryBean 特点:要求任务类要继承QuartzJobBean 属性:name、group、jobClass(Class,不是spring
bean)、durability 4. 配置CronTriggerFactoryBean 属性:name、group、jobDetail、cronExpression 5. 配置SchedulerFactoryBean 属性:数据源、事务管理器、configLocation、triggers -->
<bean id="dataSource" class="com.zaxxer.hikari.HikariDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver" />
<property name="jdbcUrl" value="jdbc:mysql://127.0.0.1:3306/et1912" />
<property name="username" value="root" />
<property name="password" value="root" />
</bean>
<bean id="tx" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
<bean id="jobDetail" class="org.springframework.scheduling.quartz.JobDetailFactoryBean">
<property name="name" value="orderJob" />
<property name="group" value="orderJob" />
<!-- 配置任务: jobClass的值不是spring bean,是通过反射创建的 -->
<property name="jobClass" value="com.etoak.job.OrderJob" />
<!-- 开启持久化 -->
<property name="durability" value="true" />
</bean>
<bean id="trigger" class="org.springframework.scheduling.quartz.CronTriggerFactoryBean">
<property name="name" value="orderTrigger" />
<property name="group" value="orderTrigger" />
<property name="jobDetail" ref="jobDetail" />
<property name="cronExpression" value="0/5 * * * * ?" />
</bean>
<bean class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="transactionManager" ref="tx" />
<property name="configLocation" value="classpath:quartz.properties" />
<property name="triggers">
<list>
<ref bean="trigger" />
</list>
</property>
<!-- 第一种解决Job不能使用Spring bean的问题 -->
<property name="applicationContextSchedulerContextKey" value="spring" />
<!-- 第二种解决Job不能使用Spring bean的问题 -->
<property name="jobFactory">
<bean class="com.etoak.factory.SpringJobFactory" />
</property>
</bean>
</beans>
JobController.java
package com.etoak.controller;
@RestController
@RequestMapping("/job")
public class JobController {
@Autowired
Scheduler scheduler;
@RequestMapping(value="/pause" ,produces = "text/plain;charset=UTF-8")
public String pause(String name , String group)throws SchedulerException{
TriggerKey triggerKey = new TriggerKey(name,group);
if(!scheduler.checkExists(triggerKey)) {
return "任务不存在";
}
scheduler.pauseTrigger(triggerKey);
return "success";
}
}
SpringJobFactory.java
package com.etoak.factory;
public class SpringJobFactory extends SpringBeanJobFactory {
//将第三方框架创建的对象重新加载到spring容器中
@Autowired
AutowireCapableBeanFactory ioc;
@Override
protected Object createJobInstance(TriggerFiredBundle bundle) throws Exception {
//调用父类方法创建(反射)任务实例
Object object = super.createJobInstance(bundle);
//将创建的任务重新加载到spring容器中
ioc.autowireBean(object);
return object;
}
}
第一种方式解决Job不能使用Spring bean的问题
package com.etoak.job;
public class OrderJob extends QuartzJobBean {
@Override
protected void executeInternal(JobExecutionContext context) throws JobExecutionException {
// 1. 先获取Scheduler
Scheduler scheduler = context.getScheduler();
try {
// 2. 再获取SchedulerContext
SchedulerContext schedulerContext = scheduler.getContext();
// 3. 再根据applicationContextSchedulerContextKey对应值获取spring容器
ApplicationContext ioc = (ApplicationContext) schedulerContext.get("spring");
// 4. 最后从spring容器中获取OrderService
OrderService orderService = ioc.getBean(OrderService.class);
System.out.println("OrderService - " + orderService);
orderService.delOrder();
System.out.println("删除超时订单");
} catch (SchedulerException e) {
e.printStackTrace();
}
}
}
第二种方式解决Job不能使用Spring bean的问题
package com.etoak.job;
public class OrderJob extends QuartzJobBean {
@Autowired
OrderService orderService;
@Override
protected void executeInternal(JobExecutionContext context) throws JobExecutionException {
System.out.println("OrderService22222 - " + orderService);
orderService.delOrder();
System.out.println("删除超时订单");
}
}
X错误集锦
- Caused by: org.springframework.beans.ConversionNotSupportedException: Failed to convert property value of type
引发原因:在一个bean中引用了type后边这个类型的bean,但是把 ref 写成了 value