异步调用应用场景

在SpringBoot的日常开发中,一般都是同步调用的。但实际中有很多场景非常适合使用异步来处理,如:注册新用户,送100个积分;或下单成功,发送push消息等等。

就拿注册新用户这个用例来说,为什么要异步处理?

第一个原因:容错性、健壮性,如果送积分出现异常,不能因为送积分而导致用户注册失败;

因为用户注册是主要功能,送积分是次要功能,即使送积分异常也要提示用户注册成功,然后后面在针对积分异常做补偿处理。

第二个原因:提升性能,例如注册用户花了20毫秒,送积分花费50毫秒,如果用同步的话,总耗时70毫秒,用异步的话,无需等待积分,故耗时20毫秒。

异步能解决2个问题:性能和容错性。

实现异步调用

Springboot3提供了@Async注解,只需要在方法上标注此注解,此方法即可实现异步调用。

也可以通过@EnableAsync注解开启异步功能,或者在配置类上添加@EnableAsync注解开启异步功能。

@EnableAsync注解可以直接放在SpringBoot启动类上,也可以单独放在其他配置类上

第一步:@EnableAsync放在启动类上

1
2
3
4
5
6
7
@SpringBootApplication
@EnableAsync // 开启异步支持
public class YourApplication {
    public static void main(String[] args) {
        SpringApplication.run(YourApplication.class, args);
    }
}

第二步:@EnableAsync放在配置类上

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
@Configuration
@EnableAsync // 开启异步支持
public class AsyncConfig implements AsyncConfigurer {
    @Bean(name = "asyncExecutor")
    @Override
    public Executor getAsyncExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(10);          // 核心线程数
        executor.setMaxPoolSize(20);           // 最大线程数
        executor.setQueueCapacity(100);        // 队列容量
        executor.setThreadNamePrefix("Async-"); // 线程名前缀
        executor.initialize();
        return executor;
    }
}

第三步:创建异步方法

在需要异步执行的方法上添加@Async注解,并指定执行器名称

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;

import java.util.concurrent.CompletableFuture;

@Service
public class AsyncService {

    // 无返回值的异步方法
    @Async("asyncExecutor") // 指定执行器,若不指定则使用默认执行器
    public void asyncMethodWithoutReturn() {
        System.out.println("异步方法执行,线程名:" + Thread.currentThread().getName());
        // 模拟耗时操作
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
        System.out.println("异步方法执行完成");
    }

    // 有返回值的异步方法(使用CompletableFuture包装返回值)
    @Async("asyncExecutor")
    public CompletableFuture<String> asyncMethodWithReturn() {
        System.out.println("异步方法执行,线程名:" + Thread.currentThread().getName());
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
        return CompletableFuture.completedFuture("异步执行结果");
    }
}

第四步:用异步方法

在 Controller 或其他 Service 中注入并调用异步方法:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.concurrent.CompletableFuture;

@RestController
public class AsyncController {

    @Autowired
    private AsyncService asyncService;

    @GetMapping("/async")
    public String async() {
        // 调用无返回值的异步方法
        asyncService.asyncMethodWithoutReturn();
        return "异步方法已触发";
    }

    @GetMapping("/async-with-result")
    public String asyncWithResult() throws Exception {
        // 调用有返回值的异步方法
        CompletableFuture<String> future = asyncService.asyncMethodWithReturn();
        
        // 阻塞等待结果(实际场景中通常不直接阻塞)
        String result = future.get();
        
        // 或使用回调处理结果
        // future.thenAccept(result -> System.out.println("异步结果: " + result));
        
        return "异步结果: " + result;
    }
}

注意事项

  1. 方法限制:
    • 异步方法必须是public的。
    • 异步方法不能在同一个类中被调用(Spring 通过 AOP 代理实现异步,自调用会失效)。
  2. 异常处理:
    • 无返回值的异步方法中抛出的异常无法直接捕获,建议在方法内部使用try-catch。
    • 有返回值的异步方法可通过CompletableFuture的exceptionally()或handle()处理异常。
  3. 事务问题:
    • 异步方法与事务不兼容,若需事务支持,建议在异步方法内部手动管理事务。
  4. 性能优化:
    • 根据业务需求调整线程池参数(核心线程数、最大线程数、队列容量)。
    • 避免创建过多异步任务导致资源耗尽。

扩展:全局异常处理

可实现AsyncUncaughtExceptionHandler接口处理无返回值异步方法的异常:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.AsyncConfigurer;

import java.lang.reflect.Method;

@Configuration
public class AsyncExceptionConfig implements AsyncConfigurer {

    @Override
    public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
        return (throwable, method, objects) -> {
            System.err.println("异步方法异常: " + method.getName());
            throwable.printStackTrace();
        };
    }
}