SpringBoot使用异步调用

本文介绍了在SpringBoot中如何使用@Async注解实现异步调用,包括自定义线程池、注意事项和示例。强调了异步调用在非关键业务如日志记录中的应用,以及避免在同一个类内调用@Async方法。同时,文章提到了线程池配置和异步方法与事务管理的交互。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

SpringBoot使用异步调用

介绍

异步请求的处理。除了异步请求,一般上我们用的比较多的应该是异步调用。通常在开发过程中,会遇到一个方法是和实际业务无关的,没有紧密性的。比如记录日志信息等业务。这个时候正常就是启一个新线程去做一些业务处理,让主线程异步的执行其他业务。

使用方式:

  1. 在 Spring Framework 的 Spring Task 模块,提供了 @Async 注解,可以添加在方法上,自动实现该方法的异步调用

  2. 需要在启动类或配置类加上@EnableAsync使异步调用@Async注解生效

  3. 在需要异步执行的方法上加入此注解即可@Async(“threadPool”),threadPool为自定义线程池。(@Async默认使用SimpleAsyncTaskExecutor线程池。也可以根据Bean Name指定特定线程池)

注意事项:

  1. 在默认情况下,未设置TaskExecutor时,默认是使用SimpleAsyncTaskExecutor这个线程池,但此线程不是真正意义上的线程池,因为线程不重用,每次调用都会创建一个新的线程。可通过控制台日志输出可以看出,每次输出线程名都是递增的。所以最好我们来自定义一个线程池。

  2. 调用的异步方法,不能为同一个类的方法(包括同一个类的内部类),简单来说,因为Spring在启动扫描时会为其创建一个代理类,而同类调用时,还是调用本身的代理类的,所以和平常调用是一样的。

  3. 其他的注解如@Cache等也是一样的道理,说白了,就是Spring的代理机制造成的。所以在开发中,最好把异步服务单独抽出一个类来管理。

  4. 同步事务方法内部调用异步方法, 异步方法会新开启一个事务, 异步事务方法调用异步方法, 内部异步方法也会新开启事务。

  5. 同步事务方法调用异步方法, 异步方法报错, 同步方法不会回滚, 想同步方法回滚, 那就需要同步方法捕获异步方法的报错, 继续往外抛异常(异步方法必须带返回值)。

一些异步使用场景
文章阅读的业务逻辑 = 查询文章详情 + 更新文章阅读量后再响应客户端, 其实也无需等到阅读量更新后才响应文章详情给客户端, 用户查看文章是主要逻辑, 而文章阅读量更新是次要逻辑, 况且阅读量就算更新失败一点数据偏差也不会影响用户阅读因此这两个数据库操作之间的一致性是较弱的,这类都能用异步事件去优化。

@Async失效场景

  1. 异步方法使用static修饰
  2. 异步类没有使用@Component、@Service等注解,导致spring无法扫描到异步类
  3. 调用的异步方法,不能为同一个类的方法(包括同一个类的内部类)。PS:因为Spring在启动扫描时会为其创建一个代理类,而同类调用时,还是调用本身的代理类的,所以和平常调用是一样的
  4. 类中需要使用@Autowired或@Resource等注解自动注入,不能自己手动new对象
  5. 如果使用SpringBoot框架必须在启动类中增加@EnableAsync注解
  6. 在Async 方法上标注@Transactional是没用的。 在Async 方法调用的方法上标注@Transactional 有效。

同步与异步调用示例

  1. 异步调用,对应的是同步调用。
  2. 同步调用:指程序按照 定义顺序 依次执行,每一行程序都必须等待上一行程序执行完成之后才能执行;
  3. 异步调用:指程序在顺序执行时,不等待异步调用的语句返回结果,就执行后面的程序。

配置线程池参数

package com.xiaoge;

/**
 * TODO
 *
 * @author <a href="mailto:zhangxiao@dist.com.cn">Zhang Xiao</a>
 * @since
 */
public interface AsyncConfigConstant {

    /**
     * 最大线程
     */
    int MAX_SIZE = 6;

    /**
     * 核心线程
     */
    int CORE_SIZE = 3;

    /**
     * 保持时间(默认秒)
     */
    int KEEP_ALIVE = 3;

