听说可以十分钟掌握Spring Boot 集成定时任务、异步调用?

简介: 在项目开发中,经常需要定时任务来帮助我们来做一些内容,比如定时发送短信/站内信息、数据汇总统计、业务监控等,所以就要用到我们的定时任务,在Spring Boot中编写定时任务是非常简单的事,下面通过实例介绍如何在Spring Boot中创建定时任务

1. 定时任务


在项目开发中,经常需要定时任务来帮助我们来做一些内容,比如定时发送短信/站内信息、数据汇总统计、业务监控等,所以就要用到我们的定时任务,在Spring Boot中编写定时任务是非常简单的事,下面通过实例介绍如何在Spring Boot中创建定时任务


1.1 @Scheduled-fixedRate方式


1.1.1  pom配置


只需要引入 Spring Boot Starter jar包即可,Spring Boot Starter 包中已经内置了定时的方法


<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter</artifactId>
</dependency>
复制代码


1.1.2 加入注解


在Spring Boot的主类中加入**@EnableScheduling** 注解,启用定时任务的配置


package com;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableScheduling;
@SpringBootApplication
@EnableScheduling
public class ScheduleTaskApplication {
    public static void main(String[] args) {
        SpringApplication.run(ScheduleTaskApplication.class, args);
    }
}
复制代码


1.1.3 创建测试类


package com.task;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import java.text.SimpleDateFormat;
import java.util.Date;
//定时任务
@Component
public class SchedulerTask {
    private static final SimpleDateFormat f=new SimpleDateFormat("HH:mm:ss");
    @Scheduled(fixedRate = 5000)//5秒执行一次
    public void processFixedRate(){
        System.out.println("processFixedRate方式开启定时任务:现在的时间是"+f.format(new Date()));
    }
}
复制代码


1.1.4 参数说明


在上面的入门例子中,使用了@Scheduled(fixedRate = 5000) 注解来定义每过5秒执行的任务,对于@Scheduled 的使用可以总结 如下几种方式:


  1. @Scheduled(fixedRate = 5000) :上一次开始执行时间点之后5秒再执行
  2. @Scheduled(fixedDelay = 5000) :上一次执行完毕时间点之后5秒再执行
  3. @Scheduled(initialDelay=1000, fixedRate=5000) :第一次延迟1秒后执行,之后按fixedRate的规则每5秒执行一次


1.1.5 运行测试


1.JPG


1.2 @Scheduled-cron方式


还可以用另一种方式实现定时任务,只需修改测试类即可


1.2.1 修改测试类


package com.task;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import java.text.SimpleDateFormat;
import java.util.Date;
//定时任务
@Component
public class SchedulerTask {
    private static final SimpleDateFormat f=new SimpleDateFormat("HH:mm:ss");
    @Scheduled(cron = "*/5 * * * * *")
    public void processFixedRate(){
        System.out.println("processFixedRate方式开启定时任务:现在的时间是"+f.format(new Date()));
    }
}
复制代码


1.2.2 测试


2.JPG


1.2.3 参数说明


cron 一共有七位,最后一位是年,Spring Boot 定时方案中只需要设置六位即可


  1. 第一位,表示秒,取值 0 ~ 59;
  2. 第二位,表示分,取值 0 ~ 59;
  3. 第三位,表示小时,取值 0 ~ 23;
  4. 第四位,日期天/日,取值 1 ~31;
  5. 第五位,日期月份,取值 1~12;
  6. 第六位,星期,取值 1 ~ 7,星期一,星期二...,注,1 表示星期 天,2 表示星期一;
  7. 第七位,年份,可以留空,取值 1970 ~ 2099


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


  1. (*)星号,可以理解为每的意思,每秒、每分、每天、每月、每年..(?)问号,问号只能出现在日期和星期这两个位置,表示这个位置的值不确定(-)减号,表达一个范围,如在小时字段中使用“10 ~ 12”,则表示从 10 到 12 点,即 10、11、12
  2. (,)逗号,表达一个列表值,如在星期字段中使用“1、2、4”,则表示星期一、星期二、星期四
  3. (/)斜杠,如 x/y,x 是开始值,y 是步⻓长,比如在第一位(秒),0/15 就是从 0 秒开始,每隔 15 秒执 行一次。


下面列举几个常用的例子: 0 0 1 * * ? :每天凌晨1 点执行; 0 5 1 * * ?:每天 凌晨1 点 5 分执行;


2. 异步调用


2.1 同步调用


同步调用指程序按照定义顺序依次执行,每一行程序都必须等待上一行程序执行完成之后才能执行


