在Springboot项目中使用Quartz执行定时任务

所使用的jar包

        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-quartzartifactId>
        dependency>

使用默认单机模式。单机模式中,Job 和Trigger是存放在内存中Map,通过源码可以看出 quartz-2.3.0.sources.jar!/org/quartz/simpl/RAMJobStore.java

    protected HashMap<JobKey, JobWrapper> jobsByKey = new HashMap<JobKey, JobWrapper>(1000);

    protected HashMap<TriggerKey, TriggerWrapper> triggersByKey = new HashMap<TriggerKey, TriggerWrapper>(1000);

    protected HashMap<String, HashMap<JobKey, JobWrapper>> jobsByGroup = new HashMap<String, HashMap<JobKey, JobWrapper>>(25);

    protected HashMap<String, HashMap<TriggerKey, TriggerWrapper>> triggersByGroup = new HashMap<String, HashMap<TriggerKey, TriggerWrapper>>(25);

    protected TreeSet<TriggerWrapper> timeTriggers = new TreeSet<TriggerWrapper>(new TriggerWrapperComparator());

    protected HashMap<String, Calendar> calendarsByName = new HashMap<String, Calendar>(25);

    protected Map<JobKey, List<TriggerWrapper>> triggersByJob = new HashMap<JobKey, List<TriggerWrapper>>(1000);

    protected final Object lock = new Object();

    protected HashSet<String> pausedTriggerGroups = new HashSet<String>();

    protected HashSet<String> pausedJobGroups = new HashSet<String>();

    protected HashSet<JobKey> blockedJobs = new HashSet<JobKey>();

官网支持集群是把这些数据存放在mysql, 也有人改成使用redis存放这些数据

结合Springboot

注意此处Job要扩展QuartzJobBean, 只有这样才能使用@Autowired进来的其它service实例,否则要显式地new 一个相应service的实例

@Slf4j
@Component
@DisallowConcurrentExecution
public class MyJob extends QuartzJobBean {

    @Autowired
    ApplicationService applicationService;


    @Override
    protected void executeInternal(JobExecutionContext jobExecutionContext) throws JobExecutionException {

        log.info("触发MyJob=========");

        applicationService.doSomething();

        Trigger trigger = jobExecutionContext.getTrigger();
        log.info("触发MyJob==========the trigger time : {}, 当前时间: {}",
                trigger.getStartTime(), new Date());

    }
}

设置Schedule

public interface QuartzService {

    void startSchedule ();

    void deployMySchedule (Myparams params) throws SchedulerException, ParseException;

}

定义trigger并设置Schedule


@Slf4j
@Service
public class QuartzServiceImpl implements QuartzService {


    private final SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss", Locale.ENGLISH);

    private final String myJobName = "MyJobName";
    private final String QUARTZ_JOB_GROUP_SUFFIX = "Group";


    @Autowired
    private Scheduler scheduler;

    @Override
    public void deployMySchedule (MyParams params) throws SchedulerException, ParseException {

        if (checkMyJobExists(params)) {
            log.warn("已存在{}的任务", params.getJobName());
            updateMyJobTrigger(params);
        } else {
            log.info("不存在{}的任务, 添加任务", params.getJobName());
            arrangeNewMyJobSchedule(params);
        }
    }


    @Override
    public void startSchedule ()  {

        try {
            scheduler.start();
        } catch (SchedulerException e) {
            log.error("Quartz 启动Schedule 出现异常 ==== ", e);
        }
    }

    /**
     * {
     *     "myJobName": "my.customized.job.name",
     *     "triggerTime": "2023-09-21 23:23:23"
     * }
     * @param params
     * @throws SchedulerException
     */