    /**
     * 队列容量
     */
    int QUEUE_CAPACITY = 1000;

    /**
     * 名称前缀
     */
    String THREAD_NAME_PREFIX = "async-task-thread-pool-";

}

线程bean

package com.xiaoge;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

import java.util.concurrent.Executor;
import java.util.concurrent.ThreadPoolExecutor;

@Configuration
public class ExecutorConfig {

    @Bean("taskExecutor")
    public Executor myExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        // 核心线程
        executor.setCorePoolSize(AsyncConfigConstant.CORE_SIZE);
        // 最大线程
        executor.setMaxPoolSize(AsyncConfigConstant.MAX_SIZE);
        // 队列容量
        executor.setQueueCapacity(AsyncConfigConstant.QUEUE_CAPACITY);
        // 保持时间(默认秒)
        executor.setKeepAliveSeconds(AsyncConfigConstant.KEEP_ALIVE);
        // 名称前缀
        executor.setThreadNamePrefix(AsyncConfigConstant.THREAD_NAME_PREFIX);
		/*
		AbortPolicy(中止策略):这是默认的拒绝策略。当任务被拒绝时,它会抛出一个RejectedExecutionException异常。
		CallerRunsPolicy(调用者运行策略):当任务被拒绝时,此策略会直接在 execute 方法的调用线程中运行被拒绝的任务,除非线程池已经被关闭。
		DiscardPolicy(丢弃策略):当任务被拒绝时,此策略会默默地丢弃被拒绝的任务,不会有任何处理。
		DiscardOldestPolicy(丢弃最旧的任务策略):当任务被拒绝时,此策略将丢弃工作队列中最旧的未处理任务,然后尝试重新执行任务(新的或者旧的)。
		*/
        // 拒绝策略
        executor.setRejectedExecutionHandler( new ThreadPoolExecutor.AbortPolicy());
        executor.initialize();
        return executor;
    }

}

同步service

package com.xiaoge;

import org.springframework.stereotype.Component;

import java.util.Random;


/**
 * TODO
 *
 * @author <a href="mailto:zhangxiao@dist.com.cn">Zhang Xiao</a>
 * @since
 */
@Component
public class Task {

    public static Random random =new Random();

    public void doTaskOne() throws Exception {
        System.out.println("开始做任务一");
        long start = System.currentTimeMillis();
        Thread.sleep(random.nextInt(10000));
        long end = System.currentTimeMillis();
        System.out.println("完成任务一,耗时:" + (end - start) + "毫秒");
    }

    public void doTaskTwo() throws Exception {
        System.out.println("开始做任务二");
        long start = System.currentTimeMillis();
        Thread.sleep(random.nextInt(10000));
        long end = System.currentTimeMillis();
        System.out.println("完成任务二,耗时:" + (end - start) + "毫秒");
    }

    public void doTaskThree() throws Exception {
        System.out.println("开始做任务三");
        long start = System.currentTimeMillis();
        Thread.sleep(random.nextInt(10000));
        long end = System.currentTimeMillis();
        System.out.println("完成任务三,耗时:" + (end - start) + "毫秒");
    }

}

异步service

package com.xiaoge;

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;

/**
 * TODO
 *
 * @author <a href="mailto:zhangxiao@dist.com.cn">Zhang Xiao</a>
 * @since
 */
@Component
public class AsyncTask {

    public static Random random =new Random();

    @Async("taskExecutor")
    public Future<String> doTaskOne() throws Exception {
        System.out.println("开始做任务一");
        long start = System.currentTimeMillis();
        Thread.sleep(random.nextInt(10000));
        long end = System.currentTimeMillis();
        System.out.println("完成任务一,耗时:" + (end - start) + "毫秒");
        return new AsyncResult<>("任务一完成");
    }

    @Async("taskExecutor")
    public Future<String> doTaskTwo() throws Exception {
        System.out.println("开始做任务二");
        long start = System.currentTimeMillis();
        Thread.sleep(random.nextInt(10000));
        long end = System.currentTimeMillis();
        System.out.println("完成任务二,耗时:" + (end - start) + "毫秒");
        return new AsyncResult<>("任务二完成");
    }

