【SpringBoot】9 定时任务(Quartz)

Gitee仓库

https://gitee.com/Lin_DH/system

介绍

实现方式

java定时任务调度的实现方式:Timer,ScheduledExecutor,Spring Scheduler,JCron Tab,Quartz 等。

Quartz

Quartz是一个由Java开发的开源项目,它可以与J2EE、J2SE应用程序相结合也可以单独使用。
Quartz是一个定时调度框架,用来执行定时任务(某个时间出发执行某个动作)。

官网

https://www.quartz-scheduler.org/

核心概念

Scheduler:调度器,负责进行任务的调度与管理。Scheduler可以启动、停止、暂停、恢复任务的执行,还可以配置任务的触发条件和执行计划。
Trigger:触发器,负责定义任务的触发条件,即何时触发任务执行。一个Job可以关联一个或多个Trigger,根据时间表达式或特定的时间间隔来配置触发器。
TriggerBuilder:触发器构建器,用来创建触发器的实例。
Job:业务组件,需要被调度任务执行的具体事件。需要将Job注册到Scheduler中,调度器会调用Job接口的execute方法完成具体的业务实现。
JobDetail:任务详情,JobDetail是与Job相关联的详细信息,包括Job名称、所属的Job类、Job的身份标识等。
JobBuilder:任务构建器,用来创建JobDetail实例。
JobStore:任务存储,Quartz的持久化机制,负责将任务和调度相关的信息存储到数据库或其他存储介质中。即使应用程序重启或服务器关闭,已经配置的调度任务仍然可以保留。
Listener:监听器,Quartz提供了丰富的监听器接口,可以监控任务的状态变化、执行情况、异常事件。通过实现监听器接口,可以在任务执行前后、暂停、恢复、出错等情况下执行额外的逻辑。
ThreadPool:线程池,Scheduler使用线程池来并发执行任务,提高任务的处理效率。允许配置线程池的大小、类型、特性,以适应不同的负载情况。
在这里插入图片描述

三大核心组件

Scheduler(调度器)、Trigger(触发器)、Job(任务)

持久化方式

Quartz提供了两种持久化方式
1)RAMJobStore:在默认情况下,Quartz会将任务调度的运行信息保存在内存中,这种方法提供了最佳的性能。不足之处是缺乏数据的持久性,当程序停止或崩溃时,所有运行的信息都会丢失。
2)JobStoreTX:分布式方式一般采用此中方式,持久化到数据库中。所有的任务信息都会保存到数据库中,可以控制事务,如果应用程序或服务器关闭或重启时,已经保存到数据库中的任务信息不会丢失,并且可以恢复继续执行。

依赖

pom.xml

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-quartz</artifactId>
</dependency>

<!-- 读取yml配置文件 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-configuration-processor</artifactId>
    <optional>true</optional>
</dependency>

方式1(简单)

需求

实现固定的一个或几个Job的定时任务的效果。

效果图

在这里插入图片描述

代码实现

ScheduleConfig.java

package com.lm.system.config;

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;

/**
 * @author DUHAOLIN
 * @date 2024/8/12
 */
@Data
@Configuration
@ConfigurationProperties(prefix = "schedule")
public class ScheduleConfig {

    private String cron;

}

application.yml

schedule:
  cron:  0/5 * * * * ?  #5s执行一次

Task.java

package com.lm.system.job;

import lombok.extern.slf4j.Slf4j;

import java.util.Random;

/**
 * 具体的任务类
 * @author DUHAOLIN
 * @date 2024/8/12
 */
@Slf4j
public class Task {

    public void execute(int arg) {
        int i = new Random().nextInt(arg);
        log.info("执行具体任务,{}", i);
    }

}

TaskSchedule.java

package com.lm.system.job;

import com.lm.system.config.ScheduleConfig;
import lombok.extern.slf4j.Slf4j;
import org.quartz.JobDetail;
import org.quartz.Trigger;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.quartz.CronTriggerFactoryBean;
import org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean;
import org.springframework.scheduling.quartz.SchedulerFactoryBean;

import javax.annotation.Resource;

/**
 * @author DUHAOLIN
 * @date 2024/8/12
 */
@Slf4j
@Configuration
public class TaskSchedule {

    @Resource
    private ScheduleConfig scheduleConfig;

    @Bean
    public SchedulerFactoryBean schedulerFactoryBean(Trigger taskTrigger) {
        log.info("注册调度器");
        SchedulerFactoryBean factoryBean = new SchedulerFactoryBean();
        factoryBean.setTriggers(taskTrigger); //注册触发器
        return factoryBean;
    }

