听说可以十分钟掌握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<>("任务三完成");
    }
}



相关文章
|
1月前
|
人工智能 自然语言处理 Java
Spring 集成 DeepSeek 的 3大方法(史上最全)
DeepSeek 的 API 接口和 OpenAI 是兼容的。我们可以自定义 http client,按照 OpenAI 的rest 接口格式,去访问 DeepSeek。自定义 Client 集成DeepSeek ,可以通过以下步骤实现。步骤 1:准备工作访问 DeepSeek 的开发者平台,注册并获取 API 密钥。DeepSeek 提供了与 OpenAI 兼容的 API 端点(例如),确保你已获取正确的 API 地址。
Spring 集成 DeepSeek 的 3大方法(史上最全)
|
2月前
|
Java 调度 Spring
Spring之定时任务基本使用篇
本文介绍了在Spring Boot项目中使用定时任务的基本方法。主要通过`@Scheduled`注解实现,需添加`@EnableScheduling`开启定时任务功能。文中详细解析了Cron表达式的语法及常见实例,如每秒、每天特定时间执行等。此外,还探讨了多个定时任务的执行方式(并行或串行)及其潜在问题,并留待后续深入讨论。
120 64
|
1月前
|
Java 关系型数据库 MySQL
SpringBoot 通过集成 Flink CDC 来实时追踪 MySql 数据变动
通过详细的步骤和示例代码,您可以在 SpringBoot 项目中成功集成 Flink CDC,并实时追踪 MySQL 数据库的变动。
247 43
|
23天前
|
Cloud Native Java Nacos
springcloud/springboot集成NACOS 做注册和配置中心以及nacos源码分析
通过本文,我们详细介绍了如何在 Spring Cloud 和 Spring Boot 中集成 Nacos 进行服务注册和配置管理,并对 Nacos 的源码进行了初步分析。Nacos 作为一个强大的服务注册和配置管理平台,为微服务架构提供
269 14
|
1月前
|
人工智能 Java API
支持 40+ 插件,Spring AI Alibaba 简化智能体私有数据集成
通过使用社区官方提供的超过 20 种 RAG 数据源和 20 种 Tool Calling 接口,开发者可以轻松接入多种外部数据源(如 GitHub、飞书、云 OSS 等)以及调用各种工具(如天气预报、地图导航、翻译服务等)。这些默认实现大大简化了智能体的开发过程,使得开发者无需从零开始,便可以快速构建功能强大的智能体系统。通过这种方式,智能体不仅能够高效处理复杂任务,还能适应各种应用场景,提供更加智能、精准的服务。
391 11
|
23天前
|
消息中间件 XML 前端开发
springBoot集成websocket实时消息推送
本文介绍了如何在Spring Boot项目中集成WebSocket实现实时消息推送。首先,通过引入`spring-boot-starter-websocket`依赖,配置`WebSocketConfig`类来启用WebSocket支持。接着,创建`WebSocketTest`服务器类,处理连接、消息收发及错误等事件,并使用`ConcurrentHashMap`管理用户连接。最后,前端通过JavaScript建立WebSocket连接,监听消息并进行相应处理。此方案适用于需要实时通信的应用场景,如聊天室、通知系统等。
|
1月前
|
监控 前端开发 Java
SpringBoot集成Tomcat、DispatcherServlet
通过这些配置,您可以充分利用 Spring Boot 内置的功能,快速构建和优化您的 Web 应用。
74 21
|
2月前
|
人工智能 安全 Dubbo
Spring AI 智能体通过 MCP 集成本地文件数据
MCP 作为一款开放协议,直接规范了应用程序如何向 LLM 提供上下文。MCP 就像是面向 AI 应用程序的 USB-C 端口,正如 USB-C 提供了一种将设备连接到各种外围设备和配件的标准化方式一样,MCP 提供了一个将 AI 模型连接到不同数据源和工具的标准化方法。
1015 13
|
2月前
|
监控 Java Nacos
使用Spring Boot集成Nacos
通过上述步骤,Spring Boot应用可以成功集成Nacos,利用Nacos的服务发现和配置管理功能来提升微服务架构的灵活性和可维护性。通过这种集成,开发者可以更高效地管理和部署微服务。
360 17
|
2月前
|
XML JavaScript Java
SpringBoot集成Shiro权限+Jwt认证
本文主要描述如何快速基于SpringBoot 2.5.X版本集成Shiro+JWT框架,让大家快速实现无状态登陆和接口权限认证主体框架,具体业务细节未实现,大家按照实际项目补充。
144 11