    @Async("taskExecutor")
    public Future<String> doTaskThree() throws Exception {
        System.out.println("开始做任务三");
        long start = System.currentTimeMillis();
        Thread.sleep(random.nextInt(10000));
        long end = System.currentTimeMillis();
        System.out.println("完成任务三,耗时:" + (end - start) + "毫秒");
        return new AsyncResult<>("任务三完成");
    }

}

启动类

package com.xiaoge;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.scheduling.annotation.EnableAsync;


/**
 * TODO
 *
 * @author <a href="mailto:zhangxiao@dist.com.cn">Zhang Xiao</a>
 * @since
 */
@EnableAsync
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
public class AsyncApplication {

    public static void main(String[] args) {
        SpringApplication.run(AsyncApplication.class, args);
    }

}

测试类

package com.xiaoge;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

/**
 * TODO
 *
 * @author <a href="mailto:zhangxiao@dist.com.cn">Zhang Xiao</a>
 * @since
 */
@RunWith(SpringRunner.class)
@SpringBootTest(classes = AsyncApplication.class)
public class AsyncTestApplication {

    @Autowired
    private AsyncTask asyncTask;

    @Autowired
    private Task task;

    /**
     * 异步方法
     * @throws Exception
     */
    @Test
    public void test() throws Exception {
        asyncTask.doTaskOne();
        asyncTask.doTaskTwo();
        asyncTask.doTaskThree();
    }

    /**
     * 同步方法
     * @throws Exception
     */
    @Test
    public void test1() throws Exception {
        task.doTaskOne();
        task.doTaskTwo();
        task.doTaskThree();
    }


}
### 实现 Spring Boot 控制器中的异步调用 为了在 Spring Boot 应用程序中实现控制器的异步调用,可以遵循以下方式构建应用程序结构和配置。 #### 配置主类 确保主应用类已启用异步支持。这可以通过添加 `@EnableAsync` 注解到主类上来完成[^2]: ```java package com.work; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.scheduling.annotation.EnableAsync; @SpringBootApplication @EnableAsync public class SpringBootWorkApplication { public static void main(String[] args) { SpringApplication.run(SpringBootWorkApplication.class, args); } } ``` #### 创建异步服务类 由于在同一类内无法有效触发代理机制从而导致异步功能失效的情况存在,因此建议将实际执行的任务逻辑放置于独立的服务类之中[^1]。这里定义了一个简单的异步服务接口及其对应的实现类: ```java // AsyncService.java package com.work.service; import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Service; @Service public interface AsyncService { @Async void performTask(); } // AsyncServiceImpl.java package com.work.service.impl; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Service; import java.util.concurrent.CompletableFuture; @Service public class AsyncServiceImpl implements AsyncService { private final Logger logger = LoggerFactory.getLogger(this.getClass()); @Override @Async public CompletableFuture<Void> performTask() throws InterruptedException { logger.info("Starting async task..."); Thread.sleep(5000L); // Simulate a long-running process. logger.info("Finished async task."); return CompletableFuture.completedFuture(null); } } ``` 注意,在此例子中不仅实现了 `performTask()` 方法作为真正的业务处理单元,还通过返回 `CompletableFuture<Void>` 来允许客户端跟踪任务进度并接收回调通知。 #### 构建控制器以发起异步请求 最后一步是在控制器层面上提供 RESTful API 接口供外部访问,并在此处调用之前创建好的异步服务实例: ```java // TaskController.java package com.work.controller; import com.work.service.AsyncService; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController @RequestMapping("/tasks") public class TaskController { private final AsyncService asyncService; @Autowired public TaskController(AsyncService asyncService){ this.asyncService = asyncService; } @GetMapping("/async-task") public String startAsyncTask(){ try{ asyncService.performTask().get(); // Wait for the completion of asynchronous operation synchronously here just to demonstrate purpose only. // In real-world applications you may not want your web request waiting on an async call like this. } catch (Exception e){ System.out.println(e.getMessage()); } return "Asynchronous task has been started."; } } ``` 上述代码片段展示了如何在一个名为 `/tasks/async-task` 的 GET 请求路径下启动一个异步任务。需要注意的是,尽管在这个例子中使用了 `.get()` 方法等待异步操作的结果以便展示目的,但在生产环境中通常不会这样做,而是让前端轮询状态或设置监听器等方式获取最终结果。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

只因为你温柔

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值