SpringBoot中异步调用的使用

本文详细讲解了如何在Spring Boot中使用@Async异步注解,包括如何配置自定义线程池,避免同类方法调用失效,以及异步请求与异步调用的区别。特别提到将异步方法抽离和开启CGLIB代理以调用同类异步任务。

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

一,使用方法
1,启动类上加@EnableAsync注解,开启异步
2,在需要异步执行的方法上加@Async(“threadpool”)注解,(threadpool)可以为空

二,注意事项
(1)在默认情况下,未设置TaskExecutor时,默认是使用SimpleAsyncTaskExecutor这个线程池,但此线程不是真正意义上的线程池,因为线程不重用,每次调用都会创建一个新的线程。可通过控制台日志输出可以看出,每次输出线程名都是递增的。所以最好我们来自定义一个线程池。
(2)调用的异步方法,不能为同一个类的方法(包括同一个类的内部类),简单来说,因为Spring在启动扫描时会为其创建一个代理类,而同类调用时,还是调用本身的代理类的,所以和平常调用是一样的。其他的注解如@Cache等也是一样的道理,说白了,就是Spring的代理机制造成的。所以在开发中,最好把异步服务单独抽出一个类来管理。

三,什么情况下会导致@Async异步方法会失效?
(1)调用同一个类下注有@Async异步方法:在spring中像@Async和@Transactional、cache等注解本质使用的是动态代理,其实Spring容器在初始化的时候Spring容器会将含有AOP注解的类对象“替换”为代理对象(简单这么理解),那么注解失效的原因就很明显了,就是因为调用方法的是对象本身而不是代理对象,因为没有经过Spring容器,那么解决方法也会沿着这个思路来解决。
(2)调用的是静态(static )方法
(3)调用(private)私有化方法

四,解决3中问题1的方式(其它2,3两个问题自己注意下就可以了)

将要异步执行的方法单独抽取成一个类,原理就是当你把执行异步的方法单独抽取成一个类的时候,这个类肯定是被Spring管理的,其他Spring组件需要调用的时候肯定会注入进去,这时候实际上注入进去的就是代理类了。
其实我们的注入对象都是从Spring容器中给当前Spring组件进行成员变量的赋值,由于某些类使用了AOP注解,那么实际上在Spring容器中实际存在的是它的代理对象。那么我们就可以通过上下文获取自己的代理对象调用异步方法。

@Controller
@RequestMapping("/app")
public class EmailController {
 
    //获取ApplicationContext对象方式有多种,这种最简单,其它的大家自行了解一下
    @Autowired
    private ApplicationContext applicationContext;
 
    @RequestMapping(value = "/email/asyncCall", method = GET)
    @ResponseBody
    public Map<String, Object> asyncCall () {
        Map<String, Object> resMap = new HashMap<String, Object>();
        try{
            //这样调用同类下的异步方法是不起作用的
            //this.testAsyncTask();
            //通过上下文获取自己的代理对象调用异步方法
            EmailController emailController = (EmailController)applicationContext.getBean(EmailController.class);
            emailController.testAsyncTask();
            resMap.put("code",200);
        }catch (Exception e) {
            resMap.put("code",400);
            logger.error("error!",e);
        }
        return resMap;
    }
 
    //注意一定是public,且是非static方法
    @Async
    public void testAsyncTask() throws InterruptedException {
        Thread.sleep(10000);
        System.out.println("异步任务执行完成!");
    }
 
}

6.开启cglib代理,手动获取Spring代理类,从而调用同类下的异步方法。

首先,在启动类上加上@EnableAspectJAutoProxy(exposeProxy = true)注解。
代码实现,如下:

@Service
@Transactional(value = "transactionManager", readOnly = false, propagation = Propagation.REQUIRED, rollbackFor = Throwable.class)
public class EmailService {
 
    @Autowired
    private ApplicationContext applicationContext;
 