2.1.1 定义一个Task类


创建三个处理函数分别模拟三个执行任务的操作,操作消耗时间随机取(10秒内)


package com.task;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
import java.util.Random;
//同步调用
@Component
public class AsyncTask {
    public static Random random = new Random();
    public void testTask1() throws Exception{
        System.out.println("开启任务一");
        long starttime = System.currentTimeMillis();
        Thread.sleep(random.nextInt(1000));
        long endtime = System.currentTimeMillis();
        System.out.println("完成任务一消耗的时间"+(endtime-starttime)+"毫秒");
    }
    public void testTask2() throws Exception{
        System.out.println("开启任务二");
        long starttime = System.currentTimeMillis();
        Thread.sleep(random.nextInt(1000));
        long endtime = System.currentTimeMillis();
        System.out.println("完成任务二消耗的时间"+(endtime-starttime)+"毫秒");
    }
    public void testTask3() throws Exception{
        System.out.println("开启任务三");
        long starttime = System.currentTimeMillis();
        Thread.sleep(random.nextInt(1000));
        long endtime = System.currentTimeMillis();
        System.out.println("完成任务三消耗的时间"+(endtime-starttime)+"毫秒");
    }
}
复制代码


2.1.2 创建测试类


package com;
import com.task.AsyncTask;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
class ScheduleTaskApplicationTests {
    @Test
    void contextLoads() {
    }
    @Autowired
    private AsyncTask asyncTask;
    @Test
    public void testTask() throws Exception{
        asyncTask.testTask1();
        asyncTask.testTask2();
        asyncTask.testTask3();
    }
}
复制代码


2.1.3 测试


3.JPG


任务一、任务二、任务三顺序的执行完了,换言之testTask1testTask2testTask3三个函数顺序的执行完成。


2.2 异步调用


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


在Spring Boot中,我们只需要通过使用@Async 注解就能简单的将原来的同步函数变为异步函数


2.2.1 修改Task类


package com.task;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
import java.util.Random;
//同步调用
@Component
public class AsyncTask {
    public static Random random = new Random();
    @Async
    public void testTask1() throws Exception{
        System.out.println("开启任务一");
        long starttime = System.currentTimeMillis();
        Thread.sleep(random.nextInt(1000));
        long endtime = System.currentTimeMillis();
        System.out.println("完成任务一消耗的时间"+(endtime-starttime)+"毫秒");
    }
    @Async
    public void testTask2() throws Exception{
        System.out.println("开启任务二");
        long starttime = System.currentTimeMillis();
        Thread.sleep(random.nextInt(1000));
        long endtime = System.currentTimeMillis();
        System.out.println("完成任务二消耗的时间"+(endtime-starttime)+"毫秒");
    }
    @Async
    public void testTask3() throws Exception{
        System.out.println("开启任务三");
        long starttime = System.currentTimeMillis();
        Thread.sleep(random.nextInt(1000));
        long endtime = System.currentTimeMillis();
        System.out.println("完成任务三消耗的时间"+(endtime-starttime)+"毫秒");
    }
}
复制代码


2.2.2 修改SpringbootAsyncApplication


为了让@Async注解能够生效,还需要在Spring Boot的主程序中配置@EnableAsync


package com;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.annotation.EnableScheduling;
@SpringBootApplication
@EnableAsync
public class ScheduleTaskApplication {
    public static void main(String[] args) {
        SpringApplication.run(ScheduleTaskApplication.class, args);
    }
}
复制代码


此时可以反复执行单元测试,你可能会遇到各种不同的结果:


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


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


2.3 异步调用结果返回


为了让testTask1testTask2testTask3 能正常结束,假设我们需要统计一下三个任务并发执行共耗时多少,这就需要等到上述三个函数都完成调动之后记录时间,并计算结果,我们如何判断上述三个异步调用是否已经执行完成呢?我们需要使用Future 来返回异步调用的结果


2.3.1  改造AsyncTask


