一文带你深入学习Spring注解之@Async使用
一文带你深入学习Spring注解之@Async使用
在现代Java应用程序中,响应速度是评价程序的重要指标,而异步处理是提高性能和响应速度的重要手段之一。Spring框架提供了@Async注解来简化异步编程,本文将深入讲解@Async注解的使用方法、实现原理以及应用场景。
一、背景
在现代Java应用程序中,响应速度是评价程序的重要指标,而异步处理是提高性能和响应速度的重要手段之一。在如今的Spring Boot全家桶中,它提供了@Async注解来简化异步编程,提高了程序处理数据的速度。
二、基本原理
要想使用@Async注解来执行异步方法,我们需要掌握两个核心注解的原理:@EnableAsync和@Async注解。接下来,就带大家去依次分析其源码,弄清楚如何异步执行。
2.1 @EnableAsync注解
- 作用:启用 Spring Async功能
- 使用位置:通常标注在配置类上(
@Configuration
或 Spring Boot 的主启动类上)。 - 工作原理:
(1)根据上面@EnableAsync源码可以看出,该注解中使用了一个@Import注解导入了一个AsyncConfigurationSelector.class类。
(2)进入到AsyncConfigurationSelector类,可以看到该类继承了AdviceModeImportSelector抽象类,AdviceModeImportSelector抽象类又实现了ImportSelector接口。并且在selectImports()方法中根据不同的AdviceMode来选择和返回适当的导入语句。
(3)进入到ProxyAsyncConfiguration配置类中,可以看到会创建一个AsyncAnnotationBeanPostProcessor对象,并设置相关的属性后返回该对象。
(4)再进入到AsyncAnnotationBeanPostProcessor类中,有个setBeanFactory()方法,在这个方法中往BeanFactory容器中增加了一个AsyncAnnotationAdvisor增强器。
(5)进入这个AsyncAnnotationAdvisor增强器的构造函数可以看到,分别创建了一个通知以及切入点。
(6)在这个创建增强方法的的函数中,可以看到创建了一个拦截器,而在这个创建切入点的函数中,可以看出方法入参就是Async类型,因此,最终通过这个拦截器来拦截标注有@Async注解的方法。
(7)进入到AnnotationAsyncExecutionInterceptor拦截器,可以看到其继承了AsyncExecutionInterceptor,主要的拦截方法就在这个AsyncExecutionInterceptor父类的invoke()方法中。主要步骤也就下面三步:1. 获取线程池;2. 创建线程;3. 提交任务到线程去执行。
(8)看下获取线程池的处理逻辑。主要步骤是:1.先从缓存中去查找,如果缓存中存在线程池就直接返回这个线程池去使用;2.如果缓存中没找到再根据方法的@Async注解value值进行判断;3.方法的的@Async注解设置了value值,则把value的值当做线程池的bean名称去Spring容器中找到这个bean并返回去使用;4.如果方法的@Async注解没有设置了value值,则使用默认的线程池去调用。
(9)看下获取注解值的方法。主要逻辑就是先拿到方法的Async注解,再根据拿到的注解获取该注解的value属性值。
(10)再看下使用默认线程池的方法。主要是先调用父类的getDefaultExecutor方法去获取线程池对象,如果能获取到就直接使用该线程池对象,如果获取不到就新建个SimpleAsyncTaskExecutor线程池对象。
感兴趣的同学点进到SimpleAsyncTaskExecutor源码里看,就能发现,这个线程池非常简单,通过这个先吃创建的线程不能重复使用,且每次调用都会去创建一个线程,在高并发或者访问很频发按的场景并不适用,很容易造成系统资源耗尽,严重就可能导致系统崩溃。所以在使用@Async注解去实现方法的异步调用时最好使用自定义线程池,具体的使用方法在下文中还会介绍。
2.2 @Async注解
- 作用:标记方法为异步方法,指定异步允许策略,让对应的方法在执行的时候会按照配置的线程池去执行,不会占用和阻塞主线程。
- 使用位置:通常标注在在需要支持异步的类或方法上。
- 工作原理:
能够看出,该注解的作用位置可以是类上也可以是方法上。当标记到类上的时候,该类中所有保留的方法都可以走异步,标记在方法时,仅对该方法生效。通过本章第一小节@EnableAsync注解的实现原理介绍,不难理解实现异步方法的核心其实是@EnableAsync注解,而@Async注解有两个作用,第一个就是实现了切入点的作用,用于标识哪些方法或者类需要异步处理,第二个就是标识自定义线程池。
三、使用方法
1.一定要自定义线程池,不推荐使用默认线程池;
2.这是基于注解来实现的,所以需要在配置类上添加@EnableAsync注解,启用异步功能@Async才能生效;
3.@Async所在的类必须由Spring管理(类需包含@Compent、@Service等Spring注解)
4.调用该异步方法的类也必须是Spring管理的类;
5.异步方法对应类的实例必须也由Spring创建的(@Autowired、@Resource)
@Configuration
@EnableAsync
public class AsyncConfig implements AsyncConfigurer {
@Override
public Executor getAsyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(7);
executor.setMaxPoolSize(42);
executor.setQueueCapacity(11);
executor.setThreadNamePrefix("ht-core-executor-");
executor.initialize();
return executor;
}
@Override
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
return new SimpleAsyncUncaughtExceptionHandler();
}
}
四、避免踩坑的点
4.1 @EnableAsync注解和Executor的bean不能少
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;
@Configuration
@EnableAsync
public class AppConfig{
// 配置内容
}
4.2 不能和@Transaction注解同时标记,会导致无法异步执行
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class MyService {
@Transactional
public void transactionalMethod() {
// 事务逻辑
asyncMethod();
}
@Async
public void asyncMethod() {
// 异步逻辑
}
}
4.3 被@Async注解修饰的方法,返回值只能是void或者Future以及子类
import java.util.concurrent.CompletableFuture;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
@Service
public class MyAsyncService {
@Async
public CompletableFuture<String> asyncMethod() {
return CompletableFuture.supplyAsync(() -> {
// 异步任务逻辑
return "异步任务完成";
});
}
}
4.4 @Async注解不能标记在静态方法上
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
@Service
public class AsyncService {
@Async
public void asyncMethod() {
// 异步任务逻辑
}
// 错误用法,不能在静态方法上使用@Async
@Async
public static void staticAsyncMethod() {
// 异步任务逻辑
}
}
4.5 异步方法的类内部不能直接调用该方法this.asyncMethod()
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
@Service
public class AsyncService {
@Autowired
private AsyncService self;// 注入自身
public void externalMethod() {
self.asyncMethod(); // 异步任务逻辑
}
@Async
public void asyncMethod() {
// 异步任务逻辑
}
}
4.6 @Async异常处理
当使用@Async注解时,如果异步方法抛出异常,该异常不会在调用该异步方法的地方直接被捕获,而是会被包装在Future或CompletableFuture对象中。具体来说:
- 如果返回值是void,异常会被记录在日志中,并且不会返回给调用者。
- 如果返回值是Future或CompletableFuture,可以通过这些对象来检查和处理异常。
@Service
public class AsyncService {
@Async
public Future<String> asyncMethod() {
return CompletableFuture.supplyAsync(() -> {
if (true) { // 模拟异常
throw new RuntimeException("异步任务异常");
}
return "异步任务完成";
});
}
}
@RestController
public class AsyncController {
@Autowired
private AsyncService asyncService;
@GetMapping("/async")
public String async() {
Future<String> future = asyncService.asyncMethod();
try {
return future.get(); // 捕获处理异常
} catch (InterruptedException | ExecutionException e) {
return "任务失败:" + e.getMessage();
}
}
}
五、应用场景
@Async注解适用于各种需要异步处理的场景,例如:
1.后台任务处理
在Web应用中,有些任务(如发送邮件、生成报告)耗时较长,可以使用@Async异步处理,使用户无需等待任务完成即可获得响应。
2.并行处理
对于可以并行处理的任务,如并行数据处理、并行调用多个外部服务,使用@Async可以提高效率。
3.提高系统吞吐量
通过异步调用,可以充分利用多线程资源,提高系统的吞吐量和响应速度。
六、局限性分析
1.线程池管理复杂性:
默认的线程池配置可能无法满足高并发和大规模的生产环境的需求,需要细致地对线程参数进行调优,如核心线程数、最大线程数、队列容量等。这些配置不可能会导致线程资源耗尽或任务堆积,影响服务的稳定性。
2. 缺乏高级功能:
@Async提供的功能相对简单,缺乏如任务调度、任务依赖管理、失败重试、超时控制等高级功能。这些功能在复杂业务场景中可能是必须的,而@Async无法提供。
3.调试和错误处理困难:
异步任务的调试和错误处理较为复杂。异常不会直接传递到调用者,需要通过回调或其他机制处理,增加了代码复杂度和维护难度。
4.可观察性和监控不足:
对于生产系统,监控和可观察性非常重要,@Async在这方面的支持有限,难以监控异步任务的执行情况、性能指标、异常等信息。
5.事务支持问题:
异步方法中的事务管理可能会出现问题。默认情况下,事务上下文不会传递到异步方法中,需要额外配置或编码来确保事务的正确传播。
6.可扩展性限制:
对于需要高度可拓展性的系统,@Async的简单模型可能不足以应对复杂的负载均衡、动态拓展等需求。
七、结语
Spring Boot的@Async注解提供了简单且强大的方式来实现异步处理。通过启用异步支持、定义异步方法并自定义TaskExecutor,可以高效地处理各种异步任务。掌握@Async注解的使用和原理,有助于提升应用程序的性能和响应速度。但是其局限性也是非常明显的,因此,要想用好该@Async注解,还是需要在此基础上加以扩展。