在做项目时,我们可能需要运行一些定时执行的定时任务,在springBoot项目中可以使用@Scheduled注解来描述一个方法的定时执行,但是这样启用定时任务不太好管理。所以最近在研究如何可视化,动态的管理定时任务。使用Quartz来启动、管理定时任务,实现定时任务的创建、暂停、执行和删除操作。
org.quartz-scheduler
quartz
2.3.0
org.springframework
spring-context-support
5.0.4.RELEASE
io.springfox
springfox-swagger2
2.9.2
com.github.xiaoymin
swagger-bootstrap-ui
1.9.6
package com.lzx.quartzjob;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableAsync;
@SpringBootApplication
@EnableAsync
public class QuartzjobApplication {
public static void main(String[] args) {
SpringApplication.run(QuartzjobApplication.class, args);
}
}
package com.example.demo;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.Contact;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
/**
* 描述:
*
* @Auther: lzx
* @Date: 2019/9/23 09:11
*/
@Configuration
@EnableSwagger2
@Profile({"local-k8s","dev-k8s"})
public class SwaggerConfiguration {
@Bean
public Docket createRestApi(){
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo())
.select()
.apis(RequestHandlerSelectors.basePackage("com.example.demo"))
.paths(PathSelectors.any())
.build();
}
private ApiInfo apiInfo(){
return new ApiInfoBuilder()
.title("自动任务接口")
.description("自动任务接口")
.termsOfServiceUrl("http://www.kcamkj.cn/autojob/")
.contact(new Contact("lzx","http://www.kcsmkj.cn","[email protected]"))
.version("1.0")
.build();
}
}
package com.example.demo;
import org.quartz.Scheduler;
import org.quartz.spi.TriggerFiredBundle;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.quartz.AdaptableJobFactory;
import org.springframework.scheduling.quartz.SchedulerFactoryBean;
import org.springframework.stereotype.Component;
/**
* 描述: quartz
*
* @Auther: lzx
* @Date: 2019/9/24 13:16
*/
@Configuration
public class QuartzConfig {
/**
* 解决Job中注入Spring Bean 为 null 的问题
*/
@Component("quartzJobFactory")
public class QuartzJobFactory extends AdaptableJobFactory {
@Autowired
private AutowireCapableBeanFactory capableBeanFactory;
@Override
protected Object createJobInstance(TriggerFiredBundle bundle) throws Exception {
Object jobInstance = super.createJobInstance(bundle);
capableBeanFactory.autowireBean(jobInstance);
return jobInstance;
}
}
@Bean(name = "scheduler")
public Scheduler scheduler(QuartzJobFactory quartzJobFactory)throws Exception{
SchedulerFactoryBean factoryBean = new SchedulerFactoryBean();
factoryBean.setJobFactory(quartzJobFactory);
factoryBean.afterPropertiesSet();
Scheduler scheduler = factoryBean.getScheduler();
scheduler.start();
return scheduler;
}
}
package com.lzx.quartzjob.swagger.model;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Getter;
import lombok.Setter;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import java.io.Serializable;
import java.util.Date;
/**
* 自动任务管理模型
* @author lzx
* @date 2019-09-07
*/
@Getter
@Setter
@ApiModel("自动任务模型")
public class QuartzJob implements Serializable {
@ApiModelProperty(hidden = true)
public static final String JOB_KEY = "JOB_KEY";
@ApiModelProperty(value = "任务id",dataType = "Long",example = "201909250001",required = true)
@NotNull(message = "任务id不能为空")
private Long id;
/**
* 定时器名称
*/
@ApiModelProperty(value = "定时器名称",dataType = "String",example = "测试任务",required = true)
@NotBlank(message = "定时器名称不能为空")
private String jobName;
/**
* Bean名称
*/
@ApiModelProperty(value = "Bean名称",dataType = "String",example = "testTaskJob",required = true)
@NotBlank(message = "Bean名称不能为空")
private String beanName;
/**
* 方法名称
*/
@ApiModelProperty(value = "方法名称",dataType = "String",example = "run",required = true)
@NotBlank(message = "方法名称不能为空")
private String methodName;
/**
* 参数
*/
@ApiModelProperty(value = "参数",dataType = "String",required = false)
private String params;
/**
* cron表达式
*/
@ApiModelProperty(value = "cron表达式",dataType = "String",required = true,example = "0/1 * * * * ?")
@NotBlank(message = "cron表达式不能为空")
private String cronExpression;
/**
* 状态
*/
@ApiModelProperty(value = "状态",dataType = "Boolean",required = true,example = "true")
@NotNull(message = "状态不能为空")
private Boolean isPause = false;
/**
* 备注
*/
@ApiModelProperty(value = "备注",dataType = "String",required = false)
private String remark;
/**
* 创建日期
*/
@ApiModelProperty(value = "创建日期",dataType = "Timestamp",required = false)
@JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss",timezone = "GTM+8")
private Date updateTime;
@ApiModelProperty(value = "是否自动需要关闭",dataType = "boolean",required = true)
@NotNull(message = "是否自动需要关闭不能为空")
private boolean needClose = false;
}
package com.example.demo;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
/**
* @author zli
* @version 1.0
*/
@Component
public class SpringBeanUtil implements ApplicationContextAware {
private static ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext)
throws BeansException {
SpringBeanUtil.applicationContext = applicationContext;
}
/**
* 通过名称在spring容器中获取对象
*
* @param beanName bean名字
* @return bean对象
*/
public static Object getBeanFromSpringByBeanName(String beanName) {
return applicationContext.getBean(beanName);
}
}
package com.example.demo;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.springframework.util.ReflectionUtils;
import java.lang.reflect.Method;
/**
* 描述: 执行方法的函数
*
* @Auther: lzx
* @Date: 2019/9/25 13:21
*/
@Slf4j
public class QuartzRunnable implements Runnable {
private Object target;
private Method method;
private String params;
public QuartzRunnable(String beanName, String methodName, String params) throws NoSuchMethodException {
this.target = SpringBeanUtil.getBeanFromSpringByBeanName(beanName);
this.params = params;
if(StringUtils.isNotBlank(params)){
this.method = target.getClass().getDeclaredMethod(methodName,String.class);
}else{
this.method = target.getClass().getDeclaredMethod(methodName);
}
}
@Override
public void run() {
try{
ReflectionUtils.makeAccessible(method);
if(StringUtils.isNotBlank(params)){
method.invoke(target,params);
}else{
method.invoke(target);
}
}catch (Exception e){
log.error(e.getMessage(),e);
}
}
}
package com.example.demo;
import lombok.extern.slf4j.Slf4j;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.quartz.JobKey;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.quartz.QuartzJobBean;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
/**
* 描述:
*
* @Auther: lzx
* @Date: 2019/9/25 13:11
*/
@Async
@Slf4j
public class ExecutionJob extends QuartzJobBean {
private ExecutorService executorService = Executors.newSingleThreadExecutor();
@Override
protected void executeInternal(JobExecutionContext context) throws JobExecutionException {
QuartzJob quartzJob = (QuartzJob)context.getMergedJobDataMap().get(QuartzJob.JOB_KEY);
try {
log.info("任务准备执行,任务名称:{}",quartzJob.getJobName());
long startTime = System.currentTimeMillis();
QuartzRunnable task = new QuartzRunnable(quartzJob.getBeanName(), quartzJob.getMethodName(), quartzJob.getParams());
Future> future = executorService.submit(task);
future.get();
long times = System.currentTimeMillis() - startTime;
log.info("任务执行完毕,任务名称:{} 总耗时:{} 毫秒",quartzJob.getJobName(),times);
if(quartzJob.isNeedClose()){
log.info("执行后需要关闭任务");
JobKey jobKey = JobKey.jobKey(QuartzJobServiceImpl.JOB_NAME + quartzJob.getId());
context.getScheduler().pauseJob(jobKey);
context.getScheduler().deleteJob(jobKey);
log.info("{}任务关闭",quartzJob.getJobName());
}
} catch (Exception e) {
log.error(e.getMessage(),e);
}
}
}
package com.example.demo;
import org.quartz.SchedulerException;
import java.util.List;
/**
* 描述:
*
* @Auther: lzx
* @Date: 2019/9/25 10:44
*/
public interface QuartzJobService {
/**
* 查询所有任务
* @return
* @throws SchedulerException
*/
List findAllJobs() throws SchedulerException;
/**
* 添加任务
* @param quartzJob
* @return
*/
QuartzJob addJob(QuartzJob quartzJob);
/**
* 删除任务
* @param quartzJobId
* @return
*/
boolean deleteJob(String quartzJobId);
}
package com.example.demo;
import org.quartz.*;
import org.quartz.impl.matchers.GroupMatcher;
import org.quartz.impl.triggers.CronTriggerImpl;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Set;
import static org.quartz.TriggerBuilder.newTrigger;
/**
* 描述:
*
* @Auther: lzx
* @Date: 2019/9/25 10:46
*/
@Service
public class QuartzJobServiceImpl implements QuartzJobService {
@Resource(name = "scheduler")
private Scheduler scheduler;
public static final String JOB_NAME = "TASK_";
@Override
public List findAllJobs() throws SchedulerException {
Set triggerKeys = scheduler.getTriggerKeys(GroupMatcher.anyGroup());
List names = new ArrayList<>();
for (TriggerKey j : triggerKeys) {
try {
QuartzJob o = (QuartzJob) scheduler.getTrigger(j).getJobDataMap().get(QuartzJob.JOB_KEY);
names.add(o);
} catch (SchedulerException e) {
e.printStackTrace();
}
}
return names;
}
@Override
public QuartzJob addJob(QuartzJob quartzJob) {
JobDetail jobDetail = JobBuilder.newJob(ExecutionJob.class)
.withIdentity(JOB_NAME + quartzJob.getId())
.build();
Trigger cronTrigger = newTrigger()
.withIdentity(JOB_NAME + quartzJob.getId())
.startNow()
.withSchedule(CronScheduleBuilder.cronSchedule(quartzJob.getCronExpression()))
.build();
cronTrigger.getJobDataMap().put(QuartzJob.JOB_KEY,quartzJob);
((CronTriggerImpl)cronTrigger).setStartTime(new Date());
try {
quartzJob.setIsPause(false);
scheduler.scheduleJob(jobDetail,cronTrigger);
} catch (SchedulerException e) {
e.printStackTrace();
}
return quartzJob;
}
@Override
public boolean deleteJob(String quartzJobId) {
JobKey jobKey = JobKey.jobKey(JOB_NAME + quartzJobId);
try {
scheduler.pauseJob(jobKey);
scheduler.deleteJob(jobKey);
} catch (SchedulerException e) {
e.printStackTrace();
}
return true;
}
}
package com.example.demo;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.quartz.SchedulerException;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
import java.util.List;
/**
* 描述:
*
* @Auther: lzx
* @Date: 2019/9/25 10:52
*/
@RestController
@RequestMapping("/quartzJob")
@Api(tags = {"定时任务管理"})
public class QuartzJobController {
private QuartzJobService quartzJobService;
public QuartzJobController(QuartzJobService quartzJobService) {
this.quartzJobService = quartzJobService;
}
@GetMapping("/fingAllJobs")
@ApiOperation(value = "获取所有定时任务")
public List fingAllJobs() throws SchedulerException {
return quartzJobService.findAllJobs();
}
@ApiOperation(value = "添加任务")
@PostMapping("/addJob")
public QuartzJob addJob(@RequestBody @Valid QuartzJob quartzJob){
return quartzJobService.addJob(quartzJob);
}
@ApiOperation(value = "删除任务")
@DeleteMapping("/deleteJob/{quartzJobId}")
public boolean deleteJob(@PathVariable("quartzJobId") String quartzJobId){
return quartzJobService.deleteJob(quartzJobId);
}
}
package com.example.demo.task;
import lombok.extern.slf4j.Slf4j;
import org.quartz.Scheduler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
* 描述:
*
* @Auther: lzx
* @Date: 2019/9/25 15:51
*/
@Component
@Slf4j
public class TestTaskJob {
@Autowired
private Scheduler scheduler;
public void run(){
log.info("执行TestTaskJob,run。。。。");
}
public void runAndClose(){
log.info("执行任务,执行完成后关闭任务");
}
}
浏览器打开地址
http://localhost:项目端口/doc.html
该地址为项目接口文档地址,使用接口文档管理页面来测试接口