    @Bean
    public CronTriggerFactoryBean taskTrigger(JobDetail taskJob) {
        log.info("注册触发器");
        CronTriggerFactoryBean bean = new CronTriggerFactoryBean();
        bean.setJobDetail(taskJob);
        bean.setCronExpression(scheduleConfig.getCron());
        return bean;
    }

    @Bean
    public MethodInvokingJobDetailFactoryBean taskJob() {
        log.info("注册任务");
        MethodInvokingJobDetailFactoryBean bean = new MethodInvokingJobDetailFactoryBean();
        bean.setConcurrent(false); //同一Job不允许并发执行
        bean.setTargetObject(new Task()); //目标方法所在的实例对象
        bean.setTargetMethod("execute"); //需要执行的方法
        bean.setArguments(100); //需要执行的方法的入参
        return bean;
    }

}

项目目录结构图

在这里插入图片描述

方式2(按配置文件配置加载)

参考链接

Spring+Quartz 实现手动开关定时器(https://www.jianshu.com/p/2b21cbb1dcac)
从0到1搭建SpringBoot整合Quartz定时任务框架(https://blog.csdn.net/HJW_233/article/details/131427247?spm=1001.2014.3001.5501)

需求

实现按配置文件的配置启动一个或多个定时任务(WxJob,AlipayJob,OctJob),并且可以通过浏览器或访问Swagger对Job进行增删改查和暂停。

效果图

在这里插入图片描述

代码实现

application.yml

schedule:
  cron:  0/5 * * * * ?  #5s执行一次
  wxFlag: true
  alipayFlag: true
  octFlag: false

SpringUtil.java

package com.lm.system.util;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.stereotype.Component;


/**
 * @author DUHAOLIN
 * @date 2024/8/16
 */
@Component
public final class SpringUtil implements BeanFactoryPostProcessor {

    /**
     * Spring应用上下文环境
     */
    private static ConfigurableListableBeanFactory beanFactory;


    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        SpringUtil.beanFactory = beanFactory;
    }

    @SuppressWarnings("unchecked")
    public static <T> T getBean(String name) {
        return (T) beanFactory.getBean(name);
    }


}

JobType.java

package com.lm.system.common.enums;

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

/**
 * @author DUHAOLIN
 * @date 2024/8/14
 */
public enum JobType {

    WxJob("wxJob"),
    AlipayJob("alipayJob"),
    OctJob("octJob")

    ;

    JobType(String name) {
        this.name = name;
    }

    private final static List<String> jobNames = Arrays.stream(JobType.values()).map(JobType::getName).collect(Collectors.toList());

    private final String name;

    public String getName() {
        return name;
    }

    public static List<String>  getJobNames() {
        return jobNames;
    }

}

注:想要让application.yml中的 schedule 下的属性跳转到ScheduleConfig类,需要重新编译程序。(spring-boot-configuration-processor依赖的作用)

ScheduleConfig.java

package com.lm.system.config;

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;

/**
 * @author DUHAOLIN
 * @date 2024/8/12
 */
@Data
@Configuration
@ConfigurationProperties(prefix = "schedule")
public class ScheduleConfig {

    private String cron;
    private boolean wxFlag; //是否开启该类型定时任务的标志
    private boolean alipayFlag;
    private boolean octFlag;

}

WxJob.java

package com.lm.system.schedule.job;

import lombok.extern.slf4j.Slf4j;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.springframework.stereotype.Component;

/**
 * @author DUHAOLIN
 * @date 2024/8/14
 */
@Slf4j
@Component
public class WxJob implements Job {
    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException {
        log.info("wxJob.execute(context)");
    }
}

AlipayJob.java

package com.lm.system.schedule.job;

import lombok.extern.slf4j.Slf4j;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.springframework.stereotype.Component;

/**
 * @author DUHAOLIN
 * @date 2024/8/14
 */
@Slf4j
@Component
public class AlipayJob implements Job {
    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException {
        log.info("alipayJob.execute(context)");
    }
}

OctJob.java

package com.lm.system.schedule.job;

import lombok.extern.slf4j.Slf4j;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.springframework.stereotype.Component;

/**
 * @author DUHAOLIN
 * @date 2024/8/14
 */
@Slf4j
@Component
public class OctJob implements Job {
    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException {
        log.info("octJob.execute(context)");
    }
}

Constants.java

package com.lm.system.common;

/**
 * @author DUHAOLIN
 * @date 2024/8/16
 */
public class Constants {

    public static final String TASK_PARAMS = "PARAMS";
    /**
     * 默认
     */
    public static final String MISFIRE_DEFAULT = "0";
    /**
     * 立即执行
     */
    public static final String MISFIRE_IGNORE_MISFIRES = "1";
    /**
     * 执行一次
     */
    public static final String MISFIRE_FIRE_AND_PROCEED = "2";
    /**
     * 放弃执行
     */
    public static final String MISFIRE_DO_NOTHING = "3";


}

CustomJob.java

package com.lm.system.common;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
 * @author DUHAOLIN
 * @date 2024/8/14
 */
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class CustomJob {

    private String jobId;
    private String jobName;
    private String jobGroup;
    /**
     * 目标bean名
     */
    private String beanTarget;
    /**
     * 目标bean的方法名
     */
    private String beanMethodTarget;
    /**
     * 执行表达式
     */
    private String cronExpression;

    /**
     * 是否并发
     * 0:不允许并发执行
     * 1:允许并发执行
     */
    private String concurrent;

    /**
     * 计划策略
     */
    private String misfirePolicy = Constants.MISFIRE_DEFAULT;

}

AbstractJob.java

package com.lm.system.common;

import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;

import java.lang.reflect.InvocationTargetException;

/**
 * 采用了设计模式中的模板方法模式
 * @author DUHAOLIN
 * @date 2024/8/16
 */
public abstract class AbstractJob implements Job {
    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException {
        try {
            doExecute(context);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    protected abstract void doExecute(JobExecutionContext jobExecutionContext) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException;

}

JobExecuteUtil.java

package com.lm.system.util;

import com.lm.system.common.CustomJob;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import com.lm.system.common.Constants;
import org.springframework.beans.BeanUtils;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

/**
 * @author DUHAOLIN
 * @date 2024/8/16
 */
public class JobExecuteUtil {

    public static void execute(JobExecutionContext context) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        Object params = context.getMergedJobDataMap().get(Constants.TASK_PARAMS);
        CustomJob job = new CustomJob();
        BeanUtils.copyProperties(params, job); //将params的数据拷贝到job
        Job bean = SpringUtil.getBean(job.getBeanTarget());
        Method method = bean.getClass().getMethod(job.getBeanMethodTarget(), JobExecutionContext.class);
        method.invoke(bean, context);
    }

}

QuartzJobExecution.java

package com.lm.system.schedule.execution;

import com.lm.system.common.AbstractJob;
import com.lm.system.util.JobExecuteUtil;
import org.quartz.JobExecutionContext;

import java.lang.reflect.InvocationTargetException;

/**
 * @author DUHAOLIN
 * @date 2024/8/16
 */
public class QuartzJobExecution extends AbstractJob {
    @Override
    protected void doExecute(JobExecutionContext context) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        JobExecuteUtil.execute(context);
    }
}

QuartzDisallowConcurrentExecution.java

package com.lm.system.schedule.execution;

import com.lm.system.common.AbstractJob;
import com.lm.system.util.JobExecuteUtil;
import org.quartz.DisallowConcurrentExecution;
import org.quartz.JobExecutionContext;

import java.lang.reflect.InvocationTargetException;

/**
 * @author DUHAOLIN
 * @date 2024/8/16
 */
@DisallowConcurrentExecution //禁止并发执行
public class QuartzDisallowConcurrentExecution extends AbstractJob {
    @Override
    protected void doExecute(JobExecutionContext context) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        JobExecuteUtil.execute(context);
    }
}

