重学SpringBoot系列之异步任务与定时任务

发布时间 - 2025-06-27 00:00:00    点击率:

重学SpringBoot系列之异步任务与定时任务实现Async异步任务环境准备同步调用异步调用异步回调为异步任务规划线程池Spring Boot任务线程池自定义线程池优雅地关闭线程池通过@Scheduled实现定时任务开启定时任务方法不同定时方式的解析1.fixedDelay和fixedRate,单位是毫秒,它们的区别就是:cron表达式:灵活实现定时任务解决定时任务单线程运行的问题quartz简单定时任务(内存持久化)引入对应的 maven依赖创建一个任务类Job创建 Quartz 定时配置类深入解析核心概念SimpleTrigger and CronTriggerquartz动态定时任务(数据库持久化)前言原理配置动态配置代码实现分布式任务调度框架---xxl-job实现Async异步任务环境准备

spring boot 入口类上配置 @enableasync 注解开启异步处理。 创建任务抽象类 abstracttask,并分别配置三个任务方法 dotaskone()dotasktwo()dotaskthree()

代码语言:javascript代码运行次数:0运行复制
public abstract class AbstractTask {    private static Random random = new Random();    public void doTaskOne() throws Exception {        System.out.println("开始做任务一");        long start = currentTimeMillis();        sleep(random.nextInt(10000));        long end = currentTimeMillis();        System.out.println("完成任务一,耗时:" + (end - start) + "毫秒");    }    public void doTaskTwo() throws Exception {        System.out.println("开始做任务二");        long start = currentTimeMillis();        sleep(random.nextInt(10000));        long end = currentTimeMillis();        System.out.println("完成任务二,耗时:" + (end - start) + "毫秒");    }    public void doTaskThree() throws Exception {        System.out.println("开始做任务三");        long start = currentTimeMillis();        sleep(random.nextInt(10000));        long end = currentTimeMillis();        System.out.println("完成任务三,耗时:" + (end - start) + "毫秒");    }}

同步调用

下面通过一个简单示例来直观的理解什么是同步调用:

定义 Task 类,继承 AbstractTask,三个处理函数分别模拟三个执行任务的操作,操作消耗时间随机取(10 秒内)。代码语言:javascript代码运行次数:0运行复制
@Componentpublic class SyncTask extends AbstractTask {}
单元测试 用例中,注入 SyncTask 对象,并在测试用例中执行 doTaskOne(),doTaskTwo(),doTaskThree() 三个方法。代码语言:javascript代码运行次数:0运行复制
@RunWith(SpringRunner.class)@SpringBootTestpublic class TaskTest {    @Autowired    private SyncTask task;    @Test    public void testSyncTasks() throws Exception {        task.doTaskOne();        task.doTaskTwo();        task.doTaskThree();    }}

执行单元测试,可以看到类似如下输出:

代码语言:javascript代码运行次数:0运行复制
开始做任务一完成任务一,耗时:6720毫秒开始做任务二完成任务二,耗时:6604毫秒开始做任务三完成任务三,耗时:9448毫秒

任务一、任务二、任务三顺序的执行完了,换言之 doTaskOne(),doTaskTwo(),doTaskThree() 三个方法按调用顺序的先后执行完成。


异步调用

上述的 同步调用 虽然顺利的执行完了三个任务,但是可以看到 执行时间比较长,若这三个任务本身之间 不存在依赖关系,可以 并发执行 的话,同步调用在 执行效率 方面就比较差,可以考虑通过 异步调用 的方式来 并发执行。

Application启动类上面加上@EnableAsync 创建 AsyncTask类,分别在方法上配置 @Async 注解,将原来的 同步方法 变为 异步方法。代码语言:javascript代码运行次数:0运行复制
@Componentpublic class SyncTask extends AbstractTask {    @Async    @Override    public void doTaskOne() throws Exception {        super.doTaskOne();    }    @Async    @Override    public void doTaskTwo() throws Exception {        super.doTaskTwo();    }    @Async    @Override    public void doTaskThree() throws Exception {        super.doTaskThree();    }}
单元测试 用例中,注入 AsyncTask 对象,并在测试用例中执行 doTaskOne(),doTaskTwo(),doTaskThree() 三个方法。代码语言:javascript代码运行次数:0运行复制
@Autowiredprivate AsyncTask asyncTask;@Testpublic void testAsyncTasks() throws Exception {    asyncTask.doTaskOne();    asyncTask.doTaskTwo();    asyncTask.doTaskThree();}
执行单元测试,可以看到类似如下输出:代码语言:javascript代码运行次数:0运行复制
开始做任务三开始做任务一开始做任务二

