写点什么

@Async 异步失效的 9 种场景

  • 2024-05-08
    福建
  • 本文字数:3141 字

    阅读完需:约 10 分钟

前言


最近有位小伙伴问了我一个问题:他在项目某个方法使用@Async注解,但是还是该方法还是同步执行了,异步不起作用,到底是什么原因呢?


伪代码如下:

@Slf4j@Servicepublic class UserService {
@Async public void async(String value) { log.info("async:{}", value); }}
复制代码


这个问题还是比较有意思的,今天这篇文章总结了 @Async 注解失效的 9 种场景,希望对你会有所帮助。



1 未使用 @EnableAsync 注解


在 Spring 中要开启 @Async 注解异步的功能,需要在项目的启动类,或者配置类上,使用@EnableAsync注解。


例如:

@EnableAsync@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})public class Application {
public static void main(String[] args) { SpringApplication.run(Application.class, args); }}
复制代码


@EnableAsync注解相当于一个开关,控制是否开启@Async注解异步的功能,默认是关闭的。


如果在项目的启动类上未使用 @EnableAsync 注解,则 @Async 注解异步的功能不生效。


2 内部方法调用


我们在日常开发中,经常需要在一个方法中调用另外一个方法,例如:

@Slf4j@Servicepublic class UserService {
public void test() { async("test"); }
@Async public void async(String value) { log.info("async:{}", value); }}
复制代码


这个示例中,在 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 类中的某个方法时,有时把权限修饰符定义错了,例如:

@Slf4j@Servicepublic class UserService {
@Async private void async(String value) { log.info("async:{}", value); }}
复制代码


这个例子中将 UserService 类的 async()方法的权限修饰符定义成了 private 的,这样 @Async 注解也会失效。

因为 private 修饰的方法,只能在 UserService 类的对象中使用。


而 @Async 注解的异步功能,需要使用 Spring 的 AOP 生成 UserService 类的代理对象,该代理对象没法访问 UserService 类的 private 方法,因此会出现 @Async 注解失效的问题。


4 方法返回值错误


我们在写一个新的方法时,经常需要定义方法的返回值。


返回值可以是 void、int、String、User 等等,但如果返回值定义错误,也可能会导致 @Async 注解的异步功能失效。


例如:

@Servicepublic class UserService {
@Async public String async(String value) { log.info("async:{}", value); return value; }}
复制代码


UserService 类的 async 方法的返回值是 String,这种情况竟然会导致 @Async 注解的异步功能失效。


在 AsyncExecutionInterceptor 类的 invoke()方法,会调用它的父类 AsyncExecutionAspectSupport 中的 doSubmit 方法,该方法时异步功能的核心代码,如下:



从图中看出,@Async 注解的异步方法的返回值,要么是 Future,要么是 null。


因此,在实际项目中,如果想要使用 @Async 注解的异步功能,相关方法的返回值必须是void或者Future


5 方法用 static 修饰了


有时候,我们的方法会使用 static 修饰,这样在调用的地方,可以直接使用类名.方法名,访问该方法了。

但如果在 @Async 方法上加了 static 修饰符,例如:

@Slf4j@Servicepublic class UserService {
@Async public static void async(String value) { log.info("async:{}", value); }}
复制代码


这时 @Async 的异步功能会失效,因为这种情况 idea 会直接报错:Methods annotated with '@Async' must be overridable 。


使用 @Async 注解声明的方法,必须是能被重写的,很显然 static 修饰的方法,是类的静态方法,是不允许被重写的。


因此这种情况下,@Async 注解的异步功能会失效。


6 方法用 final 修饰


在 Java 种 final 关键字,是一个非常特别的存在。


用 final 修饰的类,没法被继承。


用 final 修饰的方法,没法被重写。


用 final 修饰的变量,没法被修改。


如果 final 使用不当,也会导致 @Async 注解的异步功能失效,例如:

@Slf4j@Servicepublic class UserService {
public void test() { async("test"); }
@Async public final void async(String value) { log.info("async:{}", value); }}
复制代码


这种情况下 idea 也会直接报错:Methods annotated with '@Async' must be overridable 。


因为使用 final 关键字修饰的方法,是没法被子类重写的。


因此这种情况下,@Async 注解的异步功能会失效。


7 业务类没加 @Service 注解


有时候,我们在新加 Service 类时,会忘了加@Service注解,例如:

@Slf4j//@Servicepublic class UserService {
@Async public void async(String value) { log.info("async:{}", value); }}
@Servicepublic class TestService {
@Autowired private UserService userService;
public void test() { userService.async("test"); }}
复制代码


这种情况下,@Async 注解异步的功能也不会生效。因为 UserService 类没有使用 @Service、@Component 或者 @Controller 等注解声明,该类不会被 Spring 管理,因此也就无法使用 Spring 的异步功能。


8 自己 new 的对象


在项目中,我们经常需要 new 一个对象,然后对他赋值,或者调用它的方法。


但如果 new 了一个 Service 类的对象,可能会出现一些意想不到的问题,例如:

@Slf4j@Servicepublic class UserService {
@Async public void async(String value) { log.info("async:{}", value); }}
@Servicepublic class TestService {
public void test() { UserService userService = new UserService(); userService.async("test"); }}
复制代码


在 TestService 类的 test()方法中,new 了一个 UserService 类的对象,然后调用该对象的 async()方法。


很显然这种情况下,async()方法只能同步执行,没法异步执行。


因为在项目中,我们自己 new 的对象,不会被 Spring 管理,因此也就无法使用 Spring 的异步功能。


不过我们可以通过BeanPostProcessor类,将创建的对象手动注入到 Spring 容器中。


9 Spring 无法扫描异步类


我们在 Spring 项目中可以使用@ComponentScan注解指定项目中扫描的包路径,例如:

@ComponentScan({"com.susan.demo.service1"})@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})public class Application {
public static void main(String[] args) { SpringApplication.run(Application.class, args); }}
复制代码


项目中 com.susan.demo.service1 这个路径是不存在的,会导致 @Async 注解异步的功能失效。


同时如果 @ComponentScan 注解定义的路径,没有包含你新加的 Servcie 类的路径,@Async 注解异步的功能也会失效。


文章转载自:苏三说技术

原文链接:https://www.cnblogs.com/12lisu/p/18176502

体验地址:http://www.jnpfsoft.com/?from=infoq

用户头像

还未添加个人签名 2023-06-19 加入

还未添加个人简介

评论

发布
暂无评论
@Async异步失效的9种场景_Java_不在线第一只蜗牛_InfoQ写作社区