ScheduleUtil.java

package com.lm.system.util;

import com.lm.system.common.Constants;
import com.lm.system.common.CustomJob;
import com.lm.system.schedule.execution.QuartzDisallowConcurrentExecution;
import com.lm.system.schedule.execution.QuartzJobExecution;
import lombok.extern.slf4j.Slf4j;
import org.quartz.*;

/**
 * @author DUHAOLIN
 * @date 2024/8/16
 */
@Slf4j
public class ScheduleUtil {

    public static void createJob(Scheduler scheduler, CustomJob job) throws SchedulerException {
        log.info("注册{}", job.getJobName());

        //获取任务类型
        Class<? extends Job> jobClass = getJobClass(job);

        //构建Job
        JobDetail jobDetail = JobBuilder.newJob(jobClass).withIdentity(job.getJobName(), job.getJobGroup()).build();

        //创建表达式调度器构造器
        CronScheduleBuilder builder = CronScheduleBuilder.cronSchedule(job.getCronExpression());

        //配置执行策略
        builder = handlerCronScheduleMisfirePolicy(job, builder);

        //创建触发器
        String triggerName = job.getJobName().replace("Job", "Trigger");
        CronTrigger trigger = TriggerBuilder.newTrigger()
                .withIdentity(triggerName, job.getJobGroup())
                .withSchedule(builder).build();

        //配置参数,运行时可获取
        jobDetail.getJobDataMap().put(Constants.TASK_PARAMS, job);

        //执行调度任务
        scheduler.scheduleJob(jobDetail, trigger);
    }