如果反复执行单元测试,可能会遇到各种不同的结果,比如:

没有任何任务相关的输出 有部分任务相关的输出 乱序的任务相关的输出

原因是目前 doTaskOne(),doTaskTwo(),doTaskThree() 这三个方法已经 异步并发执行 了。主程序在 异步调用 之后,主程序并不会理会这三个函数是否执行完成了,由于没有其他需要执行的内容,所以程序就 自动结束 了,导致了任务 不完整 或是 没有输出 相关内容的情况。


异步回调

为了让 doTaskOne(),doTaskTwo(),doTaskThree() 能正常结束,假设我们需要统计一下三个任务 并发执行 共耗时多少,这就需要等到上述三个函数都完成动用之后记录时间,并计算结果。

那么我们如何判断上述三个 异步调用 是否已经执行完成呢?我们需要使用 Future 来返回 异步调用结果

创建 AsyncCallBackTask 类,声明 doTaskOneCallback(),doTaskTwoCallback(),doTaskThreeCallback() 三个方法,对原有的三个方法进行包装。代码语言:javascript代码运行次数:0运行复制
@Componentpublic class AsyncCallBackTask extends AbstractTask {    @Async    public Future doTaskOneCallback() throws Exception {        super.doTaskOne();        return new AsyncResult<>("任务一完成");    }    @Async    public Future doTaskTwoCallback() throws Exception {        super.doTaskTwo();        return new AsyncResult<>("任务二完成");    }    @Async    public Future doTaskThreeCallback() throws Exception {        super.doTaskThree();        return new AsyncResult<>("任务三完成");    }}
单元测试 用例中,注入 AsyncCallBackTask 对象,并在测试用例中执行 doTaskOneCallback(),doTaskTwoCallback(),doTaskThreeCallback() 三个方法。循环调用 FutureisDone() 方法等待三个 并发任务 执行完成,记录最终执行时间。代码语言:javascript代码运行次数:0运行复制
@Autowiredprivate AsyncCallBackTask asyncCallBackTask;@Testpublic void testAsyncCallbackTask() throws Exception {    long start = currentTimeMillis();    Future task1 = asyncCallBackTask.doTaskOneCallback();    Future task2 = asyncCallBackTask.doTaskTwoCallback();    Future task3 = asyncCallBackTask.doTaskThreeCallback();    // 三个任务都调用完成,退出循环等待    while (!task1.isDone() || !task2.isDone() || !task3.isDone()) {        sleep(1000);    }    long end = currentTimeMillis();    System.out.println("任务全部完成,总耗时:" + (end - start) + "毫秒");}

看看都做了哪些改变:

在测试用例一开始记录开始时间; 在调用三个异步函数的时候,返回Future类型的结果对象; 在调用完三个异步函数之后,开启一个循环,根据返回的Future对象来判断三个异步函数是否都结束了。若都结束,就结束循环;若没有都结束,就等1秒后再判断。 跳出循环之后,根据结束时间 - 开始时间,计算出三个任务并发执行的总耗时。

执行一下上述的单元测试,可以看到如下结果:

代码语言:javascript代码运行次数:0运行复制
开始做任务三开始做任务一开始做任务二完成任务二,耗时:2572毫秒完成任务一,耗时:7333毫秒完成任务三,耗时:7647毫秒任务全部完成,总耗时:8013毫秒

可以看到,通过 异步调用,让任务一、任务二、任务三 并发执行,有效的 减少 了程序的 运行总时间


为异步任务规划线程池Spring Boot任务线程池

线程池的作用

防止资源占用无限的扩张 调用过程省去资源的创建和销毁所占用的时间

在上一节中,我们的一个异步任务打开了一个线程,完成后销毁。在高并发环境下,不断的分配新资源,可能导致系统资源耗尽。所以为了避免这个问题,我们为异步任务规划一个线程池。当然,如果没有配置线程池的话,springboot会自动配置一个ThreadPoolTaskExecutor 线程池到bean当中。

代码语言:javascript代码运行次数:0运行复制
# 核心线程数spring.task.execution.pool.core-size=8  # 最大线程数spring.task.execution.pool.max-size=16# 空闲线程存活时间spring.task.execution.pool.keep-alive=60s# 是否允许核心线程超时spring.task.execution.pool.allow-core-thread-timeout=true# 线程队列数量spring.task.execution.pool.queue-capacity=100# 线程关闭等待spring.task.execution.shutdown.await-termination=falsespring.task.execution.shutdown.await-termination-period=# 线程名称前缀spring.task.execution.thread-name-prefix=task-

自定义线程池

有的时候,我们希望将系统内的一类任务放到一个线程池,另一类任务放到另外一个线程池,所以使用Spring Boot自带的任务线程池就捉襟见肘了。下面介绍自定义线程池的方法。

创建一个 线程池配置类TaskConfiguration ,并配置一个 任务线程池对象taskExecutor。

代码语言:javascript代码运行次数:0运行复制
@Configurationpublic class TaskConfiguration {    @Bean("taskExecutor")    public Executor taskExecutor() {        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();        executor.setCorePoolSize(10);        executor.setMaxPoolSize(20);        executor.setQueueCapacity(200);        executor.setKeepAliveSeconds(60);        executor.setThreadNamePrefix("taskExecutor-");        executor.setRejectedExecutionHandler(new CallerRunsPolicy());        return executor;    }}

上面我们通过使用 ThreadPoolTaskExecutor 创建了一个 线程池,同时设置了以下这些参数:

Reject策略预定义有四种:

AbortPolicy,用于被拒绝任务的处理程序,它将抛出RejectedExecutionException。 CallerRunsPolicy,用于被拒绝任务的处理程序,它直接在execute方法的调用线程中运行被拒绝的任务。 DiscardOldestPolicy,用于被拒绝任务的处理程序,它放弃最旧的未处理请求,然后重试execute。 DiscardPolicy,用于被拒绝任务的处理程序,默认情况下它将丢弃被拒绝的任务。

创建 AsyncExecutorTask类,三个任务的配置和 AsyncTask 一样,不同的是 @Async 注解需要指定前面配置的 线程池的名称taskExecutor。

代码语言:javascript代码运行次数:0运行复制
@Componentpublic class AsyncExecutorTask extends AbstractTask {    @Async("taskExecutor")    public Future doTaskOneCallback() throws Exception {        super.doTaskOne();        System.out.println("任务一,当前线程:" + Thread.currentThread().getName());        return new AsyncResult<>("任务一完成");    }    @Async("taskExecutor")    public Future doTaskTwoCallback() throws Exception {        super.doTaskTwo();        System.out.println("任务二,当前线程:" + Thread.currentThread().getName());        return new AsyncResult<>("任务二完成");    }    @Async("taskExecutor")    public Future doTaskThreeCallback() throws Exception {        super.doTaskThree();        System.out.println("任务三,当前线程:" + Thread.currentThread().getName());        return new AsyncResult<>("任务三完成");    }}

单元测试 用例中,注入 AsyncExecutorTask 对象,并在测试用例中执行 doTaskOne(),doTaskTwo(),doTaskThree() 三个方法。

代码语言:javascript代码运行次数:0运行复制
@SpringBootTestpublic class AsyncExecutorTaskTest {    @Autowired    private AsyncExecutorTask task;    @Test    public void testAsyncExecutorTask() throws Exception {        task.doTaskOneCallback();        task.doTaskTwoCallback();        task.doTaskThreeCallback();        sleep(30 * 1000L);    }}

执行一下上述的 单元测试,可以看到如下结果:

代码语言:javascript代码运行次数:0运行复制
开始做任务一开始做任务三开始做任务二完成任务二,耗时:3905毫秒任务二,当前线程:taskExecutor-2完成任务一,耗时:6184毫秒任务一,当前线程:taskExecutor-1完成任务三,耗时:9737毫秒任务三,当前线程:taskExecutor-3

执行上面的单元测试,观察到 任务线程池 的 线程池名的前缀 被打印,说明 线程池 成功执行 异步任务!


优雅地关闭线程池

解决方案如下,重新设置线程池配置对象,新增线程池 setWaitForTasksToCompleteOnShutdown()setAwaitTerminationSeconds() 配置:

代码语言:javascript代码运行次数:0运行复制
@Bean("taskExecutor")public Executor taskExecutor() {    ThreadPoolTaskScheduler executor = new ThreadPoolTaskScheduler();    executor.setPoolSize(20);    executor.setThreadNamePrefix("taskExecutor-");    executor.setWaitForTasksToCompleteOnShutdown(true);    executor.setAwaitTerminationSeconds(60);    return executor;}
setWaitForTasksToCompleteOnShutdown(true): 该方法用来设置 线程池关闭 的时候 等待 所有任务都完成后,再继续 销毁 其他的 Bean,这样这些 异步任务 的 销毁 就会先于 数据库连接池对象 的销毁。 setAwaitTerminationSeconds(60): 该方法用来设置线程池中 任务的等待时间,如果超过这个时间还没有销毁就 强制销毁,以确保应用最后能够被关闭,而不是阻塞住。

通过@Scheduled实现定时任务开启定时任务方法

Scheduled定时任务是Spring boot自身提供的功能,所以不需要引入Maven依赖包

在项目入口main方法上加注解

代码语言:javascript代码运行次数:0运行复制
@EnableScheduling //开启定时任务

不同定时方式的解析1.fixedDelay和fixedRate,单位是毫秒,它们的区别就是:
fixedRate就是每隔多长时间执行一次。(开始------->X时间------>再开始)。如果间隔时间小于任务执行时间,上一次任务执行完成下一次任务就立即执行。如果间隔时间大于任务执行时间,就按照每隔X时间运行一次。 而fixedDelay是当任务执行完毕后一段时间再次执行。(开始—>结束(隔一分钟)开始----->结束)。上一次执行任务未完成,下一次任务不会开始

cron表达式:灵活

举例说明

第一位,表示秒,取值0-59 第二位,表示分,取值0-59 第三位,表示小时,取值0-23 第四位,日期天/日,取值1-31 第五位,日期月份,取值1-12 第六位,星期,取值1-7,星期一,星期二…,注:不是第1周,第二周的意思,另外:1表示星期天,2表示星期一。 第七位,年份,可以留空,取值1970-2099

cron中,还有一些特殊的符号,含义如下:

(*)星号:可以理解为每的意思,每秒,每分,每天,每月,每年… (?)问号:问号只能出现在日期和星期这两个位置。 (-)减号:表达一个范围,如在小时字段中使用“10-12”,则表示从10到12点,即10,11,12 (,)逗号:表达一个列表值,如在星期字段中使用“1,2,4”,则表示星期一,星期二,星期四 (/)斜杠:如:x/y,x是开始值,y是步长,比如在第一位(秒)0/15就是,从0秒开始,每15秒,最后就是0,15,30,45,60 另:/y,等同于0/y

cron表达式在线:http://cron.qqe2.com/


实现定时任务代码语言:javascript代码运行次数:0运行复制
@Componentpublic class ScheduledJobs {      //表示方法执行完成后5秒再开始执行    @Scheduled(fixedDelay=5000)    public void fixedDelayJob() throws InterruptedException{        System.out.println("fixedDelay 开始:" + new Date());        Thread.sleep(10 * 1000);        System.out.println("fixedDelay 结束:" + new Date());    }        //表示每隔3秒    @Scheduled(fixedRate=3000)    public void fixedRateJob()throws InterruptedException{        System.out.println("===========fixedRate 开始:" + new Date());        Thread.sleep(5 * 1000);        System.out.println("===========fixedRate 结束:" + new Date());    }    //表示每隔10秒执行一次    @Scheduled(cron="0/10 * * * * ? ")    public void cronJob(){        System.out.println("=========================== ...>>cron...." + new Date());    }}

运行结果如下:从运行结果上看,并未按照预期的时间规律运行。仔细看线程打印,竟然所有的定时任务使用的都是一个线程,所以彼此互相影响。

代码语言:javascript代码运行次数:0运行复制
===========fixedRate 结束:Tue Jul 09 19:53:04 CST 2019pool-1-thread-1fixedDelay 开始:Tue Jul 09 19:53:04 CST 2019pool-1-thread-1fixedDelay 结束:Tue Jul 09 19:53:14 CST 2019pool-1-thread-1===========fixedRate 开始:Tue Jul 09 19:53:14 CST 2019pool-1-thread-1===========fixedRate 结束:Tue Jul 09 19:53:16 CST 2019pool-1-thread-1===========fixedRate 开始:Tue Jul 09 19:53:16 CST 2019pool-1-thread-1===========fixedRate 结束:Tue Jul 09 19:53:18 CST 2019pool-1-thread-1=========================== ...>>cron....Tue Jul 09 19:53:18 CST 2019pool-1-thread-1===========fixedRate 开始:Tue Jul 09 19:53:18 CST 2019pool-1-thread-1===========fixedRate 结束:Tue Jul 09 19:53:20 CST 2019pool-1-thread-1===========fixedRate 开始:Tue Jul 09 19:53:20 CST 2019pool-1-thread-1===========fixedRate 结束:Tue Jul 09 19:53:22 CST 2019pool-1-thread-1===========fixedRate 开始:Tue Jul 09 19:53:22 CST 2019pool-1-thread-1===========fixedRate 结束:Tue Jul 09 19:53:24 CST 2019pool-1-thread-1fixedDelay 开始:Tue Jul 09 19:53:24 CST 2019pool-1-thread-1fixedDelay 结束:Tue Jul 09 19:53:34 CST 2019pool-1-thread-1=========================== ...>>cron....Tue Jul 09 19:53:34 CST 2019pool-1-thread-1===========fixedRate 开始:Tue Jul 09 19:53:34 CST 2019pool-1-thread-1===========fixedRate 结束:Tue Jul 09 19:53:36 CST 2019pool-1-thread-1===========fixedRate 开始:Tue Jul 09 19:53:36 CST 2019pool-1-thread-1===========fixedRate 结束:Tue Jul 09 19:53:38 CST 2019pool-1-thread-1===========fixedRate 开始:Tue Jul 09 19:53:38 CST 2019pool-1-thread-1===========fixedRate 结束:Tue Jul 09 19:53:40 CST 2019pool-1-thread-1===========fixedRate 开始:Tue Jul 09 19:53:40 CST 2019pool-1-thread-1===========fixedRate 结束:Tue Jul 09 19:53:42 CST 2019pool-1-thread-1===========fixedRate 开始:Tue Jul 09 19:53:42 CST 2019pool-1-thread-1===========fixedRate 结束:Tue Jul 09 19:53:44 CST 2019pool-1-thread-1===========fixedRate 开始:Tue Jul 09 19:53:44 CST 2019pool-1-thread-1===========fixedRate 结束:Tue Jul 09 19:53:46 CST 2019pool-1-thread-1===========fixedRate 开始:Tue Jul 09 19:53:46 CST 2019pool-1-thread-1===========fixedRate 结束:Tue Jul 09 19:53:48 CST 2019pool-1-thread-1fixedDelay 开始:Tue Jul 09 19:53:48 CST 2019pool-1-thread-1fixedDelay 结束:Tue Jul 09 19:53:58 CST 2019pool-1-thread-1=========================== ...>>cron....Tue Jul 09 19:53:58 CST 2019pool-1-thread-1

解决定时任务单线程运行的问题代码语言:javascript代码运行次数:0运行复制
@Configuration@EnableSchedulingpublic class ScheduleConfig implements SchedulingConfigurer {     @Override    public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {        taskRegistrar.setScheduler(scheduledTaskExecutor());    }     @Bean    public Executor scheduledTaskExecutor() {        return Executors.newScheduledThreadPool(3); //指定线程池大小    }}

再次运行上面的程序,运行时间规律就符合期望了。


quartz简单定时任务(内存持久化)

Quartz是OpenSymphony开源组织在工作计划-定时任务领域的另一个开源项目。它是完全由Java开发的,可用于执行预定任务。它类似于java.util.Timer定时器。但是与timer相比,quartz增加了许多功能。

引入对应的 maven依赖

在 springboot2.0 后官方添加了 Quartz 框架的依赖,所以只需要在 pom 文件当中引入

代码语言:javascript代码运行次数:0运行复制
                          org.springframework.boot            spring-boot-starter-quartz        

创建一个任务类Job

首先,我们需要定义一个接口来实现计时功能。我们可以将其称为任务(或任务),例如:定期发送电子邮件的任务,重新启动机器的任务以及在优惠券到期时发送SMS提醒的任务。

由于 springboot2.0 自动进行了依赖所以创建的定时任务类直接继承 QuzrtzJobBean 就可以了,新建一个定时任务类:QuartzSimpleTask

代码语言:javascript代码运行次数:0运行复制
public class QuartzSimpleTask extends QuartzJobBean {    @Override    protected void executeInternal(JobExecutionContext jobExecutionContext) throws JobExecutionException {        System.out.println("quartz简单的定时任务执行时间:"+new Date().toLocaleString());    }}

创建 Quartz 定时配置类

还需要一个可以触发任务执行的触发器。触发器触发器的基本功能是指定作业的执行时间,执行间隔和运行时间。

如何结合工作与触发?也就是说,如何分配触发器以执行指定的作业?此时,需要一个Schedule来实现此功能。

将之前创建的定时任务添加到定时调度里面

代码语言:javascript代码运行次数:0运行复制
@Configurationpublic class QuartzSimpleConfig {    //指定具体的定时任务类    @Bean    public JobDetail uploadTaskDetail() {        return JobBuilder.newJob(QuartzSimpleTask.class)                        .withIdentity("QuartzSimpleTask")                        .storeDurably().build();    }    @Bean    public Trigger uploadTaskTrigger() {        //这里设定触发执行的方式        CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule("*/5 * * * * ?");        // 返回任务触发器        return TriggerBuilder.newTrigger().forJob(uploadTaskDetail())                .withIdentity("QuartzSimpleTask")                .withSchedule(scheduleBuilder)                .build();    }}

最后运行项目查看效果,"*/5 * * * * ?"表示定时任务,每隔5秒钟执行一次。

只要项目一启动,定时任务就会开始执行


深入解析核心概念 Job:一个仅包含一个void execute(JobExecutionContext context)Abstract方法的简单接口。在实际开发中,要执行的任务是通过实现接口自定义实现的。JobExecutionContext提供调度上下文信息。代码语言:javascript代码运行次数:0运行复制
public interface Job {    void execute(JobExecutionContext context)     throws JobExecutionException;}
JobDetail:包含多个构造函数,最常用的是JobDetail(String name, String group,Class jobClass),Jobclass是实现作业接口的类,name是调度程序中任务的名称,group是调度程序中任务的组名。默认组名称为Scheduler.DEFAULT_GROUPTrigger:描述触发作业执行的时间规则的类。包含:

SimpleTrigger:一次或固定间隔时间段的触发规则。

CronTrigger:通过cron表达式描述更复杂的触发规则。

Calendar:Quartz 提供的Calendar类。触发器可以与多个Calendar关联以排除特殊日期。

Scheduler:代表独立于Quartz 的运行容器。在Scheduler 中注册了Trigger和JobDetail。它们在调度程序中具有自己的名称(名称)和组名称(Group)。触发器和JobDetail名称和组名称的组合必须唯一,但是触发器名称和组名称的组合可以与JobDetail相同。一个Job可以绑定到多个触发器,也可以不绑定。


Job还具有一个子接口:statefuljob,这是一个没有方法的标签接口,表示有状态任务。

无状态任务:它具有jobdatamap复制,因此可以并发运行; 有状态任务statefuljob:共享一个jobdatamap,并且将保存对jobdatamap的每次修改。因此,前一个有statefuljob将阻止下一个statefuljob。

SimpleTrigger and CronTrigger SimpleTrigger可以在指定的时间段内执行一个Job任务,也可以在一个时间段内多次执行。 CronTrigger功能非常强大,它基于Calendar进行作业调度,并且可以比simpletrigger更精确地指定间隔,因此crotriggersimpletrigger更常用。Crotrigger基于cron表达式。

首先,让我们了解cron表达式: 由七个子表达式组成的字符串的格式如下:

[秒] [分钟] [小时] [天] [月] [周] [年]

例如:00:00:00?\* 10,11,12 1#5 2018 ,表示2018年10月,11月和12月的第一周星期五的00:00:00。看上去不是很容易书写与记忆,但是我们可以通过网络上的在线Cron表达式生成工具,来帮助我们写表达式:在线生成cron表达式的工具:http://cron.qqe2.com/

特殊字符的含义如下:

星号( * ):可在所有字段中使用以指示相应时域中的每次时间。例如,分钟字段中的*表示“每分钟”;

问号(?):此字符仅在日期和星期字段中使用。通常将其指定为“无意义的值”,等同于点字符;

减号(-):表示范围。如果在小时字段中使用“ 10-12”,则表示10到12,即10、11、12;

逗号(,):表示列表值。如果在星期字段中使用“星期一,星期三,星期五”,则表示星期一,星期三和星期五;

斜线(/):X / Y表示相等的步长序列,其中X为起始值,y为增量步长值。如果在分钟字段中使用0/15,则表示0、15、30和45秒,而5/15在分钟字段中表示5、20、35、50,也可以使用* / y,这等效到0 / y;


quartz动态定时任务(数据库持久化)前言

在项目开发过程当中,某些定时任务,可能在运行一段时间之后,就不需要了,或者需要修改下定时任务的执行时间等等。

需要在代码当中进行修改然后重新打包发布,很麻烦。使用Quartz来实现的话不需要重新修改代码而达到要求。


原理 使用quartz提供的API完成配置任务的增删改查 将任务的配置保存在数据库中

配置

application.yml

在上面已经引入了maven依赖包,这里不再重复。直接spring属性下面加入quartz配置信息

代码语言:javascript代码运行次数:0运行复制
spring:  datasource:    url: jdbc:mysql://192.168.161.3:3306/testdb?useUnicode=true&characterEncoding=utf-8&useSSL=false    username: test    password: 4rfv$RFV    driver-class-name: com.mysql.jdbc.Driver  quartz:    job-store-type: JDBC #数据库存储quartz任务配置    jdbc:      initialize-schema: NEVER #自动初始化表结构,第一次启动的时候这里写always

动态配置代码实现

第一步 创建一个定时任务相关实体类用于保存定时任务相关信息到数据库当中

代码语言:javascript代码运行次数:0运行复制
@Datapublic class QuartzBean {    /** 任务id */    private String id;    /** 任务名称 */    private String jobName;    /** 任务执行类 */    private String jobClass;    /** 任务状态 启动还是暂停*/    private Integer status;    /** 任务运行时间表达式 */    private String cronExpression;}

第二步 创建定时任务暂停,修改,启动,单次启动工具类

代码语言:javascript代码运行次数:0运行复制
public class QuartzUtils {    /**     * 创建定时任务 定时任务创建之后默认启动状态     * @param scheduler 调度器     * @param quartzBean 定时任务信息类     */    @SuppressWarnings("unchecked")    public static void createScheduleJob(Scheduler scheduler, QuartzBean quartzBean) throws ClassNotFoundException, SchedulerException {            //获取到定时任务的执行类 必须是类的绝对路径名称            //定时任务类需要是job类的具体实现 QuartzJobBean是job的抽象类。            Class jobClass = (Class) Class.forName(quartzBean.getJobClass());            // 构建定时任务信息            JobDetail jobDetail = JobBuilder.newJob(jobClass).withIdentity(quartzBean.getJobName()).build();            // 设置定时任务执行方式            CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(quartzBean.getCronExpression());            // 构建触发器trigger            CronTrigger trigger = TriggerBuilder.newTrigger().withIdentity(quartzBean.getJobName()).withSchedule(scheduleBuilder).build();            scheduler.scheduleJob(jobDetail, trigger);    }    /**     * 根据任务名称暂停定时任务     * @param scheduler 调度器     * @param jobName 定时任务名称     */    public static void pauseScheduleJob(Scheduler scheduler, String jobName) throws SchedulerException {        JobKey jobKey = JobKey.jobKey(jobName);        scheduler.pauseJob(jobKey);    }    /**     * 根据任务名称恢复定时任务     * @param scheduler 调度器     * @param jobName 定时任务名称     */    public static void resumeScheduleJob(Scheduler scheduler, String jobName) throws SchedulerException {        JobKey jobKey = JobKey.jobKey(jobName);        scheduler.resumeJob(jobKey);    }    /**     * 根据任务名称立即运行一次定时任务     * @param scheduler 调度器     * @param jobName 定时任务名称     */    public static void runOnce(Scheduler scheduler, String jobName) throws SchedulerException {        JobKey jobKey = JobKey.jobKey(jobName);        scheduler.triggerJob(jobKey);    }    /**     * 更新定时任务     * @param scheduler 调度器     * @param quartzBean 定时任务信息类     */    public static void updateScheduleJob(Scheduler scheduler, QuartzBean quartzBean) throws SchedulerException {            //获取到对应任务的触发器            TriggerKey triggerKey = TriggerKey.triggerKey(quartzBean.getJobName());            //设置定时任务执行方式            CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(quartzBean.getCronExpression());            //重新构建任务的触发器trigger            CronTrigger trigger = (CronTrigger) scheduler.getTrigger(triggerKey);            trigger = trigger.getTriggerBuilder().withIdentity(triggerKey).withSchedule(scheduleBuilder).build();            //重置对应的job            scheduler.rescheduleJob(triggerKey, trigger);    }    /**     * 根据定时任务名称从调度器当中删除定时任务     * @param scheduler 调度器     * @param jobName 定时任务名称     */    public static void deleteScheduleJob(Scheduler scheduler, String jobName) throws SchedulerException {        JobKey jobKey = JobKey.jobKey(jobName);        scheduler.deleteJob(jobKey);    }}

控制层

代码语言:javascript代码运行次数:0运行复制
@Controller@RequestMapping("/quartz/job/")public class QuartzController {    //注入任务调度    @Resource    private Scheduler scheduler;    @PostMapping("/create")    @ResponseBody    public String createJob(@RequestBody QuartzBean quartzBean) throws SchedulerException, ClassNotFoundException {        QuartzUtils.createScheduleJob(scheduler,quartzBean);        return "已创建任务";//这里return不是生产级别代码,测试简单写一下    }    @PostMapping("/pause")    @ResponseBody    public String pauseJob(String jobName) throws SchedulerException {        QuartzUtils.pauseScheduleJob (scheduler,jobName);        return "已暂停成功";//这里return不是生产级别代码,测试简单写一下    }    @PostMapping("/run")    @ResponseBody    public String runOnce(String jobName) throws SchedulerException {        QuartzUtils.runOnce (scheduler,jobName);        return "运行任务" + jobName + "成功";//这里return不是生产级别代码,测试简单写一下    }    @PostMapping("/resume")    @ResponseBody    public String resume(String jobName) throws SchedulerException {        QuartzUtils.resumeScheduleJob(scheduler,jobName);        return "恢复定时任务成功:" + jobName;    }    @PostMapping("/update")    @ResponseBody    public String update(@RequestBody QuartzBean quartzBean) throws SchedulerException {        QuartzUtils.updateScheduleJob(scheduler,quartzBean);        return "更新定时任务调度信息成功";    }}

分布式任务调度框架—xxl-job

官网文档

全新版XXL-JOB分布式定时框架SrpingBoot-XXL-JOB


# linux  # mysql  # 工具  # qq  # ai  # 区别  # red  # Java  # JavaScript  # spring  # spring boot  # 分布式  # maven  # String  # 构造函数  # Calendar  # 字符串  # void  # 循环  # 继承  # 接口  # class  # 线程  # 并发  # 对象  # 异步  # 数据库  # http  # 完成任务  # 执行时间  # 单元测试  # 可以看到  # 被拒  # 每隔  # 并在  # 自定义  # 多个  # 创建一个 


相关栏目: 【 网站优化151355 】 【 网络推广146373 】 【 网络技术251813 】 【 AI营销90571


相关推荐: 在线制作视频的网站有哪些,电脑如何制作视频短片?  Laravel Eloquent:优雅地将关联模型字段扁平化到主模型中  如何获取上海专业网站定制建站电话?  魔方云NAT建站如何实现端口转发?  Win11怎么恢复误删照片_Win11数据恢复工具使用【推荐】  如何撰写建站申请书?关键要点有哪些?  浏览器如何快速切换搜索引擎_在地址栏使用不同搜索引擎【搜索】  制作网站软件推荐手机版,如何制作属于自己的手机网站app应用?  如何用搬瓦工VPS快速搭建个人网站?  北京网站制作公司哪家好一点,北京租房网站有哪些?  Win11搜索不到蓝牙耳机怎么办 Win11蓝牙驱动更新修复【详解】  如何在IIS管理器中快速创建并配置网站?  Laravel Livewire是什么_使用Laravel Livewire构建动态前端界面  如何在云主机快速搭建网站站点?  Laravel如何实现多级无限分类_Laravel递归模型关联与树状数据输出【方法】  湖南网站制作公司,湖南上善若水科技有限公司做什么的?  rsync同步时出现rsync: failed to set times on “xxxx”: Operation not permitted  网站制作价目表怎么做,珍爱网婚介费用多少?  如何用ChatGPT准备面试 模拟面试问答与职场话术练习教程  音乐网站服务器如何优化API响应速度?  Windows11怎样设置电源计划_Windows11电源计划调整攻略【指南】  Laravel如何实现文件上传和存储?(本地与S3配置)  如何在阿里云香港服务器快速搭建网站?  Laravel怎么多语言本地化设置_Laravel语言包翻译与Locale动态切换【手册】  Laravel如何使用Seeder填充数据_Laravel模型工厂Factory批量生成测试数据【方法】  公司网站制作价格怎么算,公司办个官网需要多少钱?  如何在万网ECS上快速搭建专属网站?  如何在阿里云虚拟机上搭建网站?步骤解析与避坑指南  Gemini怎么用新功能实时问答_Gemini实时问答使用【步骤】  javascript和jQuery中的AJAX技术详解【包含AJAX各种跨域技术】  HTML透明颜色代码怎么让下拉菜单透明_下拉菜单透明背景指南【技巧】  LinuxShell函数封装方法_脚本复用设计思路【教程】  如何在腾讯云服务器快速搭建个人网站?  网站制作怎么样才能赚钱,用自己的电脑做服务器架设网站有什么利弊,能赚钱吗?  Laravel如何实现API版本控制_Laravel版本化API设计方案  详解jQuery停止动画——stop()方法的使用  Laravel辅助函数有哪些_Laravel Helpers常用助手函数大全  如何快速生成凡客建站的专业级图册?  如何快速生成专业多端适配建站电话?  微信小程序 配置文件详细介绍  制作旅游网站html,怎样注册旅游网站?  Laravel如何集成Inertia.js与Vue/React?(安装配置)  零基础网站服务器架设实战:轻量应用与域名解析配置指南  如何使用 jQuery 正确渲染 Instagram 风格的标签列表  Laravel如何使用Gate和Policy进行授权?(权限控制)  最好的网站制作公司,网购哪个网站口碑最好,推荐几个?谢谢?  韩国网站服务器搭建指南:VPS选购、域名解析与DNS配置推荐  Laravel Pest测试框架怎么用_从PHPUnit转向Pest的Laravel测试教程  如何在阿里云虚拟主机上快速搭建个人网站?  Laravel如何自定义分页视图?(Pagination示例)