# demo-quartz **Repository Path**: debug_life/demo-quartz ## Basic Information - **Project Name**: demo-quartz - **Description**: quartz学习成果 - **Primary Language**: Java - **License**: Apache-2.0 - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2022-11-25 - **Last Updated**: 2022-12-08 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # SpringBoot之Quartz ## pom.xml SpringBoot版本为2.7.5 ```xml org.springframework.boot spring-boot-starter-quartz ``` ## 配置定时任务 * 创建定时任务配置类。 * 初始化定时任务详情,设置需要执行的服务类和指定方法并将定时任务详情注册到Spring容器。 * 初始化定时任务触发器,设置对应的定时任务详情以及触发时间并将定时任务触发器注册到Spring容器。 * 初始化定时任务总控制器,设置所有定时任务触发器,设置是否启动定时任务,将定时任务总控制器注册到Spring容器。 * 启动应用后如果定时任务设置为启动,则会根据定时任务触发器触发执行对应的方法。 定时任务配置类: ```java /** * 定时任务-配置类 * @author sword * @date 2022/11/17 17:07 */ @Configuration @Slf4j @RequiredArgsConstructor public class SchedulerConfig { /** * 定时任务-参数 */ private final SchedulerProperties schedulerProperties; /** * 时钟-定时任务详情 * @param clockService 时钟服务 * @return 时钟-定时任务详情 * @author wukongjian * @date 2019/10/16 10:10 */ @Bean public MethodInvokingJobDetailFactoryBean clockJobDetail(ClockService clockService) { return QuartzUtil.createJobDetail(clockService, "clock"); } /** * 时钟-定时任务触发器 * @param clockJobDetail 时钟-定时任务详情 * @return org.springframework.scheduling.quartz.CronTriggerFactoryBean 时钟-定时任务触发器 * @author wukongjian * @date 2019/10/16 10:10 */ @Bean public CronTriggerFactoryBean clockCronTrigger(JobDetail clockJobDetail) { return QuartzUtil.createCronTrigger(clockJobDetail, "* * * * * ?"); } /** * Quartz定时任务总控制器 * @param cronTriggers 配置的所有定时任务触发器 * @return Quartz定时任务总控制器 * @author wukongjian * @date 2019/10/16 9:35 */ @Bean public SchedulerFactoryBean scheduler(Trigger[] cronTriggers) { SchedulerFactoryBean schedulerFactoryBean = new SchedulerFactoryBean(); // 设置定时任务触发器 schedulerFactoryBean.setTriggers(cronTriggers); // 设置自动启动定时任务 schedulerFactoryBean.setAutoStartup(schedulerProperties.isEnabled()); log.info("定时任务{}启动", schedulerProperties.isEnabled() ? "已" : "未"); return schedulerFactoryBean; } } ``` 定时任务详情设置的服务类和对应方法: ```java /** * 时钟服务 * @author sword * @date 2022/11/17 17:10 */ public interface ClockService { /** * 滴答一下 * @author sword * @date 2022/11/17 17:11 */ void clock(); } /** * 时钟服务实现类 * @author sword * @date 2022/11/17 17:11 */ @Service @Slf4j public class ClockServiceImpl implements ClockService { @Override public void clock() { log.info(LocalDateTime.now().toString()); } } ``` 可以在 `application.yml` 中设置定时任务是否开启,如果不设置,则默认不开启。 application.yml: ```yaml scheduler: enabled: true ``` 定时任务参数类: ```java /** * 定时任务-参数 * @author sword * @date 2022/12/6 13:48 */ @ConfigurationProperties(prefix = SchedulerProperties.PREFIX) @Component @Data public class SchedulerProperties { /** * 参数前缀 */ public static final String PREFIX = "scheduler"; /** * 是否开启 * 默认为空 */ private boolean enabled = false; } ``` Quartz工具类主要是封装了定时任务详情和定时任务触发器的初始化方法。 ```java /** * Quartz工具类 * @author sword * @date 2022/11/17 20:27 */ public class QuartzUtil { /** * 初始化一个定时任务详情 * @param targetObject 目标类对象 * @param targetMethod 目标方法 * @return org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean 定时详情 * @author wukongjian * @date 2019/10/16 9:58 */ public static MethodInvokingJobDetailFactoryBean createJobDetail(Object targetObject, String targetMethod) { MethodInvokingJobDetailFactoryBean jobDetailFactoryBean = new MethodInvokingJobDetailFactoryBean(); // 设置目标类对象 jobDetailFactoryBean.setTargetObject(targetObject); // 设置目标方法 jobDetailFactoryBean.setTargetMethod(targetMethod); // 防止并发执行 jobDetailFactoryBean.setConcurrent(false); return jobDetailFactoryBean; } /** * 初始化一个定时任务触发器 * @param jobDetail 定时任务详情 * @param cronExpression 触发器执行时间表达式 * @return 定时任务触发器 * @author wukongjian * @date 2019/10/16 10:04 */ public static CronTriggerFactoryBean createCronTrigger(JobDetail jobDetail, String cronExpression) { CronTriggerFactoryBean cronTriggerFactoryBean = new CronTriggerFactoryBean(); // 设置定时任务详情 cronTriggerFactoryBean.setJobDetail(jobDetail); // 设置执行时间表达式 cronTriggerFactoryBean.setCronExpression(cronExpression); return cronTriggerFactoryBean; } } ``` 应用启动定时触发执行指定方法: ```log 16:53:14.553 [Thread-1] DEBUG org.springframework.boot.devtools.restart.classloader.RestartClassLoader - Created RestartClassLoader org.springframework.boot.devtools.restart.classloader.RestartClassLoader@29952818 . ____ _ __ _ _ /\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \ ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \ \\/ ___)| |_)| | | | | || (_| | ) ) ) ) ' |____| .__|_| |_|_| |_\__, | / / / / =========|_|==============|___/=/_/_/_/ :: Spring Boot :: (v2.7.5) 2022-12-06 16:53:15.138 INFO 57352 --- [ restartedMain] com.sword.demo.Application : Starting Application using Java 1.8.0_202 on wkj with PID 57352 (E:\workspace\Git\demo\quartz-springboot\target\classes started by wukongjian in E:\workspace\Git\demo\quartz-springboot) 2022-12-06 16:53:15.141 INFO 57352 --- [ restartedMain] com.sword.demo.Application : No active profile set, falling back to 1 default profile: "default" 2022-12-06 16:53:15.191 INFO 57352 --- [ restartedMain] .e.DevToolsPropertyDefaultsPostProcessor : Devtools property defaults active! Set 'spring.devtools.add-properties' to 'false' to disable 2022-12-06 16:53:15.191 INFO 57352 --- [ restartedMain] .e.DevToolsPropertyDefaultsPostProcessor : For additional web related logging consider setting the 'logging.level.web' property to 'DEBUG' 2022-12-06 16:53:16.266 ERROR 57352 --- [ restartedMain] o.a.catalina.core.AprLifecycleListener : An incompatible version [1.1.27] of the Apache Tomcat Native library is installed, while Tomcat requires version [1.2.14] 2022-12-06 16:53:16.563 INFO 57352 --- [ restartedMain] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port(s): 8080 (http) 2022-12-06 16:53:16.571 INFO 57352 --- [ restartedMain] o.apache.catalina.core.StandardService : Starting service [Tomcat] 2022-12-06 16:53:16.571 INFO 57352 --- [ restartedMain] org.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/9.0.68] 2022-12-06 16:53:16.693 INFO 57352 --- [ restartedMain] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext 2022-12-06 16:53:16.693 INFO 57352 --- [ restartedMain] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 1501 ms 2022-12-06 16:53:16.791 INFO 57352 --- [ restartedMain] c.s.demo.quartz.config.SchedulerConfig : 定时任务已启动 2022-12-06 16:53:16.812 INFO 57352 --- [ restartedMain] org.quartz.impl.StdSchedulerFactory : Using default implementation for ThreadExecutor 2022-12-06 16:53:16.833 INFO 57352 --- [ restartedMain] org.quartz.core.SchedulerSignalerImpl : Initialized Scheduler Signaller of type: class org.quartz.core.SchedulerSignalerImpl 2022-12-06 16:53:16.833 INFO 57352 --- [ restartedMain] org.quartz.core.QuartzScheduler : Quartz Scheduler v.2.3.2 created. 2022-12-06 16:53:16.834 INFO 57352 --- [ restartedMain] org.quartz.simpl.RAMJobStore : RAMJobStore initialized. 2022-12-06 16:53:16.834 INFO 57352 --- [ restartedMain] org.quartz.core.QuartzScheduler : Scheduler meta-data: Quartz Scheduler (v2.3.2) 'scheduler' with instanceId 'NON_CLUSTERED' Scheduler class: 'org.quartz.core.QuartzScheduler' - running locally. NOT STARTED. Currently in standby mode. Number of jobs executed: 0 Using thread pool 'org.quartz.simpl.SimpleThreadPool' - with 10 threads. Using job-store 'org.quartz.simpl.RAMJobStore' - which does not support persistence. and is not clustered. 2022-12-06 16:53:16.834 INFO 57352 --- [ restartedMain] org.quartz.impl.StdSchedulerFactory : Quartz scheduler 'scheduler' initialized from an externally provided properties instance. 2022-12-06 16:53:16.834 INFO 57352 --- [ restartedMain] org.quartz.impl.StdSchedulerFactory : Quartz scheduler version: 2.3.2 2022-12-06 16:53:16.837 INFO 57352 --- [ restartedMain] org.quartz.core.QuartzScheduler : JobFactory set to: org.springframework.scheduling.quartz.AdaptableJobFactory@11ad3263 2022-12-06 16:53:17.976 INFO 57352 --- [ restartedMain] o.s.b.d.a.OptionalLiveReloadServer : LiveReload server is running on port 35729 2022-12-06 16:53:18.026 INFO 57352 --- [ restartedMain] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8080 (http) with context path '' 2022-12-06 16:53:18.028 INFO 57352 --- [ restartedMain] o.s.s.quartz.SchedulerFactoryBean : Starting Quartz Scheduler now 2022-12-06 16:53:18.028 INFO 57352 --- [ restartedMain] org.quartz.core.QuartzScheduler : Scheduler scheduler_$_NON_CLUSTERED started. 2022-12-06 16:53:18.041 INFO 57352 --- [ restartedMain] com.sword.demo.Application : Started Application in 3.475 seconds (JVM running for 4.916) 2022-12-06 16:53:18.047 INFO 57352 --- [eduler_Worker-1] c.s.d.q.service.impl.ClockServiceImpl : 2022-12-06T16:53:18.047 2022-12-06 16:53:18.049 INFO 57352 --- [eduler_Worker-2] c.s.d.q.service.impl.ClockServiceImpl : 2022-12-06T16:53:18.049 2022-12-06 16:53:18.050 INFO 57352 --- [eduler_Worker-3] c.s.d.q.service.impl.ClockServiceImpl : 2022-12-06T16:53:18.050 2022-12-06 16:53:19.001 INFO 57352 --- [eduler_Worker-4] c.s.d.q.service.impl.ClockServiceImpl : 2022-12-06T16:53:19.001 2022-12-06 16:53:20.005 INFO 57352 --- [eduler_Worker-5] c.s.d.q.service.impl.ClockServiceImpl : 2022-12-06T16:53:20.005 ``` ## 定时任务手动操作接口 定时任务服务: ```java /** * 定时任务-服务 * @author sword * @date 2022/11/28 17:24 */ public interface SchedulerService { /** * 手动执行指定定时任务详情 * * @param jobDetailId 任务详情id * @throws InvocationTargetException 执行目标方法异常 * @throws IllegalAccessException 非法访问异常 * @author sword * @date 2022/11/28 17:25 */ void invokeJobDetail(String jobDetailId) throws InvocationTargetException, IllegalAccessException; /** * 启动定时任务 * * @throws SchedulerException 定时问题异常 * @author sword * @date 2022/11/28 17:25 */ void start() throws SchedulerException; /** * 暂停定时任务,可以重新启动 * * @throws SchedulerException 定时问题异常 * @author sword * @date 2022/11/28 17:25 */ void standby() throws SchedulerException; /** * 关闭定时任务,无法重新启动 * * @throws SchedulerException 定时问题异常 * @author sword * @date 2022/11/28 17:25 */ void shutdown() throws SchedulerException; } ``` 定时任务服务实现类: ```java /** * 定时任务-服务实现类 * @author sword * @date 2022/11/28 17:29 */ @Service @RequiredArgsConstructor public class SchedulerServiceImpl implements SchedulerService { /** * 定时任务总控制器 */ private final Scheduler scheduler; /** * Spring上下文 */ private final ApplicationContext context; @Override public void invokeJobDetail(String jobDetailId) throws InvocationTargetException, IllegalAccessException { /* * 根据指定定时任务详情bean的id获取该bean * 因为定时任务详情配置的都是FactoryBean,所以需要添加前缀&来直接获取该bean的MethodInvoker接口, * 而不是使用FactoryBean的getObject方法获取到的JobDetail * 获取到定时任务详情后执行该任务 */ ((MethodInvoker) context.getBean("&" + jobDetailId)).invoke(); } @Override public void start() throws SchedulerException { scheduler.start(); } @Override public void standby() throws SchedulerException { scheduler.standby(); } @Override public void shutdown() throws SchedulerException { scheduler.shutdown(); } } ``` 定时任务接口: ```java /** * 定时任务-接口 * @author sword * @date 2022/11/29 15:33 */ @RestController @RequestMapping("/scheduler-job") @Tag(name = "SchedulerApi", description = "定时任务-接口") @RequiredArgsConstructor public class SchedulerApi { /** * 定时任务-服务 */ private final SchedulerService schedulerService; /** * 手动执行指定定时任务详情 * @param jobDetailId 定时任务详情bean的id * @author sword * @date 2022/11/29 15:33 */ @PostMapping("/invokeJobDetail") @Operation(summary = "手动执行指定定时任务详情") @Parameter(name = "jobDetailId", description = "定时任务详情bean的id", in = ParameterIn.QUERY) public void invokeJobDetail(String jobDetailId) throws InvocationTargetException, IllegalAccessException { schedulerService.invokeJobDetail(jobDetailId); } /** * 启动定时任务 * @author sword * @date 2022/11/29 15:33 */ @PostMapping("/start") @Operation(summary = "启动定时任务") public void start() throws SchedulerException { schedulerService.start(); } /** * 暂停定时任务,可以重新启动 * @author sword * @date 2022/11/29 15:33 */ @PostMapping("/standby") @Operation(summary = "暂停定时任务,可以重新启动") public void standby() throws SchedulerException { schedulerService.standby(); } /** * 关闭定时任务,无法重新启动 * @author sword * @date 2022/11/29 15:33 */ @PostMapping("/shutdown") @Operation(summary = "关闭定时任务,无法重新启动") public void shutdown() throws SchedulerException { schedulerService.shutdown(); } } ``` ![swagger文档](https://gitee.com/debug_life/demo-quartz/raw/master/image/2022-12-08_095255.gif) ## Cron表达式 上面讲的定时任务触发器使用的是CronTrigger,使用Cron表达式来指定触发的时间。 *以下内容来自于 `org.quartz.CronTrigger` 和 `org.quartz.CronExpression` 的javadoc。* Cron表达式包含6个必填字段和一个可选字段,7个字段从左往右按顺序以空格相隔。 从左往右的字段描述如下: | 字段名称 | 允许的值 | 允许的特殊字符 | | -- | -- | -- | | 秒 | 0-59 | , - * / | | 分 | 0-59 | , - * / | | 时 | 0-23 | , - * / | | 日 | 1-31 | , - * ? / L W | | 月 | 0-11 或者 JAN-DEC | , - * / | | 星期几| 1-7 或者 SUN-SAT | , - * ? / L # | | 年(可选)| 空 或者 1970-2199 | , - * / | 字段允许的值和特殊字符不区分大小写。 “\*”字符用于指定所有值。例如,“分”字段中的“\*”表示“每分钟”。 “?”字符可以在“日”和“星期几”字段中使用。它用于指定“无特定值”。当需要在“日”和“星期几”两个字段中的一个字段中指定内容且另一个字段不使用时,如“日”字段值设置为“\*”,即每天,此时“星期几”字段的值可以设置为“?”,即无特定。 “-”字符用于指定范围。例如,“时”字段中的“10-12”表示“10小时、11小时和12小时”。支持溢出范围,即左侧的数字大于右侧的数字,如“22-2”即晚上10点到凌晨2点之间。 “,”字符用于指定列表。例如,“星期几”字段中的“MON,WED,FRI”表示“星期一,星期三和星期五”。 “/”字符用于在所有字段的允许起始值的基础上再次指定需要的增量值,例如,“秒”字段中的“0/15”表示“0秒、15秒、30秒和45秒”。秒字段中的“5/15”表示“5秒、20秒、35秒和50秒”。在“/”之前指定“\*”等同于指定0作为起始值。要注意的是,增量值如果超过了字段的允许值的上限则重新从起始值开始,即“月”字段中的“7/6”仅在7月时触发,没有有效的增量值。 “L”字符是“last”的缩写,可以在“日”和“星期几”字段中使用,在两个字段中有各自不同的含义。例如,“日”字段中的值“L”表示“一个月的最后一天”,如1月31日或者非闰年的2月28日,“L-n”表示“一个月的最后一天的前第n天”,如“L-3”为“一个月的最后一天的前第3天”。“L”在“星期几”字段中单独使用,它只表示“7”或“SAT”,如果“L”作为后缀和“1-7”中的数字一起使用,则表示“当月的最后xxx天”,例如“6L”表示“当月中的最后一个星期五”。使用“L”选项时,不要指定列表或范围如“1,7L”或者“1-7L”,否则会出现无法预计的结果。 “W”字符可以在“日”字段中使用,用于指定最接近给定日期的工作日(星期一至星期五)。例如,如果“日”字段的值为“15W”,则其含义为:“最接近当月15日的工作日”。因此,如果15日是星期六,则将在14日星期五触发;如果15日是星期天,则将在16日星期一触发,如果15日是星期二,则将在15日星期二触发。但是,如果“日”字段的值为“1W”且第1天是星期六,则将在当月3日星期一触发,而不会在上个月的工作日触发,因为它不会“跳过”一个月的日期边界。“W”字符只能和一个月的固定某一天一起使用,不能和日期范围或列表一起使用。 “L”和“W”字符也可以组合为“LW”用于“日”字段,即“当月的最后一个工作日”。 “#”字符可以在“星期几”字段中使用,用于指定每月的第n个星期几。例如,“星期几”字段中的值“6#3”表示当月的第三个星期五(“6”=星期五,“#3”=当月的第3个星期五)。其他示例:“2#1”=当月的第一个星期一,“4#5”=当月第五个星期三。如果指定“#5”且当月没有第五个星期几,则不会触发。如果使用“#”字符,则“星期几”字段中只能有一个表达式(“3#1,6#3”无效,因为有两个表达式)。 案例: | 表达式 | 备注 | | -- | -- | | 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 \* \* ? | 每天14:00到14:59,每分钟一次 | | 0 0/5 14 \* \* ? | 每天14:00到14:55,每5分钟一次 | | 0 0/5 14,18 \* \* ? | 每天14:00到14:55和18:00到18:55,每5分钟一次 | | 0 0-5 14 \* \* ? | 每天14:00到14:05,每分钟一次 | | 0 10,44 14 ? 3 WED | 三月每周三14:10和14: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年、2003年、2004年和2005年每个月的最后一个星期五10:15 | | 0 15 10 ? \* 6#3 | 每月第三个星期五10:15 | ## [相关源码详见gitee](https://gitee.com/debug_life/demo-quartz)