    @Async
    public void testSyncTask() throws InterruptedException {
        Thread.sleep(10000);
        System.out.println("异步任务执行完成!");
    }
 
 
    public void asyncCallTwo() throws InterruptedException {
        //this.testSyncTask();
//        EmailService emailService = (EmailService)applicationContext.getBean(EmailService.class);
//        emailService.testSyncTask();
        boolean isAop = AopUtils.isAopProxy(EmailController.class);//是否是代理对象;
        boolean isCglib = AopUtils.isCglibProxy(EmailController.class);  //是否是CGLIB方式的代理对象;
        boolean isJdk = AopUtils.isJdkDynamicProxy(EmailController.class);  //是否是JDK动态代理方式的代理对象;
        //以下才是重点!!!
        EmailService emailService = (EmailService)applicationContext.getBean(EmailService.class);
        EmailService proxy = (EmailService) AopContext.currentProxy();
        System.out.println(emailService == proxy ? true : false);
        proxy.testSyncTask();
        System.out.println("end!!!");
    }

三、异步请求与异步调用的区别

两者的使用场景不同,异步请求用来解决并发请求对服务器造成的压力,从而提高对请求的吞吐量;而异步调用是用来做一些非主线流程且不需要实时计算和响应的任务,比如同步日志到kafka中做日志分析等。
异步请求是会一直等待response相应的,需要返回结果给客户端的;而异步调用我们往往会马上返回给客户端响应,完成这次整个的请求,至于异步调用的任务后台自己慢慢跑就行,客户端不会关心。
相关资源:springboot高并发下提高吞吐量的实现
原文链接

### Spring Boot使用 @Async 注解实现异步调用方法 在 Spring Boot 中,`@Async` 注解可以用于标记一个方法为异步执行。这意味着当调用该方法时,它将在单独的线程中运行,而不会阻塞主线程。为了实现这一功能,需要进行以下配置和编码。 #### 1. 启用异步支持 首先,在 Spring Boot 的启动或配置上添加 `@EnableAsync` 注解以启用异步支持[^2]。 ```java @SpringBootApplication @EnableAsync // 启用异步支持 public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } } ``` #### 2. 配置自定义线程池(可选) 如果需要对线程池进行更精细的控制,可以通过创建一个自定义线程池来替代默认线程池[^3]。例如: ```java @Configuration public class ExecutorConfig { @Value("${async.executor.thread.core_pool_size:5}") // 核心线程数,默认为5 private int corePoolSize; @Value("${async.executor.thread.max_pool_size:10}") // 最大线程数,默认为10 private int maxPoolSize; @Value("${async.executor.thread.queue_capacity:100}") // 队列容量,默认为100 private int queueCapacity; @Value("${async.executor.thread.name.prefix:AsyncThread-}") // 线程名称前缀 private String namePrefix; @Bean(name = "taskExecutor") public ThreadPoolTaskExecutor taskExecutor() { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); executor.setCorePoolSize(corePoolSize); executor.setMaxPoolSize(maxPoolSize); executor.setQueueCapacity(queueCapacity); executor.setThreadNamePrefix(namePrefix); executor.initialize(); return executor; } } ``` #### 3. 定义异步方法 接下来,在 Spring 管理的组件(如 `@Service` 或 `@Component` 注解的)中定义异步方法,并使用 `@Async` 注解标记该方法[^2]。例如: ```java @Service public class MyService { @Async("taskExecutor") // 指定使用自定义线程池 public CompletableFuture<Void> asyncTask(String message) throws InterruptedException { System.out.println("异步任务开始:" + message + ",当前线程:" + Thread.currentThread().getName()); Thread.sleep(2000); // 模拟耗时操作 System.out.println("异步任务结束:" + message + ",当前线程:" + Thread.currentThread().getName()); return CompletableFuture.completedFuture(null); } } ``` 在此示例中,`@Async("taskExecutor")` 指定了使用自定义线程池 `taskExecutor` 来执行异步任务。如果没有指定线程池名称,则会使用默认线程池。 #### 4. 调用异步方法 最后,在控制器或其他服务中调用异步方法。由于异步方法返回的是 `CompletableFuture`,可以利用其提供的 API 进行结果处理[^2]。例如: ```java @RestController @RequestMapping("/async") public class MyController { @Autowired private MyService myService; @GetMapping("/execute") public String executeAsyncTask() { try { CompletableFuture<Void> future1 = myService.asyncTask("任务1"); CompletableFuture<Void> future2 = myService.asyncTask("任务2"); CompletableFuture.allOf(future1, future2).join(); // 等待所有任务完成 } catch (Exception e) { return "任务执行失败:" + e.getMessage(); } return "所有异步任务已完成!"; } } ``` #### 注意事项 - 异步方法必须定义在 Spring 管理的 Bean 中(如 `@Service`、`@Component` 注解的)[^2]。 - 如果在同一中直接调用异步方法,则代理机制无法生效,导致异步调用失效[^2]。 - 可以通过返回 `CompletableFuture<T>` 或 `ListenableFuture<T>` 来获取异步方法的结果[^1]。 --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值