@Async 异步失效的 9 种场景
前言
最近有位小伙伴问了我一个问题:他在项目某个方法使用@Async
注解,但是还是该方法还是同步
执行了,异步
不起作用,到底是什么原因呢?
伪代码如下:
这个问题还是比较有意思的,今天这篇文章总结了 @Async 注解失效的 9 种场景,希望对你会有所帮助。
1 未使用 @EnableAsync 注解
在 Spring 中要开启 @Async 注解异步的功能,需要在项目的启动类,或者配置类上,使用@EnableAsync
注解。
例如:
@EnableAsync
注解相当于一个开关
,控制是否开启@Async
注解异步的功能,默认是关闭的。
如果在项目的启动类上未使用 @EnableAsync 注解,则 @Async 注解异步的功能不生效。
2 内部方法调用
我们在日常开发中,经常需要在一个方法中调用另外一个方法,例如:
这个示例中,在 UserService 类中的 test()方法中调用了 async()方法。
如果在 controller 中 @Autowired 了 UserService 类的对象,调用了它的 test()方法,则 async()异步的功能会失效。
我们知道 Spring 通过 @Async 注解实现异步的功能,底层其实是通过 Spring 的AOP
实现的,也就是说它需要通过JDK动态代理
或者cglib
,生成代理对象
。
异步的功能,是在代理对象中增加的,我们必须调用代理对象的 test()方法才行。
而在类中直接进行方法的内部调用,在 test()方法中调用 async()方法,调用的是该类原对象的 async 方法,相当于调用了 this.async()方法,而并非 UserService 代理类的 async()方法。
因此,像这种内部方法调用,@Async 注解的异步功能会失效。
3 方法非 public
在 Java 中有 4 种权限修饰符
public
:所有类都可以访问。private
:只能同一个类访问。protected
:同一个类,同一个包下的其他类,不同包下的子类可以访问。默认修饰符
:同一个类,同一个包下的其他类可以访问。
在实际工作中,我们使用频率最高的可能是 public 和 private 了。
如果我在定义 Service 类中的某个方法时,有时把权限修饰符定义错了,例如:
这个例子中将 UserService 类的 async()方法的权限修饰符定义成了 private 的,这样 @Async 注解也会失效。
因为 private 修饰的方法,只能在 UserService 类的对象中使用。
而 @Async 注解的异步功能,需要使用 Spring 的 AOP 生成 UserService 类的代理对象,该代理对象没法访问 UserService 类的 private 方法,因此会出现 @Async 注解失效的问题。
4 方法返回值错误
我们在写一个新的方法时,经常需要定义方法的返回值。
返回值可以是 void、int、String、User 等等,但如果返回值定义错误,也可能会导致 @Async 注解的异步功能失效。
例如:
UserService 类的 async 方法的返回值是 String,这种情况竟然会导致 @Async 注解的异步功能失效。
在 AsyncExecutionInterceptor 类的 invoke()方法,会调用它的父类 AsyncExecutionAspectSupport 中的 doSubmit 方法,该方法时异步功能的核心代码,如下:
从图中看出,@Async 注解的异步方法的返回值,要么是 Future,要么是 null。
因此,在实际项目中,如果想要使用 @Async 注解的异步功能,相关方法的返回值必须是void
或者Future
。
5 方法用 static 修饰了
有时候,我们的方法会使用 static 修饰,这样在调用的地方,可以直接使用类名.方法名,访问该方法了。
但如果在 @Async 方法上加了 static 修饰符,例如:
这时 @Async 的异步功能会失效,因为这种情况 idea 会直接报错:Methods annotated with '@Async' must be overridable 。
使用 @Async 注解声明的方法,必须是能被重写的,很显然 static 修饰的方法,是类的静态方法,是不允许被重写的。
因此这种情况下,@Async 注解的异步功能会失效。
6 方法用 final 修饰
在 Java 种 final 关键字,是一个非常特别的存在。
用 final 修饰的类,没法被继承。
用 final 修饰的方法,没法被重写。
用 final 修饰的变量,没法被修改。
如果 final 使用不当,也会导致 @Async 注解的异步功能失效,例如:
这种情况下 idea 也会直接报错:Methods annotated with '@Async' must be overridable 。
因为使用 final 关键字修饰的方法,是没法被子类重写的。
因此这种情况下,@Async 注解的异步功能会失效。
7 业务类没加 @Service 注解
有时候,我们在新加 Service 类时,会忘了加@Service
注解,例如:
这种情况下,@Async 注解异步的功能也不会生效。因为 UserService 类没有使用 @Service、@Component 或者 @Controller 等注解声明,该类不会被 Spring 管理,因此也就无法使用 Spring 的异步功能。
8 自己 new 的对象
在项目中,我们经常需要 new 一个对象,然后对他赋值,或者调用它的方法。
但如果 new 了一个 Service 类的对象,可能会出现一些意想不到的问题,例如:
在 TestService 类的 test()方法中,new 了一个 UserService 类的对象,然后调用该对象的 async()方法。
很显然这种情况下,async()方法只能同步执行,没法异步执行。
因为在项目中,我们自己 new 的对象,不会被 Spring 管理,因此也就无法使用 Spring 的异步功能。
不过我们可以通过BeanPostProcessor
类,将创建的对象手动注入到 Spring 容器中。
9 Spring 无法扫描异步类
我们在 Spring 项目中可以使用@ComponentScan
注解指定项目中扫描的包路径,例如:
项目中 com.susan.demo.service1 这个路径是不存在的,会导致 @Async 注解异步的功能失效。
同时如果 @ComponentScan 注解定义的路径,没有包含你新加的 Servcie 类的路径,@Async 注解异步的功能也会失效。
文章转载自:苏三说技术
评论