package com.task;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.AsyncResult;
import org.springframework.stereotype.Component;
import java.util.Random;
import java.util.concurrent.Future;
//同步调用
@Component
public class AsyncTask {
    public static Random random = new Random();
    @Async
    public Future<String> testTask1() throws Exception{
        System.out.println("开启任务一");
        long starttime = System.currentTimeMillis();
        Thread.sleep(random.nextInt(1000));
        long endtime = System.currentTimeMillis();
        System.out.println("完成任务一消耗的时间"+(endtime-starttime)+"毫秒");
        return new AsyncResult<>("任务一完成");
    }
    @Async
    public Future<String> testTask2() throws Exception{
        System.out.println("开启任务二");
        long starttime = System.currentTimeMillis();
        Thread.sleep(random.nextInt(1000));
        long endtime = System.currentTimeMillis();
        System.out.println("完成任务二消耗的时间"+(endtime-starttime)+"毫秒");
        return new AsyncResult<>("任务二完成");
    }
    @Async
    public Future<String> testTask3() throws Exception{
        System.out.println("开启任务三");
        long starttime = System.currentTimeMillis();
        Thread.sleep(random.nextInt(1000));
        long endtime = System.currentTimeMillis();
        System.out.println("完成任务三消耗的时间"+(endtime-starttime)+"毫秒");
        return new AsyncResult<>("任务三完成");
    }
}
复制代码


2.3.2 改造测试类


package com;
import com.task.AsyncTask;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.concurrent.Future;
@SpringBootTest
class ScheduleTaskApplicationTests {
    @Test
    void contextLoads() {
    }
    @Autowired
    private AsyncTask asyncTask;
    @Test
    public void testTask() throws Exception{
//        asyncTask.testTask1();
//        asyncTask.testTask2();
//        asyncTask.testTask3();
        Future<String> taskOne = asyncTask.testTask1();
        Future<String> taskTwo = asyncTask.testTask2();
        Future<String> taskThree = asyncTask.testTask3();
        while (true){
            if (taskOne.isDone()&&taskTwo.isDone()&&taskThree.isDone()){
                break;
            }
            Thread.sleep(10000);
        }
    }
}
复制代码


2.3.3 测试


4.JPG


2.3.4 总结


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


2.4 异步调用自定义线程池


开启异步注解 @EnableAsync 方法上加 @Async 默认实现 SimpleAsyncTaskExecutor 不是真的线程池,这个类不重用线程,每次调用 都会创建一个新的线程


2.4.1 自定义线程池


package com;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.util.concurrent.Executor;
import java.util.concurrent.ThreadPoolExecutor;
@SpringBootApplication
@EnableAsync
public class SpringbootAsyncApplication {
    public static void main(String[] args) {
        SpringApplication.run(SpringbootAsyncApplication.class, args);
    }
    @Bean("myTaskExecutor")
    public Executor myTaskExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(10);//核心线程数量,线程池创建时候初始化的线程数
        executor.setMaxPoolSize(15);//最大线程数,只有在缓冲队列满了之后才会申请超过核心线程数的线程
        executor.setQueueCapacity(200);//缓冲队列,用来缓冲执行任务的队列
        executor.setKeepAliveSeconds(60);//当超过了核心线程数之外的线程在空闲时间到达之后会被销毁
        executor.setThreadNamePrefix("myTask-");//设置好了之后可以方便我们定位处理任务所在的线程池
        executor.setWaitForTasksToCompleteOnShutdown(true);//用来设置线程池关闭的时候等待所有任务都完成再
继续销毁其他的Bean
        executor.setAwaitTerminationSeconds(60);//该方法用来设置线程池中任务的等待时间,如果超过这个时候还没
有销毁就强制销毁,以确保应用最后能够被关闭,而不是阻塞住。
        //线程池对拒绝任务的处理策略:这里采用了CallerRunsPolicy策略,当线程池没有处理能力的时候,该策略会直接在 
execute 方法的调用线程中运行被拒绝的任务;如果执行程序已关闭,则会丢弃该任务
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        return executor;
    }
}
复制代码


2.4.2 改造AsyncTask


@Async后面加上自定义线程池名字即可


package com.task;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.AsyncResult;
import org.springframework.stereotype.Component;
import java.util.Random;
import java.util.concurrent.Future;
//同步调用
@Component
public class AsyncTask {
    public static Random random = new Random();
    @Async("myTaskExecutor")
    public Future<String> testTask1() throws Exception{
        System.out.println("开启任务一");
        long starttime = System.currentTimeMillis();
        Thread.sleep(random.nextInt(1000));
        long endtime = System.currentTimeMillis();
        System.out.println("完成任务一消耗的时间"+(endtime-starttime)+"毫秒");
        return new AsyncResult<>("任务一完成");
    }
    @Async("myTaskExecutor")
    public Future<String> testTask2() throws Exception{
        System.out.println("开启任务二");
        long starttime = System.currentTimeMillis();
        Thread.sleep(random.nextInt(1000));
        long endtime = System.currentTimeMillis();
        System.out.println("完成任务二消耗的时间"+(endtime-starttime)+"毫秒");
        return new AsyncResult<>("任务二完成");
    }
    @Async("myTaskExecutor")
    public Future<String> testTask3() throws Exception{
        System.out.println("开启任务三");
        long starttime = System.currentTimeMillis();
        Thread.sleep(random.nextInt(1000));
        long endtime = System.currentTimeMillis();
        System.out.println("完成任务三消耗的时间"+(endtime-starttime)+"毫秒");
        return new AsyncResult<>("任务三完成");
    }
}



