SpringBoot-Quartz


本文的出现是为了能够分享个人所学的相关知识,检验自身学习成果。内容会和其他技术存在部分关联,如有任何描述错误或者说明有误的地方,还望各位大佬指出。

1. 背景

Quartz(Job Scheduler),一个比较流行的定时任务框架。搜索引擎会有详细的介绍,此处不再赘述。

2. 环境

JDK:1.8

IDE:IDEA

maven依赖:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-quartz</artifactId>
    <version>2.4.0</version>
</dependency>
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.16</version>
    <optional>true</optional>
</dependency>

3. 详解

Quartz的上手难度其实挺低的,它会给我们提供很多简单的定时任务方法。同时也是可以使用cron表达式设定任务执行时间,接下来的例子就是使用cron表达式进行任务的创建。cron的用法会在后续blog中讲解。

3.1 创建对外开放的类

import JobRunnable;
import lombok.extern.slf4j.Slf4j;
import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.text.ParseException;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

@Slf4j
@Component
public class QuartzUtil {
    private static Scheduler SCHEDULER;

    private static Map<String, Runnable> JOB_KEY_MAP = new LinkedHashMap<>(64);

    private static ThreadPoolExecutor THREAD_POOL;

    private QuartzUtil() {
    }

    @PostConstruct
    public void init() {
        // quartz scheduler
        try {
            SCHEDULER = StdSchedulerFactory.getDefaultScheduler();
            // 及时启动
            SCHEDULER.start();
            // startDelayed(1) 为延时启动,参数为int类型,单位 秒
        } catch (SchedulerException e) {
            log.error("Create Default StdScheduler error.", e);
        }

        // threadpool
        THREAD_POOL = new ThreadPoolExecutor(8, 64, 0, TimeUnit.SECONDS,
                new LinkedBlockingQueue(64), new ThreadPoolExecutor.AbortPolicy());
    }

    @PreDestroy
    public void destroy() {
        // 参数表示  当前正在执行的所有任务执行完才会结束scheduler
        if (SCHEDULER != null) {
            try {
                SCHEDULER.shutdown(true);
            } catch (SchedulerException e) {
                log.error("Shutdown Scheduler error.", e);
            }
        }

        if (THREAD_POOL != null && !THREAD_POOL.isShutdown()) {
            THREAD_POOL.shutdown();
        }
    }

    public static void addTask(String cron, Runnable runnable) {
        if (runnable != null && CronExpression.isValidExpression(cron)) {
            CronExpression cronExpression;
            try {
                cronExpression = new CronExpression(cron);
            } catch (ParseException e) {
                log.error("parse cron error,add Task error.", e);
                return;
            }
            addTask(cronExpression, runnable);
        } else {
            log.error("param invalid, addTask error.");
            return;
        }
    }

    public static void addTask(CronExpression cron, Runnable runnable) {
        if (runnable == null || cron == null || !CronExpression.isValidExpression(cron.getCronExpression())) {
            log.error("param invalid, addTask error.");
            return;
        }
        String uuid = UUID.randomUUID().toString();
        JobDetail job = JobBuilder.newJob(JobRunnable.class).withIdentity(uuid).build();

        Trigger trigger = TriggerBuilder.newTrigger()
                .startNow()
                .withIdentity(uuid)
                .withSchedule(CronScheduleBuilder.cronSchedule(cron)).build();
        try {
            SCHEDULER.scheduleJob(job, trigger);
        } catch (SchedulerException e) {
            log.error("ScheduledJob error.", e);
        }
        JOB_KEY_MAP.put(uuid, runnable);
    }


    public static ThreadPoolExecutor getThreadPool() {
        return THREAD_POOL;
    }

    public static Map<String, Runnable> getJobKeyMap() {
        return JOB_KEY_MAP;
    }

    public static Scheduler getScheduler() {
        return SCHEDULER;
    }
}

3.2 创建一个实现org.quartz.Job的类JobRunnable

import QuartzUtil;
import lombok.extern.slf4j.Slf4j;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobKey;
import org.quartz.SchedulerException;

@Slf4j
public class JobRunnable implements Job {
    @Override
    public void execute(JobExecutionContext context) {
        JobKey jobKey = context.getJobDetail().getKey();
        if (QuartzUtil.getJobKeyMap().containsKey(jobKey.getName())) {
            Runnable runnable = QuartzUtil.getJobKeyMap().get(jobKey.getName());
            QuartzUtil.getThreadPool().execute(runnable);
        } else {
            try {
                QuartzUtil.getScheduler().deleteJob(jobKey);
            } catch (SchedulerException e) {
                log.error("Job info is not contain ,Delete Job error.", e);
            }
        }
    }
}

3.3 调用,验证方式为springboot启动后添加scheduled。当然你也可以在springboot入口类的main方法里简单验证下

import QuartzUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;

@Slf4j
@Component
public class StartWare implements CommandLineRunner {
    @Override
    public void run(String... args) throws Exception {
        // cron表达式 0/2 * * * * ?  表示每分钟的0秒开始没过2秒执行一次
        QuartzUtil.addTask("0/2 * * * * ?", () -> log.info("0/2 * * * * ? scheduled exec."));
    }
}

3.4 验证日志

INFO 3816 --- [pool-1-thread-1] c.w.q.common.springawares.StartWare      : 0/2 * * * * ? scheduled exec.
INFO 3816 --- [pool-1-thread-2] c.w.q.common.springawares.StartWare      : 0/2 * * * * ? scheduled exec.
INFO 3816 --- [pool-1-thread-3] c.w.q.common.springawares.StartWare      : 0/2 * * * * ? scheduled exec.
INFO 3816 --- [pool-1-thread-4] c.w.q.common.springawares.StartWare      : 0/2 * * * * ? scheduled exec.
INFO 3816 --- [pool-1-thread-5] c.w.q.common.springawares.StartWare      : 0/2 * * * * ? scheduled exec.
INFO 3816 --- [pool-1-thread-6] c.w.q.common.springawares.StartWare      : 0/2 * * * * ? scheduled exec.

在使用的过程中其实有很多需要注意的点,比如quartz对job和trigger的name进行的一致的校验。之所以会封装一层,改job为runnable,是为了更方便调用,包括1.8之后能使用lambda后能直接构建匿名内部类,减少不必要的类的创建。



 上一篇
Nginx-LoadBalance Nginx-LoadBalance
本文的出现是为了能够分享个人所学的相关知识,检验自身学习成果。内容会和其他技术存在部分关联,如有任何描述错误或者说明有误的地方,还望各位大佬指出。 1. 了解NginxNginx,高性能的HTTP和反向代理服务器。反向代理作为它的核心
2020-06-28
下一篇 
SpringBoot-Build-MyBatis SpringBoot-Build-MyBatis
本blog的出现是为了能够分享个人所学的相关知识,检验自身学习成果。内容会和其他技术存在部分关联,如有任何描述错误或者说明有误的地方,还望指出。 一、简介SpringBoot中使用Mybatis之前,先搭建基本的SpringBoot项目
2020-05-28
  目录