    private void arrangeNewMyJobSchedule (MyParams params) throws SchedulerException, ParseException {

        String jobName = params.getMyJobName();
        String jobGroup = params.getMyJobName()+QUARTZ_JOB_GROUP_SUFFIX;
        String triggerTime = params.getTriggerTime();

        // TODO change triggerTime to Date()
        Date date = formatter.parse(triggerTime);
        log.info("{} 添加定时任务", params.getMyJobName());

        JobKey theJobKey = jobKey(jobName, jobGroup);

        JobDetail job = JobBuilder.newJob(MyJob.class)
                .usingJobData("myJobName", params.getMyJobName())
                .withIdentity(theJobKey)
                .build();

        // Simple trigger without repeating
        // withMisfireHandlingInstructionNextWithRemainingCount()
        // Does nothing, misfired execution is ignored and there is no next execution.
        // Use this instruction when you want to completely discard the misfired execution.
        // Example scenario: the trigger was suppose to start recording of a program in TV.
        // There is no point of starting recording when the trigger misfired and is already 2 hours late.
        // Discarded but job not removed
        Trigger trigger = TriggerBuilder.newTrigger()
                .withIdentity(triggerKey(jobName, jobGroup))
                .startAt(date)
                .withSchedule(SimpleScheduleBuilder.simpleSchedule()
                        .withMisfireHandlingInstructionNextWithRemainingCount())
                .build();

        // 使用触发器调度任务的执行
        scheduler.scheduleJob(job, trigger);
        log.info("{} 设置任务触发时间为: {}, 配置触发器", params.getMyJobName(), triggerTime);

        log.info("schedule: scheduleName {}, scheduleInstanceId {} ", scheduler.getSchedulerName(),
                scheduler.getSchedulerInstanceId());

    }

    private void updateMyJobTrigger (MyParams params) throws SchedulerException, ParseException {
        String jobName = params.getMyJobName();
        String jobGroup = params.getMyJobName()+"Group";
        String triggerTime = params.getTriggerTime();

        updateJobTrigger(jobName, jobGroup, triggerTime);
    }

    private boolean checkMyJobExists (MyParams params) throws SchedulerException {
        String jobName = params.getMyJobName();
        String jobGroup = params.getMyJobName()+QUARTZ_JOB_GROUP_SUFFIX;
        JobKey jobKey = new JobKey(jobName, jobGroup);
        return scheduler.checkExists(jobKey);
    }


    private void updateJobTrigger (String jobName, String jobGroup, String triggerTime) throws ParseException {

        // TODO change triggerTime to Date()
        Date date = formatter.parse(triggerTime);

        try {
            TriggerKey triggerKey = triggerKey(jobName, jobGroup);
            SimpleTrigger oldTrigger = (SimpleTrigger) scheduler.getTrigger(triggerKey);

            // Simple trigger without repeating
            // withMisfireHandlingInstructionNextWithRemainingCount()
            // 如果给的时间小于当前时间, 只重新配置触发器, 并不触发, 同时 jobdetail 也没有删除
            Trigger newTrigger = oldTrigger.getTriggerBuilder().withIdentity(triggerKey)
                    .withSchedule(simpleSchedule()
                            .withMisfireHandlingInstructionNextWithRemainingCount())
                    .startAt(date)
                    .build();
            // 重启触发器
            scheduler.rescheduleJob(oldTrigger.getKey(), newTrigger);
            log.info("Job {} 任务触发时间更新为: {}, 重新配置触发器", jobName, triggerTime);

        } catch (SchedulerException e) {
            e.printStackTrace();
        }

    }
}

在项目启动时加载schedule

@Slf4j
@Component
public class MyQuartzScheduleStart {

    @Autowired
    QuartzService quartzService;

    @PostConstruct
    public void init() {
        log.info("Quartz 调度任务开始 =====");
        quartzService.startSchedule();
    }
}

扩展阅读:线程池,多schedule, 如果Job里 Autowire了其它的 Bean,应该怎么办,官方的配置说明,官方教程,Quartz Scheduler Misfire Instructions Explained, Misfire, getting-quartz-net-to-ignore-misfires, using-disallowconcurrentexecution-in-quartz-scheduler, Quartz + Redis实现集群定时任务

例子:spring-boot-quartz-scheduler-email-scheduling-example, Quartz_Scheduler_Example_Programs_and_Sample_Code, SpringBoot——Quartz定时框架的使用详解和总结

详细解说:Quartz 是什么?一文带你入坑

你可能感兴趣的:(Java,spring,boot,后端,java,quartz)