原文链接地址:https://blog.csdn.net/u012907049/article/details/73801122/
前言
本文是对之前的一篇文章Spring+SpringMVC+mybatis+Quartz整合代码部分做的一个修改和补充, 其中最大的变化就是后台框架变成了Spring Boot。
本工程所用到的技术或工具有:
Spring Boot
Mybatis
Quartz
PageHelper
VueJS
ElementUI
MySql数据库
正文
配置
本例中仍然是使用mysql数据库作为Quartz任务持久化的存储载体。对于如何在Mysql数据库中建立表,在上一篇Spring+SpringMVC+mybatis+Quartz整合中已经有了详细的介绍。这里我们来看Spring Boot工程的建立方法。在Spring网站上有关于建立Spring Boot工程的脚手架,我们按如下图的方式设置,然后点击Generate Project即可。
接着我们在IDE中导入这个maven工程,然后可以看到src/main/resource下面有一个名字为application.properties的文件,里面的内容是空的。我们删除这个文件,然后在这个目录下新建一个名为application.yml的文件。这是配置Spring Boot工程的另一种方式,也是Spring Boot官方推荐的一种配置方式。我们在新建的这个yml文件中,加入如下代码
spring:
datasource:
url: jdbc:mysql://190.0.1.88:3306/hello_test?useUnicode=true
username: root
password: root
driver-class-name: com.mysql.jdbc.Driver
mybatis:
mapper-locations:
- classpath:com/example/demo/mapper/*.xml
type-aliases-package: com.example.demo.entity
1
2
3
4
5
6
7
8
9
10
11
上面的代码是对数据库和mybatis的一些配置。
接着我们在当前目录下再新建一个名为quartz.properties的文件。这是对Quartz的配置文件。加入如下代码:
# 固定前缀org.quartz
# 主要分为scheduler、threadPool、jobStore、plugin等部分
#
#
org.quartz.scheduler.instanceName = DefaultQuartzScheduler
org.quartz.scheduler.rmi.export = false
org.quartz.scheduler.rmi.proxy = false
org.quartz.scheduler.wrapJobExecutionInUserTransaction = false
# 实例化ThreadPool时,使用的线程类为SimpleThreadPool
org.quartz.threadPool.class = org.quartz.simpl.SimpleThreadPool
# threadCount和threadPriority将以setter的形式注入ThreadPool实例
# 并发个数
org.quartz.threadPool.threadCount = 5
# 优先级
org.quartz.threadPool.threadPriority = 5
org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread = true
org.quartz.jobStore.misfireThreshold = 5000
# 默认存储在内存中
#org.quartz.jobStore.class = org.quartz.simpl.RAMJobStore
#持久化
org.quartz.jobStore.class = org.quartz.impl.jdbcjobstore.JobStoreTX
org.quartz.jobStore.tablePrefix = QRTZ_
org.quartz.jobStore.dataSource = qzDS
org.quartz.dataSource.qzDS.driver = com.mysql.jdbc.Driver
org.quartz.dataSource.qzDS.URL = jdbc:mysql://190.0.1.88:3306/hello_test?useUnicode=true&characterEncoding=UTF-8
org.quartz.dataSource.qzDS.user = root
org.quartz.dataSource.qzDS.password = root
org.quartz.dataSource.qzDS.maxConnections = 10
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
可以看出和上一篇文章的配置文件完成相同。接着我们在com.example.demo下新建一个名为SchedulerConfig.java的文件。在这个文件里,对刚才我们新建的quartz.properties文件进行读取。
package com.example.demo;
import java.io.IOException;
import java.util.Properties;
import org.quartz.Scheduler;
import org.quartz.ee.servlet.QuartzInitializerListener;
import org.springframework.beans.factory.config.PropertiesFactoryBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.scheduling.quartz.SchedulerFactoryBean;
@Configuration
public class SchedulerConfig {
@Bean(name="SchedulerFactory")
public SchedulerFactoryBean schedulerFactoryBean() throws IOException {
SchedulerFactoryBean factory = new SchedulerFactoryBean();
factory.setQuartzProperties(quartzProperties());
return factory;
}
@Bean
public Properties quartzProperties() throws IOException {
PropertiesFactoryBean propertiesFactoryBean = new PropertiesFactoryBean();
propertiesFactoryBean.setLocation(new ClassPathResource("/quartz.properties"));
//在quartz.properties中的属性被读取并注入后再初始化对象
propertiesFactoryBean.afterPropertiesSet();
return propertiesFactoryBean.getObject();
}
/*
* quartz初始化监听器
*/
@Bean
public QuartzInitializerListener executorListener() {
return new QuartzInitializerListener();
}
/*
* 通过SchedulerFactoryBean获取Scheduler的实例
*/
@Bean(name="Scheduler")
public Scheduler scheduler() throws IOException {
return schedulerFactoryBean().getScheduler();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
注意最下方的QuartzInitializerListener。在SpringMVC中,我们在配置Quartz的时候,要在web.xml中加入如下配置:
org.quartz.ee.servlet.QuartzInitializerListener
1
2
3
4
5
这个监听器可以监听到工程的启动,在工程停止再启动时可以让已有的定时任务继续进行。由于我们目前的工程是Spring Boot,没有web.xml的配置方式,所以我们在上文的SchedulerConfig类中直接注入了这个Bean。
实现
先来看Job类。首先设置一个BaseJob接口,用来继承Job类:
package com.example.demo.job;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
public interface BaseJob extends Job{
public void execute(JobExecutionContext context) throws JobExecutionException;
}
1
2
3
4
5
6
7
8
9
然后两个Job类用来实现BaseJob类:
HelloJob
package com.example.demo.job;
import java.util.Date;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
public class HelloJob implements BaseJob {
private static Logger _log = LoggerFactory.getLogger(HelloJob.class);
public HelloJob() {
}
public void execute(JobExecutionContext context)
throws JobExecutionException {
_log.error("Hello Job执行时间: " + new Date());
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
NewJob
package com.example.demo.job;
import java.util.Date;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
public class NewJob implements BaseJob {
private static Logger _log = LoggerFactory.getLogger(NewJob.class);
public NewJob() {
}
public void execute(JobExecutionContext context)
throws JobExecutionException {
_log.error("New Job执行时间: " + new Date());
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
至于这样做的目的,我们可以先看一下Controller
package com.example.demo.controller;
import java.util.HashMap;
import java.util.Map;
import org.quartz.CronScheduleBuilder;
import org.quartz.CronTrigger;
import org.quartz.JobBuilder;
import org.quartz.JobDetail;
import org.quartz.JobKey;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.TriggerBuilder;
import org.quartz.TriggerKey;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import com.example.demo.entity.JobAndTrigger;
import com.example.demo.job.BaseJob;
import com.example.demo.service.IJobAndTriggerService;
import com.github.pagehelper.PageInfo;
@RestController
@RequestMapping(value="/job")
public class JobController
{
@Autowired
private IJobAndTriggerService iJobAndTriggerService;
//加入Qulifier注解,通过名称注入bean
@Autowired @Qualifier("Scheduler")
private Scheduler scheduler;
private static Logger log = LoggerFactory.getLogger(JobController.class);
@PostMapping(value="/addjob")
public void addjob(@RequestParam(value="jobClassName")String jobClassName,
@RequestParam(value="jobGroupName")String jobGroupName,
@RequestParam(value="cronExpression")String cronExpression) throws Exception
{
addJob(jobClassName, jobGroupName, cronExpression);
}
public void addJob(String jobClassName, String jobGroupName, String cronExpression)throws Exception{
// 启动调度器
scheduler.start();
//构建job信息
JobDetail jobDetail = JobBuilder.newJob(getClass(jobClassName).getClass()).withIdentity(jobClassName, jobGroupName).build();
//表达式调度构建器(即任务执行的时间)
CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(cronExpression);
//按新的cronExpression表达式构建一个新的trigger
CronTrigger trigger = TriggerBuilder.newTrigger().withIdentity(jobClassName, jobGroupName)
.withSchedule(scheduleBuilder).build();
try {
scheduler.scheduleJob(jobDetail, trigger);
} catch (SchedulerException e) {
System.out.println("创建定时任务失败"+e);
throw new Exception("创建定时任务失败");
}
}
@PostMapping(value="/pausejob")
public void pausejob(@RequestParam(value="jobClassName")String jobClassName, @RequestParam(value="jobGroupName")String jobGroupName) throws Exception
{
jobPause(jobClassName, jobGroupName);
}
public void jobPause(String jobClassName, String jobGroupName) throws Exception
{
scheduler.pauseJob(JobKey.jobKey(jobClassName, jobGroupName));
}
@PostMapping(value="/resumejob")
public void resumejob(@RequestParam(value="jobClassName")String jobClassName, @RequestParam(value="jobGroupName")String jobGroupName) throws Exception
{
jobresume(jobClassName, jobGroupName);
}
public void jobresume(String jobClassName, String jobGroupName) throws Exception
{
scheduler.resumeJob(JobKey.jobKey(jobClassName, jobGroupName));
}
@PostMapping(value="/reschedulejob")
public void rescheduleJob(@RequestParam(value="jobClassName")String jobClassName,
@RequestParam(value="jobGroupName")String jobGroupName,
@RequestParam(value="cronExpression")String cronExpression) throws Exception
{
jobreschedule(jobClassName, jobGroupName, cronExpression);
}
public void jobreschedule(String jobClassName, String jobGroupName, String cronExpression) throws Exception
{
try {
TriggerKey triggerKey = TriggerKey.triggerKey(jobClassName, jobGroupName);
// 表达式调度构建器
CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(cronExpression);
CronTrigger trigger = (CronTrigger) scheduler.getTrigger(triggerKey);
// 按新的cronExpression表达式重新构建trigger
trigger = trigger.getTriggerBuilder().withIdentity(triggerKey).withSchedule(scheduleBuilder).build();
// 按新的trigger重新设置job执行
scheduler.rescheduleJob(triggerKey, trigger);
} catch (SchedulerException e) {
System.out.println("更新定时任务失败"+e);
throw new Exception("更新定时任务失败");
}
}
@PostMapping(value="/deletejob")
public void deletejob(@RequestParam(value="jobClassName")String jobClassName, @RequestParam(value="jobGroupName")String jobGroupName) throws Exception
{
jobdelete(jobClassName, jobGroupName);
}
public void jobdelete(String jobClassName, String jobGroupName) throws Exception
{
scheduler.pauseTrigger(TriggerKey.triggerKey(jobClassName, jobGroupName));
scheduler.unscheduleJob(TriggerKey.triggerKey(jobClassName, jobGroupName));
scheduler.deleteJob(JobKey.jobKey(jobClassName, jobGroupName));
}
@GetMapping(value="/queryjob")
public Map
{
PageInfo
Map
map.put("JobAndTrigger", jobAndTrigger);
map.put("number", jobAndTrigger.getTotal());
return map;
}
public static BaseJob getClass(String classname) throws Exception
{
Class> class1 = Class.forName(classname);
return (BaseJob)class1.newInstance();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
注意最下面的这个方法,根据类名称,通过反射得到该类,然后创建一个BaseJob的实例。由于NewJob和HelloJob都实现了BaseJob,所以这里不需要我们手动去判断。这里涉及到了一些java多态调用的机制,篇幅原因不多做解释。
其他的部分,例如service层,dao层以及mapper,与上一篇文章几乎完全相同。我们可以看一下现在的工程结构:
我们可以看到static下的JobManager.html,这个是前端的一个简单的管理页面。Spring Boot的web工程中,静态页面可以放在static目录下。这里贴一下代码,与之前几乎没区别:
:data="tableData"
style="width:100%"
border
>
label="任务名称"
sortable
show-overflow-tooltip>
label="任务所在组"
sortable>
label="任务类名"
sortable>
label="触发器名称"
sortable>
label="触发器所在组"
sortable>
label="表达式"
sortable>
label="时区"
sortable>
type="warning"
@click="handlePause(scope.$index, scope.row)">暂停
type="info"
@click="handleResume(scope.$index, scope.row)">恢复
type="danger"
@click="handleDelete(scope.$index, scope.row)">删除
type="success"
@click="handleUpdate(scope.$index, scope.row)">修改
唯一的区别是这里所用到的所有vue插件和ElementUI插件都是通过CDN方式引入,方便读者下载之后直接运行。需要注意的是,在添加新的任务的时候,填写任务名称时一定要把这个Job类的完整路径输入进来。例如
否则会报找不到该类的错误。对于Cron表达式,可以去在线Cron表达式生成器根据自己的需求自动生成。程序的截图如下
这两个任务执行的log:
2017-06-27 17:23:56.194 ERROR 9972 --- [eduler_Worker-3] com.example.demo.job.HelloJob: Hello Job执行时间: Tue Jun 27 17:23:56 CST 2017
2017-06-27 17:23:57.042 ERROR 9972 --- [eduler_Worker-4] com.example.demo.job.NewJob: New Job执行时间: Tue Jun 27 17:23:57 CST 2017
2017-06-27 17:23:58.051 ERROR 9972 --- [eduler_Worker-5] com.example.demo.job.HelloJob: Hello Job执行时间: Tue Jun 27 17:23:58 CST 2017
2017-06-27 17:24:00.070 ERROR 9972 --- [eduler_Worker-1] com.example.demo.job.NewJob: New Job执行时间: Tue Jun 27 17:24:00 CST 2017
2017-06-27 17:24:00.144 ERROR 9972 --- [eduler_Worker-2] com.example.demo.job.HelloJob: Hello Job执行时间: Tue Jun 27 17:24:00 CST 2017
2017-06-27 17:24:02.099 ERROR 9972 --- [eduler_Worker-3] com.example.demo.job.HelloJob: Hello Job执行时间: Tue Jun 27 17:24:02 CST 2017
2017-06-27 17:24:03.329 ERROR 9972 --- [eduler_Worker-4] com.example.demo.job.NewJob: New Job执行时间: Tue Jun 27 17:24:03 CST 2017
1
2
3
4
5
6
7
工程地址
这个工程已上传至我的github。如果你按上一篇文章的方法已经在mysql数据库中用脚本建立了数据表,那么下载这个工程之后只需要把application.yml和quartz.properties中对数据库连接的配置修改成你自己的数据库配置即可。
运行方法
Spring Boot工程已经集成了服务器。右键点击DemoApplication.java -> Run As -> Java Application即可运行工程。默认端口为8080,启动后在浏览器地址栏输入http://localhost:8080/JobManager.html就可以看到效果。
2017/12/7更新:修改了scheduler的初始化方式:在配置文件中进行初始化bean,并通过autowired的方式注入到controller中。此更新已同步至我的github和本篇文章内的代码。修改了两个文件:SchedulerConfig.java和JobController.java。另:若读者需要修改程序运行端口,可在application.yml中最上面加入以下代码(以端口9065为例):
server:
port: 9065
1
2
并重新启动工程,输入http://localhost:9065/JobManager.html即可。