Java中@Async异步失效的问题

目录
文章目录隐藏
  1. 1.未使用@EnableAsync 注解
  2. 2.内部方法调用
  3. 3.方法非 public
  4. 4.方法返回值错误
  5. 5.方法用 static 修饰了
  6. 6.方法用 final 修饰
  7. 7.业务类没加@Service 注解
  8. 8.自己 new 的对象
  9. 9.Spring 无法扫描异步类

最近在项目某个方法使用@Async注解,但是该方法还是同步执行了,异步不起作用,到底是什么原因呢?

伪代码如下:

@Slf4j
@Service
public 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
@Service
public 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
@Service
public 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 注解的异步功能失效。

例如:

@Service
public class UserService {

    @Async
    public String async(String value) {
        log.info("async:{}", value);
        return value;
    }
}

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

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

AsyncExecutionInterceptor 类

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

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

5.方法用 static 修饰了

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

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

@Slf4j
@Service
public 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
@Service
public 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
//@Service
public class UserService {

    @Async
    public void async(String value) {
        log.info("async:{}", value);
    }
}

@Service
public 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
@Service
public class UserService {

    @Async
    public void async(String value) {
        log.info("async:{}", value);
    }
}

@Service
public 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 注解异步的功能也会失效。

「点点赞赏,手留余香」

0

给作者打赏,鼓励TA抓紧创作!

微信微信 支付宝支付宝

还没有人赞赏,快来当第一个赞赏的人吧!

声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系maynote@foxmail.com处理
码云笔记 » Java中@Async异步失效的问题

发表回复