相关文章
|
5天前
|
监控 Java Nacos
使用Spring Boot集成Nacos
通过上述步骤,Spring Boot应用可以成功集成Nacos,利用Nacos的服务发现和配置管理功能来提升微服务架构的灵活性和可维护性。通过这种集成,开发者可以更高效地管理和部署微服务。
68 17
|
5天前
|
XML JavaScript Java
SpringBoot集成Shiro权限+Jwt认证
本文主要描述如何快速基于SpringBoot 2.5.X版本集成Shiro+JWT框架,让大家快速实现无状态登陆和接口权限认证主体框架,具体业务细节未实现,大家按照实际项目补充。
38 11
|
7天前
|
缓存 安全 Java
Spring Boot 3 集成 Spring Security + JWT
本文详细介绍了如何使用Spring Boot 3和Spring Security集成JWT,实现前后端分离的安全认证概述了从入门到引入数据库,再到使用JWT的完整流程。列举了项目中用到的关键依赖,如MyBatis-Plus、Hutool等。简要提及了系统配置表、部门表、字典表等表结构。使用Hutool-jwt工具类进行JWT校验。配置忽略路径、禁用CSRF、添加JWT校验过滤器等。实现登录接口,返回token等信息。
133 12
|
6天前
|
人工智能 安全 Dubbo
Spring AI 智能体通过 MCP 集成本地文件数据
MCP 作为一款开放协议,直接规范了应用程序如何向 LLM 提供上下文。MCP 就像是面向 AI 应用程序的 USB-C 端口,正如 USB-C 提供了一种将设备连接到各种外围设备和配件的标准化方式一样,MCP 提供了一个将 AI 模型连接到不同数据源和工具的标准化方法。
|
13天前
|
存储 安全 Java
Spring Boot 3 集成Spring AOP实现系统日志记录
本文介绍了如何在Spring Boot 3中集成Spring AOP实现系统日志记录功能。通过定义`SysLog`注解和配置相应的AOP切面,可以在方法执行前后自动记录日志信息,包括操作的开始时间、结束时间、请求参数、返回结果、异常信息等,并将这些信息保存到数据库中。此外,还使用了`ThreadLocal`变量来存储每个线程独立的日志数据,确保线程安全。文中还展示了项目实战中的部分代码片段,以及基于Spring Boot 3 + Vue 3构建的快速开发框架的简介与内置功能列表。此框架结合了当前主流技术栈,提供了用户管理、权限控制、接口文档自动生成等多项实用特性。
56 8
|
1月前
|
XML Java API
Spring Boot集成MinIO
本文介绍了如何在Spring Boot项目中集成MinIO,一个高性能的分布式对象存储服务。主要步骤包括:引入MinIO依赖、配置MinIO属性、创建MinIO配置类和服务类、使用服务类实现文件上传和下载功能,以及运行应用进行测试。通过这些步骤,可以轻松地在项目中使用MinIO的对象存储功能。
|
2月前
|
消息中间件 Java Kafka
什么是Apache Kafka?如何将其与Spring Boot集成?
什么是Apache Kafka?如何将其与Spring Boot集成?
91 5
|
2月前
|
消息中间件 Java Kafka
Spring Boot 与 Apache Kafka 集成详解:构建高效消息驱动应用
Spring Boot 与 Apache Kafka 集成详解:构建高效消息驱动应用
72 1
|
2月前
|
存储 运维 安全
Spring运维之boot项目多环境(yaml 多文件 proerties)及分组管理与开发控制
通过以上措施,可以保证Spring Boot项目的配置管理在专业水准上,并且易于维护和管理,符合搜索引擎收录标准。
64 2
|
2月前
|
XML Java 数据库连接
SpringBoot集成Flowable:打造强大的工作流管理系统
在企业级应用开发中,工作流管理是一个核心组件,它能够帮助我们定义、执行和管理业务流程。Flowable是一个开源的工作流和业务流程管理(BPM)平台,它提供了强大的工作流引擎和建模工具。结合SpringBoot,我们可以快速构建一个高效、灵活的工作流管理系统。本文将探讨如何将Flowable集成到SpringBoot应用中,并展示其强大的功能。
493 1

热门文章

最新文章