0%

1.项目结构

在这里插入图片描述

2.Maven

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
<?xml version="1.0" encoding="UTF-8"?>  
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>com.littlefxc</groupId>
<artifactId>learn-quartz-SpringBoot</artifactId>
<version>1.0-snapshot</version>

<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>

<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.4.RELEASE</version>
</parent>

<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-quartz</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.10</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
</dependencies>
</project>

3.数据库-模型

在jar包quartz-2.3.0.jar下有数据库sql文件.

sql文件的包路径地址:org.quartz.impl.jdbcjobstore,选择tables_mysql_innodb.sql

3.1. scheduler_job_info.sql

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
DROP TABLE IF EXISTS `scheduler_job_info`;  
CREATE TABLE `scheduler_job_info` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`cron_expression` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`cron_job` bit(1) NULL DEFAULT NULL,
`job_class` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`job_group` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`job_name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
`scheduler_name` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
`repeat_time` bigint(20) NULL DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE,
UNIQUE INDEX `uk_job_name`(`job_name`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 4 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

SET FOREIGN_KEY_CHECKS = 1;

3.2.实体类

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
package com.littlefxc.example.quartz.enitiy;  

import lombok.Data;

import javax.persistence.*;
import java.io.Serializable;

/**
* @author fengxuechao
* @date 12/19/2018
*/
@Data
@Entity
@Table(name = "scheduler_job_info")
public class SchedulerJob implements Serializable {

private static final long serialVersionUID = -8990533448070839127L;

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

@Column(unique = true)
private String jobName;

private String jobGroup;

private String jobClass;

private String cronExpression;

private Long repeatTime;

private Boolean cronJob;

private String schedulerName;
}

4.配置

4.1.application.properties

com.littlefxc.example.quartz.component.CustomQuartzInstanceIdGenerator表示使用自定义的实例名生成策略,该类代码可以在5.1章节中看到,在数据库上的代码实际效果可以查看到(表qrtz_scheduler_state, 字段INSTANCE_NAME)。

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
spring.application.name=learn-quartz-SpringBoot  

# jackson Config
spring.jackson.time-zone=GMT+8
spring.jackson.date-format=yyyy-MM-dd HH:mm:sss
#spring.jackson.property-naming-strategy=SNAKE_CASE

# DataSource Config
spring.datasource.url=jdbc:mysql://localhost:3306/learn-quartz?useSSL=false
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.druid.filters=slf4j,wall
spring.datasource.druid.initial-size=1
spring.datasource.druid.min-idle=1
spring.datasource.druid.max-active=8
spring.datasource.druid.max-wait=60000
spring.datasource.druid.time-between-eviction-runs-millis=60000
spring.datasource.druid.min-evictable-idle-time-millis=300000
spring.datasource.druid.test-while-idle=true
spring.datasource.druid.test-on-borrow=false
spring.datasource.druid.test-on-return=false
spring.datasource.druid.pool-prepared-statements=true
spring.datasource.druid.max-pool-prepared-statement-per-connection-size=20

# JPA Config
spring.jpa.hibernate.ddl-auto=update
#spring.jpa.open-in-view=false
spring.jpa.show-sql=true

# Quartz Config
spring.quartz.job-store-type=jdbc
spring.quartz.jdbc.initialize-schema=never

spring.quartz.properties.org.quartz.scheduler.instanceName=${spring.application.name}
spring.quartz.properties.org.quartz.scheduler.instanceId=AUTO
spring.quartz.properties.org.quartz.scheduler.instanceIdGenerator.class=com.littlefxc.example.quartz.component.CustomQuartzInstanceIdGenerator
spring.quartz.properties.org.quartz.threadPool.threadCount=20
spring.quartz.properties.org.quartz.jobStore.class=org.quartz.impl.jdbcjobstore.JobStoreTX
spring.quartz.properties.org.quartz.jobStore.driverDelegateClass=org.quartz.impl.jdbcjobstore.StdJDBCDelegate
spring.quartz.properties.org.quartz.jobStore.useProperties=true
spring.quartz.properties.org.quartz.jobStore.misfireThreshold=60000
spring.quartz.properties.org.quartz.jobStore.tablePrefix=qrtz_
spring.quartz.properties.org.quartz.jobStore.isClustered=true
spring.quartz.properties.org.quartz.plugin.shutdownHook.class=org.quartz.plugins.management.ShutdownHookPlugin
spring.quartz.properties.org.quartz.plugin.shutdownHook.cleanShutdown=TRUE

4.2.自定义SchedulerFactoryBean

创建SchedulerFactoryBean。
黄色代码高亮处表示在SchedulerFactoryBean中注入Spring上下文(applicationContext),该类(SchedulerJobFactory)可以在5.2章节中详细查看

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
package com.littlefxc.example.quartz.config;  

import com.littlefxc.example.quartz.component.SchedulerJobFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.quartz.QuartzProperties;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.quartz.SchedulerFactoryBean;

import javax.sql.DataSource;
import java.util.Properties;

/**
* @author fengxuechao
* @date 12/19/2018
*/
@Configuration
public class SchedulerConfig {

@Autowired
private DataSource dataSource;

@Autowired
private ApplicationContext applicationContext;

@Autowired
private QuartzProperties quartzProperties;

/**
* create scheduler factory
*/
@Bean
public SchedulerFactoryBean schedulerFactoryBean() {

SchedulerJobFactory jobFactory = new SchedulerJobFactory();
jobFactory.setApplicationContext(applicationContext);

Properties properties = new Properties();
properties.putAll(quartzProperties.getProperties());

SchedulerFactoryBean factory = new SchedulerFactoryBean();
factory.setOverwriteExistingJobs(true);
factory.setDataSource(dataSource);
factory.setQuartzProperties(properties);
factory.setJobFactory(jobFactory);
return factory;
}

}

5.组件

5.1.CustomQuartzInstanceIdGenerator

用法详见4.1章节

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package com.littlefxc.example.quartz.component;  

import org.quartz.SchedulerException;
import org.quartz.spi.InstanceIdGenerator;

import java.util.UUID;

/**
* @author fengxuechao
* @date 12/19/2018
*/
public class CustomQuartzInstanceIdGenerator implements InstanceIdGenerator {

@Override
public String generateInstanceId() throws SchedulerException {
try {
return UUID.randomUUID().toString();
} catch (Exception ex) {
throw new SchedulerException("Couldn't generate UUID!", ex);
}
}

}

5.2.SchedulerJobFactory

Quartz与Spring结合。
在SchedulerFactory中引入Spring上下文。
用法详见4.2章节。

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
package com.littlefxc.example.quartz.component;  

import org.quartz.spi.TriggerFiredBundle;
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.scheduling.quartz.SpringBeanJobFactory;

/**
* 模仿了:{@link org.springframework.boot.autoconfigure.quartz.AutowireCapableBeanJobFactory}
*
* @author fengxuechao
* @date 12/19/2018
* @see <a href="http://blog.btmatthews.com/?p=40#comment-33797">注入Spring上下文(applicationContext)
*/
public class SchedulerJobFactory extends SpringBeanJobFactory implements ApplicationContextAware {

private AutowireCapableBeanFactory beanFactory;

@Override
public void setApplicationContext(final ApplicationContext context) {
beanFactory = context.getAutowireCapableBeanFactory();
}

@Override
protected Object createJobInstance(final TriggerFiredBundle bundle) throws Exception {
final Object job = super.createJobInstance(bundle);
beanFactory.autowireBean(job);
return job;
}
}

5.3.JobScheduleCreator

Scheduler 创建Job,SimpleTrigger,CronTrigger的封装类。
用法在service 层体现。

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
package com.littlefxc.example.quartz.component;  

import lombok.extern.slf4j.Slf4j;
import org.quartz.CronTrigger;
import org.quartz.JobDataMap;
import org.quartz.JobDetail;
import org.quartz.SimpleTrigger;
import org.springframework.context.ApplicationContext;
import org.springframework.scheduling.quartz.CronTriggerFactoryBean;
import org.springframework.scheduling.quartz.JobDetailFactoryBean;
import org.springframework.scheduling.quartz.QuartzJobBean;
import org.springframework.scheduling.quartz.SimpleTriggerFactoryBean;
import org.springframework.stereotype.Component;

import java.text.ParseException;
import java.util.Date;

/**
* Scheduler创建Job, SimpleTrigger, CronTrigger
*
* @author fengxuechao
* @date 12/19/2018
* @see <a href="https://blog.csdn.net/yangshangwei/article/details/78539433#withmisfirehandlinginstructiondonothing">Quartz-错过触发机制</a>
*/
@Slf4j
@Component
public class JobScheduleCreator {

/**
* Create Quartz Job.
*
* @param jobClass Class whose executeInternal() method needs to be called.
* @param isDurable Job needs to be persisted even after completion. if true, job will be persisted, not otherwise.
* @param context Spring application context.
* @param jobName Job name.
* @param jobGroup Job group.
* @return JobDetail object
*/
public JobDetail createJob(Class<? extends QuartzJobBean> jobClass, boolean isDurable,
ApplicationContext context, String jobName, String jobGroup) {
JobDetailFactoryBean factoryBean = new JobDetailFactoryBean();
factoryBean.setJobClass(jobClass);
factoryBean.setDurability(isDurable);
factoryBean.setApplicationContext(context);
factoryBean.setName(jobName);
factoryBean.setGroup(jobGroup);

// set job data map
JobDataMap jobDataMap = new JobDataMap();
jobDataMap.put(jobName + jobGroup, jobClass.getName());
factoryBean.setJobDataMap(jobDataMap);

factoryBean.afterPropertiesSet();

return factoryBean.getObject();
}

/**
* Create cron trigger.
*
* @param triggerName Trigger name.
* @param startTime Trigger start time.
* @param cronExpression Cron expression.
* @param misFireInstruction Misfire instruction (what to do in case of misfire happens).
* @return {@link CronTrigger}
*/
public CronTrigger createCronTrigger(String triggerName, Date startTime, String cronExpression, int misFireInstruction) {
CronTriggerFactoryBean factoryBean = new CronTriggerFactoryBean();
factoryBean.setName(triggerName);
factoryBean.setStartTime(startTime);
factoryBean.setCronExpression(cronExpression);
factoryBean.setMisfireInstruction(misFireInstruction);
try {
factoryBean.afterPropertiesSet();
} catch (ParseException e) {
log.error(e.getMessage(), e);
}
return factoryBean.getObject();
}

/**
* Create simple trigger.
*
* @param triggerName Trigger name.
* @param startTime Trigger start time.
* @param repeatTime Job repeat period mills
* @param misFireInstruction Misfire instruction (what to do in case of misfire happens).
* @return {@link SimpleTrigger}
*/
public SimpleTrigger createSimpleTrigger(String triggerName, Date startTime, Long repeatTime, int misFireInstruction) {
SimpleTriggerFactoryBean factoryBean = new SimpleTriggerFactoryBean();
factoryBean.setName(triggerName);
factoryBean.setStartTime(startTime);
factoryBean.setRepeatInterval(repeatTime);
factoryBean.setRepeatCount(SimpleTrigger.REPEAT_INDEFINITELY);
factoryBean.setMisfireInstruction(misFireInstruction);
factoryBean.afterPropertiesSet();
return factoryBean.getObject();
}
}

6.Jobs

这里的任务都是实现了org.quartz.Job这个接口

Simple Job

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
package com.littlefxc.example.quartz.jobs;  

import lombok.extern.slf4j.Slf4j;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.springframework.scheduling.quartz.QuartzJobBean;

import java.util.stream.IntStream;

/**
* @author fengxuechao
* @date 12/19/2018
*/
@Slf4j
public class SimpleJob extends QuartzJobBean {
@Override
protected void executeInternal(JobExecutionContext context) throws JobExecutionException {
log.info("{} Start................", context.getJobDetail().getKey());
IntStream.range(0, 5).forEach(i -> {
log.info("Counting - {}", i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
log.error(e.getMessage(), e);
}
});
log.info("{} End................", context.getJobDetail().getKey());
}
}

Cron Job

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
package com.littlefxc.example.quartz.jobs;  

import lombok.extern.slf4j.Slf4j;
import org.quartz.DisallowConcurrentExecution;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.springframework.scheduling.quartz.QuartzJobBean;

import java.util.stream.IntStream;

/**
* @author fengxuechao
* @date 12/19/2018
*/
@Slf4j
@DisallowConcurrentExecution // 这个注解告诉Quartz,一个给定的Job定义(也就是一个JobDetail实例),不并发运行。
public class SampleCronJob extends QuartzJobBean {
@Override
protected void executeInternal(JobExecutionContext context) throws JobExecutionException {
log.info("{} Start................", context.getJobDetail().getKey());
IntStream.range(0, 10).forEach(i -> {
log.info("Counting - {}", i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
log.error(e.getMessage(), e);
}
});
log.info("{} End................", context.getJobDetail().getKey());
}
}

7.控制器层

7.1.QuartzController

工作调度的主要代码

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
package com.littlefxc.example.quartz.controller;  

import com.littlefxc.example.quartz.enitiy.SchedulerJob;
import com.littlefxc.example.quartz.service.SchedulerService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.web.PageableDefault;
import org.springframework.web.bind.annotation.*;

import java.util.Map;

/**
* @author fengxuechao
* @date 12/19/2018
**/
@RestController
@RequestMapping("/job")
@Slf4j
public class QuartzController {

private final SchedulerService schedulerService;

@Autowired
public QuartzController(SchedulerService schedulerService) {
this.schedulerService = schedulerService;
}

/**
* 添加
*
* @param jobInfo
*/
@PostMapping(value = "/addjob")
public void addjob(@RequestBody SchedulerJob jobInfo) {
schedulerService.scheduleNewJob(jobInfo);
}

/**
* 暂停
*
* @param jobName
* @param jobGroup
*/
@PostMapping(value = "/pausejob")
public void pausejob(
@RequestParam String jobName, @RequestParam String jobGroup) {
schedulerService.pauseJob(jobName, jobGroup);
}

/**
* 恢复启动
*
* @param jobName
* @param jobGroup
*/
@PostMapping(value = "/resumejob")
public void resumejob(@RequestParam String jobName, @RequestParam String jobGroup) {
schedulerService.resumeJob(jobName, jobGroup);
}

/**
* 更新:移除older trigger,添加new trigger
*
* @param jobInfo
*/
@PostMapping(value = "/reschedulejob")
public void rescheduleJob(@RequestBody SchedulerJob jobInfo) {
schedulerService.updateScheduleJob(jobInfo);
}

/**
* 删除
*
* @param jobName
* @param jobGroup
*/
@PostMapping(value = "/deletejob")
public void deletejob(@RequestParam String jobName, @RequestParam String jobGroup) {
schedulerService.deleteJob(jobName, jobGroup);
}

/**
* 查询
*
* @param pageable
* @param cron
* @return
*/
@GetMapping(value = "/queryjob")
public Page<Map<String, Object>> queryjob(
@PageableDefault Pageable pageable, @RequestParam Boolean cron) {
return schedulerService.findAll(pageable, cron);
}
}

7.2.SchedulerController

仅对自定义数据库(scheduler_job_info)操作的控制器。

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
package com.littlefxc.example.quartz.controller;  

import com.littlefxc.example.quartz.enitiy.SchedulerJob;
import com.littlefxc.example.quartz.service.SchedulerService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

/**
* @author fengxuechao
* @date 12/20/2018
**/
@RestController
@RequestMapping("/job-info")
public class SchedulerController {

@Autowired
private SchedulerService schedulerService;

/**
* 根据jobName查询
* @param jobName
* @return {@link SchedulerJob}
*/
@GetMapping("/findOne")
public SchedulerJob findOne(@RequestParam String jobName) {
return schedulerService.findOne(jobName);
}
}

8.Service层

8.1.SchedulerServiceImpl

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
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
package com.littlefxc.example.quartz.service.impl;  

import com.littlefxc.example.quartz.component.JobScheduleCreator;
import com.littlefxc.example.quartz.enitiy.SchedulerJob;
import com.littlefxc.example.quartz.repository.SchedulerRepository;
import com.littlefxc.example.quartz.service.SchedulerService;
import lombok.extern.slf4j.Slf4j;
import org.quartz.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.scheduling.quartz.QuartzJobBean;
import org.springframework.scheduling.quartz.SchedulerFactoryBean;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.Date;
import java.util.List;
import java.util.Map;

/**
* @author fengxuechao
* @date 12/19/2018
*/
@Slf4j
@Transactional(rollbackFor = Exception.class)
@Service
public class SchedulerServiceImpl implements SchedulerService {

@Autowired
private SchedulerFactoryBean schedulerFactoryBean;

@Autowired
private SchedulerRepository schedulerRepository;

@Autowired
private ApplicationContext context;

@Autowired
private JobScheduleCreator scheduleCreator;

/**
* 启动所有的在表scheduler_job_info中记录的job
*/
@Override
public void startAllSchedulers() {
List<SchedulerJob> jobInfoList = schedulerRepository.findAll();
if (jobInfoList != null) {
Scheduler scheduler = schedulerFactoryBean.getScheduler();
jobInfoList.forEach(jobInfo -> {
try {
JobDetail jobDetail = JobBuilder.newJob((Class<? extends QuartzJobBean>) Class.forName(jobInfo.getJobClass()))
.withIdentity(jobInfo.getJobName(), jobInfo.getJobGroup()).build();
if (!scheduler.checkExists(jobDetail.getKey())) {
Trigger trigger;
jobDetail = scheduleCreator.createJob((Class<? extends QuartzJobBean>) Class.forName(jobInfo.getJobClass()),
false, context, jobInfo.getJobName(), jobInfo.getJobGroup());

if (jobInfo.getCronJob() && CronExpression.isValidExpression(jobInfo.getCronExpression())) {
trigger = scheduleCreator.createCronTrigger(jobInfo.getJobName(), new Date(),
jobInfo.getCronExpression(), CronTrigger.MISFIRE_INSTRUCTION_DO_NOTHING);
} else {
trigger = scheduleCreator.createSimpleTrigger(jobInfo.getJobName(), new Date(),
jobInfo.getRepeatTime(), SimpleTrigger.MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_REMAINING_COUNT);
}

scheduler.scheduleJob(jobDetail, trigger);

}
} catch (ClassNotFoundException e) {
log.error("Class Not Found - {}", jobInfo.getJobClass(), e);
} catch (SchedulerException e) {
log.error(e.getMessage(), e);
}
});
}
}

@Override
public void scheduleNewJob(SchedulerJob jobInfo) {
try {
Scheduler scheduler = schedulerFactoryBean.getScheduler();

JobDetail jobDetail = JobBuilder.newJob((Class<? extends QuartzJobBean>) Class.forName(jobInfo.getJobClass()))
.withIdentity(jobInfo.getJobName(), jobInfo.getJobGroup()).build();
if (!scheduler.checkExists(jobDetail.getKey())) {

jobDetail = scheduleCreator.createJob((Class<? extends QuartzJobBean>) Class.forName(jobInfo.getJobClass()),
false, context, jobInfo.getJobName(), jobInfo.getJobGroup());

Trigger trigger;
if (jobInfo.getCronJob()) {
trigger = scheduleCreator.createCronTrigger(jobInfo.getJobName(), new Date(), jobInfo.getCronExpression(),
CronTrigger.MISFIRE_INSTRUCTION_DO_NOTHING);
} else {
trigger = scheduleCreator.createSimpleTrigger(jobInfo.getJobName(), new Date(), jobInfo.getRepeatTime(),
SimpleTrigger.MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_REMAINING_COUNT);
}

scheduler.scheduleJob(jobDetail, trigger);
jobInfo.setSchedulerName(schedulerFactoryBean.getScheduler().getSchedulerName());
schedulerRepository.save(jobInfo);
} else {
log.error("scheduleNewJobRequest.jobAlreadyExist");
}
} catch (ClassNotFoundException e) {
log.error("Class Not Found - {}", jobInfo.getJobClass(), e);
} catch (SchedulerException e) {
log.error(e.getMessage(), e);
}
}

@Override
public void updateScheduleJob(SchedulerJob jobInfo) {
Trigger newTrigger;
if (jobInfo.getCronJob()) {
newTrigger = scheduleCreator.createCronTrigger(jobInfo.getJobName(), new Date(), jobInfo.getCronExpression(),
CronTrigger.MISFIRE_INSTRUCTION_DO_NOTHING);
} else {
newTrigger = scheduleCreator.createSimpleTrigger(jobInfo.getJobName(), new Date(), jobInfo.getRepeatTime(),
SimpleTrigger.MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_REMAINING_COUNT);
}
try {
schedulerFactoryBean.getScheduler().rescheduleJob(TriggerKey.triggerKey(jobInfo.getJobName()), newTrigger);
jobInfo.setSchedulerName(schedulerFactoryBean.getScheduler().getSchedulerName());
schedulerRepository.save(jobInfo);
} catch (SchedulerException e) {
log.error(e.getMessage(), e);
}
}

/**
* unscheduleJob(TriggerKey triggerKey)只是不再调度触发器,所以,当其他的触发器引用了这个Job,它们不会被改变
*
* @param jobName
* @return
*/
@Override
public boolean unScheduleJob(String jobName) {
try {
return schedulerFactoryBean.getScheduler().unscheduleJob(new TriggerKey(jobName));
} catch (SchedulerException e) {
log.error("Failed to un-schedule job - {}", jobName, e);
return false;
}
}

/**
* deleteJob(JobKey jobKey):<br>
* 1.循环遍历所有引用此Job的触发器,以取消它们的调度(to unschedule them)<br>
* 2.从jobstore中删除Job
*
* @param jobName job name
* @param jobGroup job group
* @return
*/
@Override
public boolean deleteJob(String jobName, String jobGroup) {
try {
boolean deleteJob = schedulerFactoryBean.getScheduler().deleteJob(new JobKey(jobName, jobGroup));
if (deleteJob) {
SchedulerJob job = schedulerRepository.findSchedulerJobByJobName(jobName);
schedulerRepository.delete(job);
}
return deleteJob;
} catch (SchedulerException e) {
log.error("Failed to delete job - {}", jobName, e);
return false;
}
}

/**
* 暂停
*
* @param jobName job name
* @param jobGroup job group
* @return
*/
@Override
public boolean pauseJob(String jobName, String jobGroup) {
try {
schedulerFactoryBean.getScheduler().pauseJob(new JobKey(jobName, jobGroup));
return true;
} catch (SchedulerException e) {
log.error("Failed to pause job - {}", jobName, e);
return false;
}
}

/**
* 恢复
*
* @param jobName job name
* @param jobGroup job group
* @return
*/
@Override
public boolean resumeJob(String jobName, String jobGroup) {
try {
schedulerFactoryBean.getScheduler().resumeJob(new JobKey(jobName, jobGroup));
return true;
} catch (SchedulerException e) {
log.error("Failed to resume job - {}", jobName, e);
return false;
}
}

@Override
public boolean startJobNow(String jobName, String jobGroup) {
try {
schedulerFactoryBean.getScheduler().triggerJob(new JobKey(jobName, jobGroup));
return true;
} catch (SchedulerException e) {
log.error("Failed to start new job - {}", jobName, e);
return false;
}
}

/**
* 分页查询
*
* @param pageable
* @param cron true: cron trigger, false: simple trigger
* @return
*/
@Transactional(readOnly = true)// 方法上注解属性会覆盖类注解上的相同属性
@Override
public Page<Map<String, Object>> findAll(Pageable pageable, Boolean cron) {
if (cron) {
return schedulerRepository.getJobWithCronTrigger(pageable);
} else {
return schedulerRepository.getJobWithSimpleTrigger(pageable);
}
}

/**
* 根据jobName查询单条记录
*
* @param jobName
* @return
*/
@Transactional(readOnly = true)
@Override
public SchedulerJob findOne(String jobName) {
return schedulerRepository.findSchedulerJobByJobName(jobName);
}

}

9.Dao层

9.1.SchedulerRepository

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
package com.littlefxc.example.quartz.repository;  

import com.littlefxc.example.quartz.enitiy.SchedulerJob;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.stereotype.Repository;

import java.util.Map;

/**
* @author fengxuechao
* @date 12/19/2018
*/
@Repository
public interface SchedulerRepository extends JpaRepository<SchedulerJob, Long> {

/**
* 仅查询simple trigger关联的Job
* 不查询cron trigger关联的job
*
* @param pageable 分页信息
* @return
*/
@Query(value = "select " +
"j.JOB_NAME, " +
"j.JOB_GROUP, " +
"j.JOB_CLASS_NAME, " +
"t.TRIGGER_NAME, " +
"t.TRIGGER_GROUP, " +
"s.REPEAT_INTERVAL, " +
"s.TIMES_TRIGGERED " +
"from qrtz_job_details as j " +
"join qrtz_triggers as t " +
"join qrtz_simple_triggers as s ON j.JOB_NAME = t.JOB_NAME " +
"and t.TRIGGER_NAME = s.TRIGGER_NAME " +
"and t.TRIGGER_GROUP = s.TRIGGER_GROUP " +
"where j.SCHED_NAME = 'schedulerFactoryBean' " +
"order by ?#{#pageable}",
countQuery = "select count(1) " +
"from qrtz_job_details as j " +
"join qrtz_triggers as t " +
"join qrtz_cron_triggers as c ON j.JOB_NAME = t.JOB_NAME " +
"and t.TRIGGER_NAME = c.TRIGGER_NAME " +
"and t.TRIGGER_GROUP = c.TRIGGER_GROUP " +
"where j.SCHED_NAME = 'schedulerFactoryBean' ",
nativeQuery = true)
Page<Map<String, Object>> getJobWithSimpleTrigger(Pageable pageable);

/**
* 仅查询cron trigger关联的Job
* 不查询simple trigger关联的job
*
* @param pageable
* @return
*/
@Query(value = "select " +
"j.JOB_NAME, " +
"j.JOB_GROUP, " +
"j.JOB_CLASS_NAME, " +
"t.TRIGGER_NAME, " +
"t.TRIGGER_GROUP, " +
"c.CRON_EXPRESSION, " +
"c.TIME_ZONE_ID " +
"from qrtz_job_details as j " +
"join qrtz_triggers as t " +
"join qrtz_cron_triggers as c ON j.JOB_NAME = t.JOB_NAME " +
"and t.TRIGGER_NAME = c.TRIGGER_NAME " +
"and t.TRIGGER_GROUP = c.TRIGGER_GROUP " +
"where j.SCHED_NAME = 'schedulerFactoryBean' " +
"order by ?#{#pageable}",
countQuery = "select count(1) " +
"from qrtz_job_details as j " +
"join qrtz_triggers as t " +
"join qrtz_cron_triggers as c ON j.JOB_NAME = t.JOB_NAME " +
"and t.TRIGGER_NAME = c.TRIGGER_NAME " +
"and t.TRIGGER_GROUP = c.TRIGGER_GROUP " +
"where j.SCHED_NAME = 'schedulerFactoryBean' ",
nativeQuery = true)
Page<Map<String, Object>> getJobWithCronTrigger(Pageable pageable);

/**
* 根据JobName查询SchedulerJob
*
* @param jobName
* @return SchedulerJob
*/
SchedulerJob findSchedulerJobByJobName(String jobName);
}

10.网页Vue+ElementUI实现

10.1.simple.html

仅对Simple Trigger管理

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
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
<!DOCTYPE html>  
<html>
<head>
<meta charset="UTF-8">
<title>QuartzDemo</title>
<link rel="stylesheet" href="https://unpkg.com/element-ui/lib/theme-chalk/index.css">
<script src="https://unpkg.com/vue/dist/vue.js"></script>
<script src="https://cdn.bootcss.com/vue-resource/1.5.1/vue-resource.js"></script>
<script src="https://unpkg.com/element-ui/lib/index.js"></script>

<style>
#top {
background: #20A0FF;
padding: 5px;
overflow: hidden
}
</style>

</head>
<body>
<div id="test">

<a href="cron.html">goto simple.html</a>

<div id="top">
<el-button type="text" @click="search" style="color:white">查询</el-button>
<el-button type="text" @click="handleadd" style="color:white">添加</el-button>
</span>
</div>

<br/>

<div style="margin-top:15px">

<el-table
ref="testTable"
:data="tableData"
style="width:100%"
border
>
<el-table-column
prop="JOB_NAME"
label="任务名称"
sortable
show-overflow-tooltip>
</el-table-column>

<el-table-column
prop="JOB_GROUP"
label="任务所在组"
sortable>
</el-table-column>

<el-table-column
prop="JOB_CLASS_NAME"
label="任务类名"
sortable>
</el-table-column>

<el-table-column
prop="TRIGGER_NAME"
label="触发器名称"
sortable>
</el-table-column>

<el-table-column
prop="TRIGGER_GROUP"
label="触发器所在组"
sortable>
</el-table-column>

<el-table-column
prop="REPEAT_INTERVAL"
label="触发间隔(毫秒)"
sortable>
</el-table-column>

<el-table-column
prop="TIMES_TRIGGERED"
label="已触发次数"
sortable>
</el-table-column>

<el-table-column label="操作" width="300">
<template scope="scope">
<el-button
size="small"
type="warning"
@click="handlePause(scope.$index, scope.row)">暂停
</el-button>

<el-button
size="small"
type="info"
@click="handleResume(scope.$index, scope.row)">恢复
</el-button>

<el-button
size="small"
type="danger"
@click="handleDelete(scope.$index, scope.row)">删除
</el-button>

<el-button
size="small"
type="success"
@click="handleUpdate(scope.$index, scope.row)">修改
</el-button>
</template>
</el-table-column>
</el-table>

<div align="center">
<el-pagination
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
:current-page="currentPage"
:page-sizes="[10, 20, 30, 40]"
:page-size="pagesize"
layout="total, sizes, prev, pager, next, jumper"
:total="totalCount">
</el-pagination>
</div>
</div>

<el-dialog title="添加任务" :visible.syn="dialogFormVisible">
<el-form :model="form">
<el-form-item label="任务名称" label-width="120px" style="width:35%">
<el-input v-model="form.jobName" auto-complete="off"></el-input>
</el-form-item>
<el-form-item label="任务分组" label-width="120px" style="width:35%">
<el-input v-model="form.jobGroup" auto-complete="off"></el-input>
</el-form-item>
<el-form-item label="任务类名" label-width="120px" style="width:35%">
<el-input v-model="form.jobClass" auto-complete="off"></el-input>
</el-form-item>
<el-form-item label="触发器类型" label-width="120px" style="width:35%">
<el-switch
v-model="form.cronJob"
active-color="#13ce66"
inactive-color="#ff4949"
active-text="cron"
inactive-text="simple">
</el-switch>
</el-form-item>
<el-form-item label="表达式" v-show="form.cronJob" label-width="120px" style="width:35%">
<el-input v-model="form.cronExpression" auto-complete="off"></el-input>
</el-form-item>
<el-form-item label="触发间隔(毫秒)" v-show="!form.cronJob" label-width="120px" style="width:35%">
<el-input v-model="form.repeatTime" auto-complete="off"></el-input>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="dialogFormVisible = false">取 消</el-button>
<el-button type="primary" @click="add">确 定</el-button>
</div>
</el-dialog>

<el-dialog title="修改任务" :visible.syn="updateFormVisible">
<el-form :model="updateform">
<el-form-item label="任务名称" label-width="120px" style="width:35%">
<el-input v-model="updateform.jobName" auto-complete="off"></el-input>
</el-form-item>
<el-form-item label="任务分组" label-width="120px" style="width:35%">
<el-input v-model="updateform.jobGroup" auto-complete="off"></el-input>
</el-form-item>
<el-form-item label="任务类名" label-width="120px" style="width:35%">
<el-input v-model="updateform.jobClass" auto-complete="off"></el-input>
</el-form-item>
<el-form-item label="触发器类型" label-width="120px" style="width:35%">
<el-switch
v-model="updateform.cronJob"
active-color="#13ce66"
inactive-color="#ff4949"
active-text="cron"
inactive-text="simple">
</el-switch>
</el-form-item>
<el-form-item label="表达式" v-show="updateform.cronJob" label-width="120px" style="width:35%">
<el-input v-model="updateform.cronExpression" auto-complete="off"></el-input>
</el-form-item>
<el-form-item label="触发间隔(毫秒)" v-show="!updateform.cronJob" label-width="120px" style="width:35%">
<el-input v-model="updateform.repeatTime" auto-complete="off"></el-input>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="updateFormVisible = false">取 消</el-button>
<el-button type="primary" @click="update">确 定</el-button>
</div>
</el-dialog>

</div>

<footer align="center">
<p>© Quartz 任务管理</p>
</footer>

<script>
var vue = new Vue({
el: "#test",
data: {
//表格当前页数据
tableData: [],

//请求的URL
url: '',

//默认每页数据量
pagesize: 10,

//当前页码
currentPage: 1,

//查询的页码
start: 1,

//默认数据总数
totalCount: 1000,

//添加对话框默认可见性
dialogFormVisible: false,

//修改对话框默认可见性
updateFormVisible: false,

//提交的表单
form: {
jobName: '',
jobGroup: '',
jobClass: '',
cronJob: false,
repeatTime: 0,
cronExpression: ''
},

// 修改的表单
updateform: {
id: 0,
jobName: '',
jobGroup: '',
jobClass: '',
cronJob: false,
repeatTime: 0,
cronExpression: ''
},
},

methods: {

//从服务器读取数据
loadData: function (pageNum, pageSize) {
this.$http.get('job/queryjob?cron=false&' + 'page=' + pageNum + '&size=' + pageSize).then(function (res) {
console.log(res);
this.tableData = res.body.content;
this.totalCount = res.body.numberOfElements;
}, function () {
console.log('failed');
});
},

//单行删除
handleDelete: function (index, row) {
this.$http.post('job/deletejob', {
"jobName": row.JOB_NAME,
"jobGroup": row.JOB_GROUP
}, {emulateJSON: true}).then(function (res) {
this.loadData(this.currentPage, this.pagesize);
}, function () {
console.log('failed');
});
},

//暂停任务
handlePause: function (index, row) {
this.$http.post('job/pausejob', {
"jobName": row.JOB_NAME,
"jobGroup": row.JOB_GROUP
}, {emulateJSON: true}).then(function (res) {
this.loadData(this.currentPage, this.pagesize);
}, function () {
console.log('failed');
});
},

//恢复任务
handleResume: function (index, row) {
this.$http.post('job/resumejob', {
"jobName": row.JOB_NAME,
"jobGroup": row.JOB_GROUP
}, {emulateJSON: true}).then(function (res) {
this.loadData(this.currentPage, this.pagesize);
}, function () {
console.log('failed');
});
},

//搜索
search: function () {
this.loadData(this.currentPage, this.pagesize);
},

//弹出对话框
handleadd: function () {
this.dialogFormVisible = true;
},

//添加
add: function () {
this.$http.post('job/addjob', this.form, {
headers: {'Content-Type': "application/json;charset=utf-8"}
}).then(function (res) {
this.loadData(this.currentPage, this.pagesize);
this.dialogFormVisible = false;
}, function () {
console.log('failed');
});
},

//更新
handleUpdate: function (index, row) {
console.log(row);
this.updateFormVisible = true;
this.updateform.jobName = row.JOB_NAME;
this.updateform.jobGroup = row.JOB_GROUP;
this.updateform.jobClass = row.JOB_CLASS_NAME;
this.updateform.cronJob = false;
this.updateform.repeatTime = row.REPEAT_INTERVAL;
this.$http.get('job-info/findOne?jobName=' + row.JOB_NAME).then(function (res) {
this.updateform.id = res.body.id;
this.updateform.cronExpression = row.cronExpression;
}, function () {
console.log('failed');
});
console.log(this.updateform)
},

//更新任务
update: function () {
this.$http.post('job/reschedulejob', this.updateform, {
headers: {'Content-Type': "application/json;charset=utf-8"}
}).then(function (res) {
this.loadData(this.currentPage, this.pagesize);
this.updateFormVisible = false;
}, function () {
console.log('failed');
});

},

//每页显示数据量变更
handleSizeChange: function (val) {
this.pagesize = val;
this.loadData(this.currentPage, this.pagesize);
},

//页码变更
handleCurrentChange: function (val) {
this.currentPage = val;
this.loadData(this.currentPage, this.pagesize);
},
},

});

//载入数据
vue.loadData(vue.currentPage, vue.pagesize);
</script>

</body>
</html>

10.2.cron.html

进队Cron Trigger 管理

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
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
<!DOCTYPE html>  
<html>
<head>
<meta charset="UTF-8">
<title>QuartzDemo</title>
<link rel="stylesheet" href="https://unpkg.com/element-ui/lib/theme-chalk/index.css">
<script src="https://unpkg.com/vue/dist/vue.js"></script>
<script src="http://cdn.bootcss.com/vue-resource/1.3.4/vue-resource.js"></script>
<script src="https://unpkg.com/element-ui/lib/index.js"></script>

<style>
#top {
background: #20A0FF;
padding: 5px;
overflow: hidden
}
</style>

</head>
<body>
<div id="test">

<a href="simple.html">goto cron.html</a>

<div id="top">
<el-button type="text" @click="search" style="color:white">查询</el-button>
<el-button type="text" @click="handleadd" style="color:white">添加</el-button>
</span>
</div>

<br/>

<div style="margin-top:15px">

<el-table
ref="testTable"
:data="tableData"
style="width:100%"
border
>
<el-table-column
prop="JOB_NAME"
label="任务名称"
sortable
show-overflow-tooltip>
</el-table-column>

<el-table-column
prop="JOB_GROUP"
label="任务所在组"
sortable>
</el-table-column>

<el-table-column
prop="JOB_CLASS_NAME"
label="任务类名"
sortable>
</el-table-column>

<el-table-column
prop="TRIGGER_NAME"
label="触发器名称"
sortable>
</el-table-column>

<el-table-column
prop="TRIGGER_GROUP"
label="触发器所在组"
sortable>
</el-table-column>

<el-table-column
prop="CRON_EXPRESSION"
label="表达式"
sortable>
</el-table-column>

<el-table-column
prop="TIME_ZONE_ID"
label="时区"
sortable>
</el-table-column>

<el-table-column label="操作" width="300">
<template scope="scope">
<el-button
size="small"
type="warning"
@click="handlePause(scope.$index, scope.row)">暂停
</el-button>

<el-button
size="small"
type="info"
@click="handleResume(scope.$index, scope.row)">恢复
</el-button>

<el-button
size="small"
type="danger"
@click="handleDelete(scope.$index, scope.row)">删除
</el-button>

<el-button
size="small"
type="success"
@click="handleUpdate(scope.$index, scope.row)">修改
</el-button>
</template>
</el-table-column>
</el-table>

<div align="center">
<el-pagination
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
:current-page="currentPage"
:page-sizes="[10, 20, 30, 40]"
:page-size="pagesize"
layout="total, sizes, prev, pager, next, jumper"
:total="totalCount">
</el-pagination>
</div>
</div>

<el-dialog title="添加任务" :visible.syn="dialogFormVisible">
<el-form :model="form">
<el-form-item label="任务名称" label-width="120px" style="width:35%">
<el-input v-model="form.jobName" auto-complete="off"></el-input>
</el-form-item>
<el-form-item label="任务分组" label-width="120px" style="width:35%">
<el-input v-model="form.jobGroup" auto-complete="off"></el-input>
</el-form-item>
<el-form-item label="任务类名" label-width="120px" style="width:35%">
<el-input v-model="form.jobClass" auto-complete="off"></el-input>
</el-form-item>
<el-form-item label="触发器类型" label-width="120px" style="width:35%">
<el-switch
v-model="form.cronJob"
active-color="#13ce66"
inactive-color="#ff4949"
active-text="cron"
inactive-text="simple">
</el-switch>
</el-form-item>
<el-form-item label="表达式" v-show="form.cronJob" label-width="120px" style="width:35%">
<el-input v-model="form.cronExpression" auto-complete="off"></el-input>
</el-form-item>
<el-form-item label="触发间隔(毫秒)" v-show="!form.cronJob" label-width="120px" style="width:35%">
<el-input v-model="form.repeatTime" auto-complete="off"></el-input>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="dialogFormVisible = false">取 消</el-button>
<el-button type="primary" @click="add">确 定</el-button>
</div>
</el-dialog>

<el-dialog title="修改任务" :visible.syn="updateFormVisible">
<el-form :model="updateform">
<el-form-item label="任务名称" label-width="120px" style="width:35%">
<el-input v-model="updateform.jobName" auto-complete="off"></el-input>
</el-form-item>
<el-form-item label="任务分组" label-width="120px" style="width:35%">
<el-input v-model="updateform.jobGroup" auto-complete="off"></el-input>
</el-form-item>
<el-form-item label="任务类名" label-width="120px" style="width:35%">
<el-input v-model="updateform.jobClass" auto-complete="off"></el-input>
</el-form-item>
<el-form-item label="触发器类型" label-width="120px" style="width:35%">
<el-switch
v-model="updateform.cronJob"
active-color="#13ce66"
inactive-color="#ff4949"
active-text="cron"
inactive-text="simple">
</el-switch>
</el-form-item>
<el-form-item label="表达式" v-show="updateform.cronJob" label-width="120px" style="width:35%">
<el-input v-model="updateform.cronExpression" auto-complete="off"></el-input>
</el-form-item>
<el-form-item label="触发间隔(毫秒)" v-show="!updateform.cronJob" label-width="120px" style="width:35%">
<el-input v-model="updateform.repeatTime" auto-complete="off"></el-input>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="updateFormVisible = false">取 消</el-button>
<el-button type="primary" @click="update">确 定</el-button>
</div>
</el-dialog>

</div>

<footer align="center">
<p>© Quartz 任务管理</p>
</footer>

<script>
var vue = new Vue({
el: "#test",
data: {
//表格当前页数据
tableData: [],

//请求的URL
url: 'job/queryjob',

//默认每页数据量
pagesize: 10,

//当前页码
currentPage: 1,

//查询的页码
start: 1,

//默认数据总数
totalCount: 1000,

//添加对话框默认可见性
dialogFormVisible: false,

//修改对话框默认可见性
updateFormVisible: false,

//提交的表单
form: {
jobName: '',
jobGroup: '',
jobClass: '',
cronJob: true,
repeatTime: 0,
cronExpression: ''
},

// 修改的表单
updateform: {
id: 0,
jobName: '',
jobGroup: '',
jobClass: '',
cronJob: true,
repeatTime: 0,
cronExpression: ''
},
},

methods: {

//从服务器读取数据
loadData: function (pageNum, pageSize) {
this.$http.get('job/queryjob?cron=true&' + 'page=' + pageNum + '&size=' + pageSize).then(function (res) {
console.log(res);
this.tableData = res.body.content;
this.totalCount = res.body.numberOfElements;
}, function () {
console.log('failed');
});
},

//单行删除
handleDelete: function (index, row) {
this.$http.post('job/deletejob', {
"jobName": row.JOB_NAME,
"jobGroup": row.JOB_GROUP
}, {emulateJSON: true}).then(function (res) {
this.loadData(this.currentPage, this.pagesize);
}, function () {
console.log('failed');
});
},

//暂停任务
handlePause: function (index, row) {
this.$http.post('job/pausejob', {
"jobName": row.JOB_NAME,
"jobGroup": row.JOB_GROUP
}, {emulateJSON: true}).then(function (res) {
this.loadData(this.currentPage, this.pagesize);
}, function () {
console.log('failed');
});
},

//恢复任务
handleResume: function (index, row) {
this.$http.post('job/resumejob', {
"jobName": row.JOB_NAME,
"jobGroup": row.JOB_GROUP
}, {emulateJSON: true}).then(function (res) {
this.loadData(this.currentPage, this.pagesize);
}, function () {
console.log('failed');
});
},

//搜索
search: function () {
this.loadData(this.currentPage, this.pagesize);
},

//弹出对话框
handleadd: function () {
this.dialogFormVisible = true;
},

//添加
add: function () {
this.$http.post('job/addjob', this.form, {
headers: {'Content-Type': "application/json;charset=utf-8"}
}).then(function (res) {
this.loadData(this.currentPage, this.pagesize);
this.dialogFormVisible = false;
}, function () {
console.log('failed');
});
},

//更新
handleUpdate: function (index, row) {
console.log(row);
this.updateFormVisible = true;
this.updateform.jobName = row.JOB_NAME;
this.updateform.jobGroup = row.JOB_GROUP;
this.updateform.jobClass = row.JOB_CLASS_NAME;
this.updateform.cronJob = true;
this.updateform.cronExpression = row.CRON_EXPRESSION;
this.$http.get('job-info/findOne?jobName=' + row.JOB_NAME).then(function (res) {
this.updateform.id = res.body.id;
this.updateform.repeatTime = res.body.repeatTime;
}, function () {
console.log('failed');
});
console.log(this.updateform)
},

//更新任务
update: function () {
this.$http.post('job/reschedulejob', this.updateform, {
headers: {'Content-Type': "application/json;charset=utf-8"}
}).then(function (res) {
this.loadData(this.currentPage, this.pagesize);
this.updateFormVisible = false;
}, function () {
console.log('failed');
});

},

//每页显示数据量变更
handleSizeChange: function (val) {
this.pagesize = val;
this.loadData(this.currentPage, this.pagesize);
},

//页码变更
handleCurrentChange: function (val) {
this.currentPage = val;
this.loadData(this.currentPage, this.pagesize);
},
},

});
//载入数据
vue.loadData(vue.currentPage, vue.pagesize);
</script>

</body>
</html>

11. 引用

https://blog.csdn.net/u012907049/article/details/73801122
https://blog.csdn.net/yangshangwei/article/details/78539433#withmisfirehandlinginstructiondonothing
http://blog.btmatthews.com/?p=40#comment-33797
https://www.baeldung.com/spring-quartz-schedule

spring-boot启动流程

SpringApplication的run方法的实现是我们本次旅程的主要线路,该方法的主要流程大体可以归纳如下:

  1. 如果我们使用的是SpringApplication的静态run方法,那么,这个方法里面首先要创建一个SpringApplication对象实例,然后调用这个创建好的SpringApplication的实例方法。在SpringApplication实例初始化的时候,它会提前做几件事情:
    根据classpath里面是否存在某个特征类org.springframework.web.context.ConfigurableWebApplicationContext来决定是否应该创建一个为Web应用使用的ApplicationContext类型。
    使用SpringFactoriesLoader在应用的classpath中查找并加载所有可用的ApplicationContextInitializer
    使用SpringFactoriesLoader在应用的classpath中查找并加载所有可用的ApplicationListener
    推断并设置main方法的定义类。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    public class SpringApplication {
    // ...
    public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
    this.resourceLoader = resourceLoader;
    Assert.notNull(primarySources, "PrimarySources must not be null");
    this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
    this.webApplicationType = deduceWebApplicationType();

    // 查找并加载所有可用的ApplicationContextInitializer
    setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
    // 查找并加载所有可用的ApplicationListener
    setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));

    this.mainApplicationClass = deduceMainApplicationClass();
    }
    }
  2. SpringApplication实例初始化完成并且完成设置后,就开始执行run方法的逻辑了,方法执行伊始,首先遍历执行所有通过SpringFactoriesLoader可以查找到并加载的SpringApplicationRunListener。调用它们的started()方法,告诉这些SpringApplicationRunListener,“嘿,SpringBoot应用要开始执行咯!”。
  3. 创建并配置当前Spring Boot应用将要使用的Environment(包括配置要使用的PropertySource以及Profile)。
  4. 遍历调用所有SpringApplicationRunListenerenvironmentPrepared()的方法,告诉他们:“当前SpringBoot应用使用的Environment准备好了咯!”。
  5. 如果SpringApplicationshowBanner属性被设置为true,则打印banner
  6. 根据用户是否明确设置了applicationContextClass类型以及初始化阶段的推断结果,决定该为当前SpringBoot应用创建什么类型的ApplicationContext并创建完成,然后根据条件决定是否添加ShutdownHook,决定是否使用自定义的BeanNameGenerator,决定是否使用自定义的ResourceLoader,当然,最重要的,将之前准备好的Environment设置给创建好的ApplicationContext使用。
  7. ApplicationContext创建好之后,SpringApplication会再次借助SpringFactoriesLoader,查找并加载classpath中所有可用的ApplicationContextInitializer,然后遍历调用这些ApplicationContextInitializer的initialize(applicationContext)方法来对已经创建好的ApplicationContext进行进一步的处理。
  8. 遍历调用所有SpringApplicationRunListenercontextPrepared()方法。
  9. 最核心的一步,将之前通过@EnableAutoConfiguration获取的所有配置以及其他形式的IoC容器配置加载到已经准备完毕的ApplicationContext。
  10. 遍历调用所有SpringApplicationRunListenercontextLoaded()方法。
  11. 调用ApplicationContextrefresh()方法,完成IoC容器可用的最后一道工序。
  12. 查找当前ApplicationContext中是否注册有CommandLineRunner,如果有,则遍历执行它们。
  13. 正常情况下,遍历执行SpringApplicationRunListenerfinished()方法、(如果整个过程出现异常,则依然调用所有SpringApplicationRunListener的finished()方法,只不过这种情况下会将异常信息一并传入处理)

Spring 源码:

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
public ConfigurableApplicationContext run(String... args) {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
ConfigurableApplicationContext context = null;
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
configureHeadlessProperty();
// SpringFactoriesLoader => META-INF/spring.factories
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting();
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(
args);
// 环境配置 None(非WEB), Servlet, Reactive(响应式)
ConfigurableEnvironment environment = prepareEnvironment(listeners,
applicationArguments);
configureIgnoreBeanInfo(environment);
// Banner
Banner printedBanner = printBanner(environment);
context = createApplicationContext();
exceptionReporters = getSpringFactoriesInstances(
SpringBootExceptionReporter.class,
new Class[] { ConfigurableApplicationContext.class }, context);
prepareContext(context, environment, listeners, applicationArguments,
printedBanner);
// IOC容器的最后一步
refreshContext(context);
afterRefresh(context, applicationArguments);
stopWatch.stop();
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass)
.logStarted(getApplicationLog(), stopWatch);
}
listeners.started(context);
// 查找 CommandLineRunner, ApplicationRunner, ...
callRunners(context, applicationArguments);
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, listeners);
throw new IllegalStateException(ex);
}

try {
listeners.running(context);
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, null);
throw new IllegalStateException(ex);
}
return context;
}

个人感觉@EnableAutoConfiguration这个Annotation最为重要,Spring框架有提供的各种名字为@Enable开头的Annotation定义,比如@EnableScheduling、@EnableCaching、@EnableMBeanExport等,@EnableAutoConfiguration的理念和做事方式其实一脉相承,简单概括一下就是,借助@Import的支持…

阅读全文 »

Java规范对分布式事务定义了标准的规范Java事务API和Java事务服务,分别是JTA和JTS一个分布式事务必须包括一个事务管理器和多个资源管理器。

资源管理器是任意类型的持久化数据存储;
而事务管理器则是承担着所有事务参与单元者的相互通讯的责任。

JTA的规范制定了分布式事务的实现的整套流程框架,定义了各个接口且只有接口,而实现分别交给事务管理器的实现方和资源管理器的实现方

阅读全文 »

这次来介绍下Spring Boot中对单元测试的整合使用,本篇会通过以下3点来介绍,基本满足日常需求:

  • Dao层单元测试
  • Service层单元测试
  • Controller层单元测试
    在单元测试中要尽量使用断言,本文所有的测试类都符合几个原则:
  • 测试类卸载src/test/java目录下
  • 测试类的包结构与被测试类的包结构相同
  • 测试类的命名都是被测试类类名后缀加上Test,例如,UserDaoImpl与UserDaoImplTest相对应
  • 测试类的方法与被测试类的方法命名相同
阅读全文 »

linux安装Java的脚本

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
#!/bin/bash

set -ex

# UPDATE THESE URLs
export JDK_URL=https://download.oracle.com/otn-pub/java/jdk/8u191-b12/2787e4a523244c269598db4e85c51e0c/jdk-8u191-linux-x64.tar.gz
export UNLIMITED_STRENGTH_URL=http://download.oracle.com/otn-pub/java/jce/8/jce_policy-8.zip

# Download Oracle Java 8 accepting the license
wget --no-cookies --header "Cookie: oraclelicense=accept-securebackup-cookie" \
${JDK_URL}
# Extract the archive
tar -xzvf jdk-*.tar.gz
# clean up the tar
rm -fr jdk-*.tar.gz
# mk the jvm dir
sudo mkdir -p /usr/lib/jvm
# move the server jre
sudo mv jdk1.8* /usr/lib/jvm/oracle_jdk8

# install unlimited strength policy
wget --no-cookies --header "Cookie: oraclelicense=accept-securebackup-cookie" \
${UNLIMITED_STRENGTH_URL}
unzip jce_policy-8.zip
mv UnlimitedJCEPolicyJDK8/local_policy.jar /usr/lib/jvm/oracle_jdk8/jre/lib/security/
mv UnlimitedJCEPolicyJDK8/US_export_policy.jar /usr/lib/jvm/oracle_jdk8/jre/lib/security/

sudo update-alternatives --install /usr/bin/java java /usr/lib/jvm/oracle_jdk8/jre/bin/java 2000
sudo update-alternatives --install /usr/bin/javac javac /usr/lib/jvm/oracle_jdk8/bin/javac 2000

sudo echo "export J2SDKDIR=/usr/lib/jvm/oracle_jdk8
export J2REDIR=/usr/lib/jvm/oracle_jdk8/jre
export PATH=$PATH:/usr/lib/jvm/oracle_jdk8/bin:/usr/lib/jvm/oracle_jdk8/db/bin:/usr/lib/jvm/oracle_jdk8/jre/bin
export JAVA_HOME=/usr/lib/jvm/oracle_jdk8
export DERBY_HOME=/usr/lib/jvm/oracle_jdk8/db" | sudo tee -a /etc/profile.d/oraclejdk.sh

chmod +x 文件名.sh

脚本来自 https://stackoverflow.com/questions/36478741/installing-oracle-jdk-on-windows-subsystem-for-linux

要使用generator插件自动生成相关文件,需要引入mybatis-generator-core这个包,在 <dependencys> 中加入

阅读全文 »

删除右键管理员权限.reg

1
2
3
Windows Registry Editor Version 5.00
[-HKEY_CLASSES_ROOT\*\shell\runas]
[-HKEY_CLASSES_ROOT\Directory\shell\runas]

文件名:获取管理员的所有权限–带图标.reg

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
Windows Registry Editor Version 5.00
[HKEY_CLASSES_ROOT\*\shell\runas]
[HKEY_CLASSES_ROOT\*\shell\runas]
@="获取超级管理员的所有权限"
"Icon"="C:\\Windows\\System32\\imageres.dll,-78"
"NoWorkingDirectory"=""
[HKEY_CLASSES_ROOT\*\shell\runas\command]
@="cmd.exe /c takeown /f \"%1\" && icacls \"%1\" /grant administrators:F"
"IsolatedCommand"="cmd.exe /c takeown /f \"%1\" && icacls \"%1\" /grant administrators:F"
[HKEY_CLASSES_ROOT\Directory\shell\runas]
[HKEY_CLASSES_ROOT\Directory\shell\runas]
@="获取超级管理员的所有权限"
"Icon"="C:\\Windows\\System32\\imageres.dll,-78"
"NoWorkingDirectory"=""
[HKEY_CLASSES_ROOT\Directory\shell\runas\command]
@="cmd.exe /c takeown /f \"%1\" /r /d y && icacls \"%1\" /grant administrators:F /t"
"IsolatedCommand"="cmd.exe /c takeown /f \"%1\" /r /d y && icacls \"%1\" /grant administrators:F /t"
[HKEY_CLASSES_ROOT\dllfile\shell]
[HKEY_CLASSES_ROOT\dllfile\shell\runas]
@="获取超级管理员的所有权限"
"HasLUAShield"=""
"NoWorkingDirectory"=""
[HKEY_CLASSES_ROOT\dllfile\shell\runas\command]
@="cmd.exe /c takeown /f \"%1\" && icacls \"%1\" /grant administrators:F"
"IsolatedCommand"="cmd.exe /c takeown /f \"%1\" && icacls \"%1\" /grant administrators:F"
[HKEY_CLASSES_ROOT\Drive\shell\runas]
[HKEY_CLASSES_ROOT\Drive\shell\runas]
@="获取超级管理员的所有权限"
"Icon"="C:\\Windows\\System32\\imageres.dll,-78"
"NoWorkingDirectory"=""
[HKEY_CLASSES_ROOT\Drive\shell\runas\command]
@="cmd.exe /c takeown /f \"%1\" /r /d y && icacls \"%1\" /grant administrators:F /t"
"IsolatedCommand"="cmd.exe /c takeown /f \"%1\" /r /d y && icacls \"%1\" /grant administrators:F /t"