    private static Class<? extends Job> getJobClass(CustomJob job) {
        boolean isConcurrent = "1".equals(job.getConcurrent());
        return isConcurrent ? QuartzJobExecution.class : QuartzDisallowConcurrentExecution.class;
    }

    /**
     * 配置定时任务策略
     */
    private static CronScheduleBuilder handlerCronScheduleMisfirePolicy(CustomJob job, CronScheduleBuilder builder) {
        switch (job.getMisfirePolicy())
        {
            case Constants.MISFIRE_DEFAULT:
                return builder;
            case Constants.MISFIRE_IGNORE_MISFIRES:
                return builder.withMisfireHandlingInstructionIgnoreMisfires();
            case Constants.MISFIRE_FIRE_AND_PROCEED:
                return builder.withMisfireHandlingInstructionFireAndProceed();
            case Constants.MISFIRE_DO_NOTHING:
                return builder.withMisfireHandlingInstructionDoNothing();
            default:
                throw new RuntimeException("策略异常");
        }
    }

}

JobSchedule.java

package com.lm.system.schedule;

import com.lm.system.common.Constants;
import com.lm.system.common.CustomJob;
import com.lm.system.common.enums.JobType;
import com.lm.system.config.ScheduleConfig;
import com.lm.system.util.ScheduleUtil;
import com.lm.system.util.SpringUtil;
import lombok.extern.slf4j.Slf4j;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.quartz.SchedulerFactoryBean;

import javax.annotation.Resource;
import java.util.*;

/**
 * @author DUHAOLIN
 * @date 2024/8/16
 */
@Slf4j
@Configuration
public class JobSchedule {

    private Scheduler scheduler;
    @Resource
    private ScheduleConfig scheduleConfig;

    /**
     * 注册默认调度器
     */
    @Bean
    public SchedulerFactoryBean schedulerFactoryBean() {
        log.info("注册调度器");
        return new SchedulerFactoryBean();
    }

    public void setScheduler() {
        scheduler = SpringUtil.getBean("schedulerFactoryBean");
    }

    @Bean
    public void initByYml() throws SchedulerException {
        setScheduler();
        scheduler.clear(); //清空定时任务
        List<CustomJob> jobs = getJobs(JobType.getJobNames());
        for (CustomJob job : jobs) {
            if (scheduleConfig.isWxFlag() && job.getJobName().startsWith("wx")) {
                ScheduleUtil.createJob(scheduler, job);
            } else if (scheduleConfig.isAlipayFlag() && job.getJobName().startsWith("alipay")) {
                ScheduleUtil.createJob(scheduler, job);
            } else if (scheduleConfig.isOctFlag() && job.getJobName().startsWith("oct")) {
                ScheduleUtil.createJob(scheduler, job);
            }
        }
    }

    /**
     * 获取需要注册的Job
     */
    private List<CustomJob> getJobs(List<String> jobNames) {
        List<CustomJob> jobs = new ArrayList<>();
        for (String jobName : jobNames) {
            CustomJob job = CustomJob.builder()
                    .jobId(UUID.randomUUID().toString())
                    .jobName(jobName)
                    .jobGroup("DEFAULT")
                    .concurrent("1")
                    .cronExpression(scheduleConfig.getCron())
                    .beanTarget(jobName)
                    .beanMethodTarget("execute")
                    .misfirePolicy(Constants.MISFIRE_DEFAULT)
                    .build();
            jobs.add(job);
        }
        return jobs;
    }

}

JobException.java

package com.lm.system.exception;

/**
 * @author DUHAOLIN
 * @date 2024/8/16
 */
public class JobException extends RuntimeException {
    public JobException() {
        super();
    }

    public JobException(String message) {
        super(message);
    }

    public JobException(String message, Throwable cause) {
        super(message, cause);
    }
    
}

ScheduleController.java

package com.lm.system.controller;

