为什么要用Quartz
我们都知道Spring Boot自带定时器:@Scheduled(cron="0/1 * * * * ?")
(记得在启动类加上注解@EnableScheduling
),这样就已经实现了定时器的功能。
那么为什么还要用Quartz
呢? Quartz更容易管理,在多任务时,更方便的去动态配置,能实现动态关闭开启效果。
Quartz表达式(Cron)
cron="0/1 * * * * ?"
名称 | 是否必须 | 允许值 | 特殊字符 |
---|---|---|---|
秒 | 是 | 0-59 | - * / |
分 | 是 | 0-59 | - * / |
时 | 是 | 0-23 | - * / |
日 | 是 | 1-31 | - * ? / L W C |
月 | 是 | 1-12 或 JAN-DEC | - * / |
周 | 是 | 1-7 或 SUN-SAT | - * ? / L C # |
年 | 否 | 空 或 1970-2099 | - * / |
特殊字符
特殊字符 | 意义 |
---|---|
* | 表示所有值 |
? | 表示未说明的值,即不关心它为何值 |
- | 表示一个指定的范围 |
, | 表示附加一个可能值 |
/ | 符号前表示开始时间,符号后表示每次递增的值 |
实例
表达式 | 意义 |
---|---|
“0 0 12 * * ?” | 每天中午12点触发 |
“0 15 10 ? * *” | 每天上午10:15触发 |
“0 15 10 * * ?” | 每天上午10:15触发 |
“0 15 10 * * ? *” | 每天上午10:15触发 |
“0 15 10 * * ? 2005” | 2005年的每天上午10:15触发 |
“0 * 14 * * ?” | 在每天下午2点到下午2:59期间的每1分钟触发 |
“0 0/5 14 * * ?” | 在每天下午2点到下午2:55期间的每5分钟触发 |
“0 0/5 14,18 * * ?” | 在每天下午2点到2:55期间和下午6点到6:55期间的每5分钟触发 |
“0 0-5 14 * * ?” | 在每天下午2点到下午2:05期间的每1分钟触发 |
“0 10,44 14 ? 3 WED” | 每年三月的星期三的下午2:10和2:44触发 |
“0 15 10 ? * MON-FRI” | 周一至周五的上午10:15触发 |
“0 15 10 15 * ?” | 每月15日上午10:15触发 |
“0 15 10 L * ?” | 每月最后一日的上午10:15触发 |
“0 15 10 ? * 6L” | 每月的最后一个星期五上午10:15触发 |
“0 15 10 ? * 6L 2002-2005” | 2002年至2005年的每月的最后一个星期五上午10:15触发 |
“0 15 10 ? * 6#3” | 每月的第三个星期五上午10:15触发 |
代码走起
pom文件添加依赖包
<!-- quartz 定时器 -->
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz</artifactId>
<version>2.3.0</version>
</dependency>
初始注入scheduler
Spring Boot
是在启动类加上@Bean
的注入
/**
* 初始注入scheduler
* @return
* @throws SchedulerException
*/
@Bean
public Scheduler scheduler() throws SchedulerException {
SchedulerFactory schedulerFactoryBean = new StdSchedulerFactory();
return schedulerFactoryBean.getScheduler();
}
Spring MVC
是在applicationContext.xml
加上下面代码
<!-- ============================================定时任务========================= -->
<!-- -------------这段是为了项目启动自动执行定时任务-----开始----------- -->
<!-- 这个类用来做需要完成的业务-->
<bean id="myJob2" class="com.xxx.xxxx.xxxxx.myJob2"></bean>
<!--定义调用对象和调用对象的方法,这个配置和普通的一样的,id是JobDetail的名字-->
<bean id="jobtask" class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
<!-- 调用的类 -->
<property name="targetObject" ref="myJob2" />
<!-- 调用类中的方法 -->
<property name="targetMethod" value="doSomething" />
<!-- 是否并发 -->
<property name ="concurrent" value ="false" />
</bean>
<!--定义触发时间 ,这边就不同了,这里必须将时间设置成无限长,因为我们要去读取数据库的时间来做为定时器的触发时间-->
<bean id="cronTrigger" class="org.springframework.scheduling.quartz.CronTriggerFactoryBean ">
<property name="jobDetail" ref="jobtask" />
<!-- cron表达式 -->
<property name="cronExpression" value="0/1 * * * * ?" />
</bean>
<!-- -------------这段是为了项目启动自动执行定时任务-------结束--------------- -->
<!-- 总管理类 如果将lazy-init='false'那么容器启动就会执行调度程序 -->
<bean id="startQuertz" lazy-init="true" autowire="no" class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
<property name="triggers">
<list>
<!--<ref bean="cronTrigger" />-->
</list>
</property>
</bean>
<!--这个类是用来设置触发时间的, startJobs方法启动调度容器,然后按照上面触发器每隔1s执行所配置的myJob2.doSomething()方法 -->
<bean id="quartzManager" class="com.xxx.xxxxx.xxxx.QuartzScheduler" lazy-init="false" init-method="startJobs" >
<!--这个对象一定要注入,这样类才能进行管理,还有在类型要用get set方法,不然会报错。-->
<property name="scheduler" ref="startQuertz" />
</bean>
<!-- =================================定时任务================================== -->
注意:其实不管是Spring Boot 还是Spring MVC 哪种方式,都是为了交给Spring这个容器进行管理
新建一个任务调度处理类
import org.quartz.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
/**
* 任务调度处理
* Created by LMD on 2019/3/28.
*/
@Configuration
public class QuartzScheduler {
// 任务调度
@Autowired
private Scheduler scheduler;
/**
* @Description: 添加一个定时任务
*
* @param jobName 任务名
* @param jobGroupName 任务组名
* @param triggerName 触发器名
* @param triggerGroupName 触发器组名
* @param jobClass 任务
* @param cron 时间设置,参考quartz说明文档
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
public void addJob(String jobName, String jobGroupName,
String triggerName, String triggerGroupName, Class jobClass, String cron) {
try {
// 任务名,任务组,任务执行类
JobDetail jobDetail= JobBuilder.newJob(jobClass).withIdentity(jobName, jobGroupName).build();
// 触发器
TriggerBuilder<Trigger> triggerBuilder = TriggerBuilder.newTrigger();
// 触发器名,触发器组
triggerBuilder.withIdentity(triggerName, triggerGroupName);
triggerBuilder.startNow();
// 触发器时间设定
triggerBuilder.withSchedule(CronScheduleBuilder.cronSchedule(cron));
// 创建Trigger对象
CronTrigger trigger = (CronTrigger) triggerBuilder.build();
// 调度容器设置JobDetail和Trigger
scheduler.scheduleJob(jobDetail, trigger);
// 启动
if (!scheduler.isShutdown()) {
scheduler.start();
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* @Description: 修改一个任务的触发时间
*
* @param jobName
* @param jobGroupName
* @param triggerName 触发器名
* @param triggerGroupName 触发器组名
* @param cron 时间设置,参考quartz说明文档
*/
public void modifyJobTime(String jobName,
String jobGroupName, String triggerName, String triggerGroupName, String cron) {
try {
TriggerKey triggerKey = TriggerKey.triggerKey(triggerName, triggerGroupName);
CronTrigger trigger = (CronTrigger) scheduler.getTrigger(triggerKey);
if (trigger == null) {
return;
}
String oldTime = trigger.getCronExpression();
if (!oldTime.equalsIgnoreCase(cron)) {
/** 方式一 :调用 rescheduleJob 开始 */
// 触发器
TriggerBuilder<Trigger> triggerBuilder = TriggerBuilder.newTrigger();
// 触发器名,触发器组
triggerBuilder.withIdentity(triggerName, triggerGroupName);
triggerBuilder.startNow();
// 触发器时间设定
triggerBuilder.withSchedule(CronScheduleBuilder.cronSchedule(cron));
// 创建Trigger对象
trigger = (CronTrigger) triggerBuilder.build();
// 方式一 :修改一个任务的触发时间
scheduler.rescheduleJob(triggerKey, trigger);
/** 方式一 :调用 rescheduleJob 结束 */
/** 方式二:先删除,然后在创建一个新的Job */
//JobDetail jobDetail = scheduler.getJobDetail(JobKey.jobKey(jobName, jobGroupName));
//Class<? extends Job> jobClass = jobDetail.getJobClass();
//removeJob(jobName, jobGroupName, triggerName, triggerGroupName);
//addJob(jobName, jobGroupName, triggerName, triggerGroupName, jobClass, cron);
/** 方式二 :先删除,然后在创建一个新的Job */
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* @Description: 移除一个任务
*
* @param jobName 任务名
* @param jobGroupName 任务组名
* @param triggerName 触发器名
* @param triggerGroupName 触发器组名
*/
public void removeJob(String jobName, String jobGroupName,
String triggerName, String triggerGroupName) {
try {
TriggerKey triggerKey = TriggerKey.triggerKey(triggerName, triggerGroupName);
scheduler.pauseTrigger(triggerKey);// 停止触发器
scheduler.unscheduleJob(triggerKey);// 移除触发器
scheduler.deleteJob(JobKey.jobKey(jobName, jobGroupName));// 删除任务
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* @Description:启动所有定时任务
*/
public void startJobs() {
try {
scheduler.start();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* @Description:关闭所有定时任务
*/
public void shutdownJobs() {
try {
if (!scheduler.isShutdown()) {
scheduler.shutdown();
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
测试
@Autowired
private QuartzScheduler quartzScheduler;
/**
* 开启 Quartz
* @return
* @throws MyException
*/
@RequestMapping(value = "/system/quartzAll")
public void quartzAll() throws MyException {
try {
System.out.println("【系统启动】开始......");
System.out.println("【增加job1启动】开始(每1秒输出一次)...");//0/10 * * * * ?
quartzScheduler.addJob("job1", "job1", "job1", "job1", MyJobAppoint.class, "0/1 * * * * ? *");
Thread.sleep(5000);
System.out.println("【修改job1时间】开始(每2秒输出一次)...");
quartzScheduler.modifyJobTime("job1", "job1", "job1", "job1", "0/2 * * * * ?");
Thread.sleep(10000);
System.out.println("【移除job1定时】开始...");
quartzScheduler.removeJob("job1", "job1", "job1", "job1");
System.out.println("【增加job2启动】开始(每1秒输出一次)...");//0/10 * * * * ?
quartzScheduler.addJob("job2", "job2", "job2", "job2", MyJobAppoint.class, "0/1 * * * * ? *");
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 关闭 Quartz
* @return
* @throws MyException
*/
@RequestMapping(value = "/system/closeQuartz")
@ResponseBody
public void closeQuartz() throws MyException {
try {
System.out.println("【移除全部定时】");//0/10 * * * * ?
// 关掉任务调度容器
quartzScheduler.shutdownJobs();
} catch (Exception e) {
e.printStackTrace();
}
}
运行项目,调用对应的接口,控制台就打印如下日志:
2019-03-28 10:56:02.448 INFO 22948 --- [-nio-80-exec-10] c.s.companycms.config.LoginInterceptor : request: 请求地址 path [/api/system/quartzAll] uri [/api/system/quartzAll]
【系统启动】开始......
【增加job1启动】开始(每1秒输出一次)...
2019-03-28 10:56:02.470 INFO 22948 --- [-nio-80-exec-10] org.quartz.core.QuartzScheduler : Scheduler DefaultQuartzScheduler_$_NON_CLUSTERED started.
2019-03-28 10:56:02: MyJobAppoint 1 doing......
2019-03-28 10:56:03: MyJobAppoint 1 doing......
2019-03-28 10:56:04: MyJobAppoint 1 doing......
2019-03-28 10:56:05: MyJobAppoint 1 doing......
2019-03-28 10:56:06: MyJobAppoint 1 doing......
2019-03-28 10:56:07: MyJobAppoint 1 doing......
【修改job1时间】开始(每2秒输出一次)...
2019-03-28 10:56:08: MyJobAppoint 1 doing......
2019-03-28 10:56:10: MyJobAppoint 1 doing......
2019-03-28 10:56:12: MyJobAppoint 1 doing......
2019-03-28 10:56:14: MyJobAppoint 1 doing......
2019-03-28 10:56:16: MyJobAppoint 1 doing......
【移除job1定时】开始...
【增加job2启动】开始(每1秒输出一次)...
2019-03-28 10:56:17.474 INFO 22948 --- [-nio-80-exec-10] org.quartz.core.QuartzScheduler : Scheduler DefaultQuartzScheduler_$_NON_CLUSTERED started.
2019-03-28 10:56:17: MyJobAppoint 1 doing......
2019-03-28 10:56:18: MyJobAppoint 1 doing......
2019-03-28 10:56:19: MyJobAppoint 1 doing......
2019-03-28 10:56:20: MyJobAppoint 1 doing......
2019-03-28 10:56:21: MyJobAppoint 1 doing......
2019-03-28 10:56:22: MyJobAppoint 1 doing......
2019-03-28 10:56:23: MyJobAppoint 1 doing......
2019-03-28 10:56:24: MyJobAppoint 1 doing......
2019-03-28 10:56:25: MyJobAppoint 1 doing......
2019-03-28 10:56:26: MyJobAppoint 1 doing......
2019-03-28 10:56:27: MyJobAppoint 1 doing......
2019-03-28 10:56:28: MyJobAppoint 1 doing......
2019-03-28 10:56:29: MyJobAppoint 1 doing......
2019-03-28 10:56:30: MyJobAppoint 1 doing......
2019-03-28 10:56:31: MyJobAppoint 1 doing......
2019-03-28 10:56:32: MyJobAppoint 1 doing......
2019-03-28 10:56:32.550 INFO 22948 --- [p-nio-80-exec-1] c.s.companycms.config.LoginInterceptor : request: 请求地址 path [/api/system/closeQuartz] uri [/api/system/closeQuartz]
【移除全部定时】
最后
谢谢大家的参考、阅读;
可能大家在实际写代码的过程中有不一样的异常出错,大家可以留言一起讨论学习。