springBoot整合Quartz实现定时任务管理

springBoot整合Quartz实现定时任务管理

前言

在做项目时,我们可能需要运行一些定时执行的定时任务,在springBoot项目中可以使用@Scheduled注解来描述一个方法的定时执行,但是这样启用定时任务不太好管理。所以最近在研究如何可视化,动态的管理定时任务。使用Quartz来启动、管理定时任务,实现定时任务的创建、暂停、执行和删除操作。

在pom.xml中添加相关依赖

        <!-- quartz依赖 -->
        <dependency>
            <groupId>org.quartz-scheduler</groupId>
            <artifactId>quartz</artifactId>
            <version>2.3.0</version>
        </dependency>
        <!-- spring-context-support 依赖,提供spring对quartz的相关类 -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context-support</artifactId>
            <version>5.0.4.RELEASE</version>
        </dependency>

        <!-- swagger2 和 swagger2 依赖,这里使用了swagger-bootstrap-ui 来生成api接口文档
        ,如果不需要可以不加入这两个依赖 -->
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger2</artifactId>
            <version>2.9.2</version>
        </dependency>
        <dependency>
            <groupId>com.github.xiaoymin</groupId>
            <artifactId>swagger-bootstrap-ui</artifactId>
            <version>1.9.6</version>
        </dependency>

启动类

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);
    }

}

swagger配置

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","lzx@qq.com"))
                .version("1.0")
                .build();
    }

}

QuartzConfig配置

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;
}

获取spring容器中对象的工具类

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);
        }
    }
}

QuartzJob业务接口定义

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<QuartzJob> findAllJobs() throws SchedulerException;

    /**
     * 添加任务
     * @param quartzJob
     * @return
     */
    QuartzJob addJob(QuartzJob quartzJob);

    /**
     * 删除任务
     * @param quartzJobId
     * @return
     */
    boolean deleteJob(String quartzJobId);
}

QuartzJob业务实现

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<QuartzJob> findAllJobs() throws SchedulerException {
        Set<TriggerKey> triggerKeys = scheduler.getTriggerKeys(GroupMatcher.anyGroup());
        List<QuartzJob> 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;
    }
}

QuartzJob 接口类Controller

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<QuartzJob> 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
该地址为项目接口文档地址,使用接口文档管理页面来测试接口

测试1——添加定时任务测试

接口测试返回成功
在这里插入图片描述
后台方法开始执行
在这里插入图片描述

测试2——获取所有正在执行的任务接口测试

在这里插入图片描述

测试3——删除定时任务接口测试

在这里插入图片描述
获取执行的任务界面查询出没有执行的记录
在这里插入图片描述
后台停止执行方法
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值