在实际开发中,我们经常会遇到一些耗时较长的操作,比如文件上传、数据批量处理、第三方接口调用等。如果采用同步调用的方式,主线程会被阻塞,等待这些耗时操作完成后才能继续执行,这会严重影响系统的响应速度和并发能力。而异步方法调用可以让主线程在发起调用后立即返回,继续执行后续任务,耗时操作则在独立的线程中完成,从而提升系统的吞吐量和响应效率。本文将详细介绍在 Spring Boot 中如何实现异步方法调用。
一、异步方法调用的核心原理
Spring Boot 的异步调用核心基于 Spring 的 AOP 机制和线程池实现。其本质是通过动态代理为标注了异步注解的方法创建代理对象,当调用该方法时,代理对象会将方法的执行任务提交到指定的线程池中,由线程池中的空闲线程来执行该任务,而主线程则直接返回,不会等待任务执行完成。
需要注意的是,异步方法调用的关键是方法的调用者和被调用者不在同一个线程中执行,因此如果在同一个类中直接调用异步方法,由于无法触发 AOP 代理,会导致异步失效,这是开发中常见的坑。
二、Spring Boot 实现异步方法调用的步骤
Spring Boot 为异步调用提供了便捷的自动配置支持,我们只需遵循以下几个步骤即可快速实现。
步骤 1:开启异步支持
在 Spring Boot 应用的主启动类或配置类上添加@EnableAsync 注解,该注解用于开启 Spring 的异步方法支持,会自动扫描并识别标注了 @Async 注解的方法。
示例代码(主启动类):
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableAsync;
@SpringBootApplication
@EnableAsync // 开启异步支持
public class AsyncDemoApplication {
public static void main(String[] args) {
SpringApplication.run(AsyncDemoApplication.class, args);
}
}
步骤 2:定义异步方法
在需要异步执行的方法上添加 @Async 注解,该注解用于标识当前方法是一个异步方法。需要注意的是,异步方法建议定义在独立的服务类中,避免在同一个类中调用导致异步失效。
异步方法的返回值可以分为两种情况:
- 无返回值:直接定义为
void类型; - 有返回值:需要使用
Future或其实现类(如CompletableFuture)作为返回值,用于获取异步方法的执行结果。
示例代码(异步服务类):
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import java.util.concurrent.CompletableFuture;
@Service
public class AsyncService {
// 无返回值的异步方法
@Async
public void asyncNoReturnMethod() {
System.out.println("无返回值异步方法开始执行,线程名:" + Thread.currentThread().getName());
try {
// 模拟耗时操作(如调用第三方接口、数据处理等)
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("无返回值异步方法执行完成");
}
// 有返回值的异步方法,使用 CompletableFuture 接收结果
@Async
public CompletableFuture<String> asyncWithReturnMethod(String param) {
System.out.println("有返回值异步方法开始执行,参数:" + param + ",线程名:" + Thread.currentThread().getName());
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
String result = "异步方法执行完成,参数:" + param;
System.out.println("有返回值异步方法执行完成,结果:" + result);
return CompletableFuture.completedFuture(result);
}
}
步骤 3:调用异步方法
在控制器或其他业务类中注入异步服务类的实例,直接调用其标注了 @Async 的方法即可。调用时,主线程会立即返回,不会等待异步方法执行完成。如果需要获取有返回值异步方法的结果,可以通过 CompletableFuture 的 get() 方法(会阻塞主线程)或 whenComplete() 方法(非阻塞)处理。
示例代码(控制器):
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
@RestController
@RequestMapping("/async")
public class AsyncController {
@Autowired
private AsyncService asyncService;
@GetMapping("/noReturn")
public String testAsyncNoReturn() {
System.out.println("主线程开始调用无返回值异步方法,线程名:" + Thread.currentThread().getName());
// 调用异步方法
asyncService.asyncNoReturnMethod();
System.out.println("主线程调用无返回值异步方法后立即返回");
return "无返回值异步方法已发起调用";
}
@GetMapping("/withReturn")
public String testAsyncWithReturn() throws ExecutionException, InterruptedException {
System.out.println("主线程开始调用有返回值异步方法,线程名:" + Thread.currentThread().getName());
// 调用有返回值的异步方法,获取 CompletableFuture 对象
CompletableFuture<String> future1 = asyncService.asyncWithReturnMethod("param1");
CompletableFuture<String> future2 = asyncService.asyncWithReturnMethod("param2");
// 方式 1:使用 get() 方法阻塞获取结果(不推荐,会失去异步优势)
// String result1 = future1.get();
// String result2 = future2.get();
// 方式 2:使用 whenComplete() 非阻塞处理结果(推荐)
future1.whenComplete((result, ex) -> {
if (ex == null) {
System.out.println("异步方法1执行结果:" + result);
} else {
System.out.println("异步方法1执行失败:" + ex.getMessage());
}
});
future2.whenComplete((result, ex) -> {
if (ex == null) {
System.out.println("异步方法2执行结果:" + result);
} else {
System.out.println("异步方法2执行失败:" + ex.getMessage());
}
});
System.out.println("主线程调用有返回值异步方法后立即返回");
return "有返回值异步方法已发起调用,结果将异步处理";
}
}
三、自定义线程池(推荐)
Spring Boot 默认的异步线程池配置可能无法满足实际业务需求(例如默认核心线程数、最大线程数较小)。因此,我们通常会自定义线程池,通过 @Bean 注解创建 ThreadPoolTaskExecutor 实例,并指定线程池参数。
3.1 自定义线程池配置类
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 AsyncThreadPoolConfig {
@Bean(name = "asyncTaskExecutor") // 线程池名称,用于指定异步方法使用该线程池
public Executor asyncTaskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
// 核心线程数(默认线程数)
executor.setCorePoolSize(5);
// 最大线程数
executor.setMaxPoolSize(10);
// 队列容量
executor.setQueueCapacity(25);
// 线程空闲时间(默认 60 秒)
executor.setKeepAliveSeconds(60);
// 线程名称前缀
executor.setThreadNamePrefix("AsyncTask-");
// 拒绝策略(当线程池满时,如何处理新任务)
// ThreadPoolExecutor.CallerRunsPolicy:由调用线程(主线程)执行该任务
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
// 初始化线程池
executor.initialize();
return executor;
}
}
3.2 异步方法指定使用自定义线程池
在@Async 注解中通过 value 属性指定自定义线程池的名称,即可让该异步方法使用指定的线程池执行。
@Async("asyncTaskExecutor") // 指定使用自定义的线程池
public void asyncNoReturnMethod() {
// 方法实现...
}
四、异步方法的异常处理
异步方法在执行过程中可能会出现异常,由于异步方法的执行线程与主线程分离,主线程无法直接捕获异步方法的异常。因此,需要单独处理异步方法的异常,常见的处理方式有两种:
4.1 针对单个异步方法处理异常
对于有返回值的异步方法,可以通过 CompletableFuture 的 exceptionally() 方法捕获异常;对于无返回值的异步方法,可以在方法内部直接 try-catch 捕获异常。
// 有返回值异步方法的异常处理
@Async("asyncTaskExecutor")
public CompletableFuture<String> asyncWithReturnMethod(String param) {
try {
System.out.println("有返回值异步方法开始执行,参数:" + param + ",线程名:" + Thread.currentThread().getName());
Thread.sleep(2000);
// 模拟异常
if ("error".equals(param)) {
throw new RuntimeException("参数异常:" + param);
}
String result = "异步方法执行完成,参数:" + param;
return CompletableFuture.completedFuture(result);
} catch (Exception e) {
// 使用 exceptionally() 处理异常
return CompletableFuture.failedFuture(e);
}
}
// 调用时处理异常
CompletableFuture<String> future = asyncService.asyncWithReturnMethod("error");
future.whenComplete((result, ex) -> {
if (ex != null) {
System.out.println("捕获到异步方法异常:" + ex.getMessage());
}
});
4.2 全局异常处理
通过实现 AsyncUncaughtExceptionHandler 接口自定义全局异常处理器,并在 @EnableAsync 注解中指定该处理器,即可统一处理所有无返回值异步方法的异常。
// 自定义全局异步异常处理器
import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler;
import java.lang.reflect.Method;
public class CustomAsyncUncaughtExceptionHandler implements AsyncUncaughtExceptionHandler {
@Override
public void handleUncaughtException(Throwable ex, Method method, Object... params) {
System.out.println("全局捕获异步方法异常:");
System.out.println("异常方法:" + method.getName());
System.out.println("异常参数:" + params);
System.out.println("异常信息:" + ex.getMessage());
}
}
// 在配置类中指定全局异常处理器
@Configuration
@EnableAsync(asyncUncaughtExceptionHandler = CustomAsyncUncaughtExceptionHandler.class)
public class AsyncConfig {
// 线程池配置...
}
五、注意事项
- 避免同一类中调用异步方法:由于 Spring 异步基于 AOP 代理,同一类中直接调用异步方法无法触发代理,会导致异步失效,方法会以同步方式执行。
- 异步方法不能是 private 修饰:private 方法无法被 AOP 代理,因此
@Async注解在 private 方法上无效。 - 合理配置线程池参数:线程池的核心线程数、最大线程数、队列容量等参数需要根据业务场景合理配置,避免出现线程池满、任务堆积或资源浪费的情况。
- 谨慎使用 get() 方法:
CompletableFuture.get()方法会阻塞主线程,若必须使用,建议设置超时时间(如get(3, TimeUnit.SECONDS)),避免主线程无限期等待。 - 事务支持:异步方法默认不继承主线程的事务,若异步方法需要事务支持,需在异步方法上单独添加
@Transactional注解,且该方法必须是 public 修饰。
六、总结
Spring Boot 通过 @EnableAsync 和 @Async 注解结合线程池,能够快速实现异步方法调用,有效提升系统的并发能力和响应速度。在实际开发中,建议自定义线程池以满足业务需求,并做好异步方法的异常处理。同时,需注意避免异步失效的常见场景,确保异步调用的正确性和高效性。通过合理使用异步调用,可以让系统更好地应对耗时操作,提升用户体验。