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