import com.lm.system.common.CustomJob;
import com.lm.system.common.ResultBody;
import com.lm.system.common.enums.JobType;
import com.lm.system.config.ScheduleConfig;
import com.lm.system.exception.JobException;
import com.lm.system.util.ScheduleUtil;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.quartz.*;
import org.quartz.impl.matchers.GroupMatcher;
import org.quartz.impl.triggers.CronTriggerImpl;
import org.springframework.http.HttpStatus;
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;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;

/**
 * @author DUHAOLIN
 * @date 2024/8/13
 */
@Slf4j
@RestController
@Api(tags = "定时任务调度接口")
@RequestMapping("schedule")
public class ScheduleController {

    @Resource
    private Scheduler scheduler;
    @Resource
    private ScheduleConfig scheduleConfig;

    @GetMapping("jobType")
    @ApiOperation("查询拥有的任务类型")
    public String jobType() {
        List<String> jobNames = JobType.getJobNames();
        return ResultBody
                .build(HttpStatus.OK)
                .setData(jobNames)
                .setCount(jobNames.size())
                .getReturn();
    }

    /**
     * 查询当前启动的Job
     */
    @GetMapping("job")
    @ApiOperation("查询当前启动的Job")
    public String job() throws SchedulerException {
        log.info("查询当前启动的Job");
        return getJobByState(Trigger.TriggerState.NORMAL);
    }

    /**
     * 任务创建或更新(未存在的就创建,已存在的则更新)
     * @param cron 非必填参数,默认为配置文件中的cron
     */
    @GetMapping("insertOrUpdate")
    @ApiOperation("任务创建或更新")
    public String insertOrUpdate(String jobName, @RequestParam(required = false, value = "cron") String cron) throws SchedulerException {
        checkJobName(jobName);

        cron = cron == null ? scheduleConfig.getCron() : cron;

        CustomJob job = new CustomJob();
        job.setJobName(jobName);
        job.setCronExpression(cron);
        log.info("任务创建与更新, jobName:{}, cron: {}", jobName, cron);

        //获取触发器标识
        TriggerKey triggerKey = getTriggerKey(jobName);
        //获取触发器
        CronTrigger trigger = (CronTrigger) scheduler.getTrigger(triggerKey);
        if (null == trigger) {
            try {
                ScheduleUtil.createJob(scheduler, job);
            } catch (ObjectAlreadyExistsException e) {
                throw new ObjectAlreadyExistsException("任务已存在");
            }
        } else { //存在该任务,进行修改
            CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(cron);
            trigger = trigger.getTriggerBuilder()
                    .withIdentity(triggerKey)
                    .withSchedule(cronScheduleBuilder)
                    .build();
            //按新的trigger重新配置job执行
            scheduler.rescheduleJob(triggerKey, trigger);
        }

        return ResultBody
                .build(HttpStatus.OK)
                .getReturn();
    }

    @GetMapping("delete")
    @ApiOperation("删除任务")
    public String delete(String jobName) throws SchedulerException {
        log.info("删除任务, jobName:{}", jobName);
        JobKey jobKey = JobKey.jobKey(jobName);
        scheduler.deleteJob(jobKey);
        return ResultBody
                .build(HttpStatus.OK)
                .getReturn();
    }

    /**
     * jobName:任务名称(需要包含JobType中的类型)
     */
    private void checkJobName(String jobName) {
        boolean b = JobType.getJobNames().contains(jobName);
        if (b) return;
        throw new JobException("jobName参数有误,该Job类型不存在");
    }


    private String getJobByState(Trigger.TriggerState triggerState) throws SchedulerException {
        Set<JobKey> jobKeys = scheduler.getJobKeys(GroupMatcher.anyJobGroup());
        List<CustomJob> results = new ArrayList<>();
        for (JobKey jobKey : jobKeys) {
            String triggerName = jobKey.getName().replace("Job", "Trigger");
            TriggerKey triggerKey = getTriggerKey(triggerName);
            Trigger.TriggerState state = scheduler.getTriggerState(triggerKey);
            if (state == triggerState) {
                CronTrigger trigger = (CronTriggerImpl) scheduler.getTrigger(triggerKey);
                CustomJob job = CustomJob.builder()
                        .jobName(jobKey.getName())
                        .jobGroup(jobKey.getGroup())
                        .cronExpression(trigger.getCronExpression())
                        .build();
                results.add(job);
            }
        }
        return ResultBody
                .build(results.size() == 0 ? HttpStatus.NO_CONTENT : HttpStatus.OK)
                .setData(results)
                .setCount(results.size())
                .getReturn();
    }

    private TriggerKey getTriggerKey(String jobName) {
        return TriggerKey.triggerKey(jobName.replace("Job", "Trigger"));
    }

}

项目目录结构图

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值