开发环境
- SpringBoot 2.1.10.RELEASE
- JDK 1.8
场景
在一个类的方法中,调用同类的异步方法无效,例如以下示例:
package com.nobody.controller;
import java.util.concurrent.TimeUnit;
import org.springframework.scheduling.annotation.Async;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("demo")
public class DemoController {
@GetMapping("test")
public void test() {
asyncTask();
System.out.println("主线程 Thread name:" + Thread.currentThread().getName());
}
@Async
public void asyncTask() {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("异步方法 Thread name:" + Thread.currentThread().getName());
}
}
启动类也添加@EnableAsync
注解
package com.nobody;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableAsync;
@SpringBootApplication
@EnableAsync
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
输出结果:
异步方法 Thread name:http-nio-9563-exec-1
主线程 Thread name:http-nio-9563-exec-1
从结果分析,明显是串行单线程执行。
原因分析
类内部方法调用时,直接进行内部调用,没有走Spring的代理类。Async注解的实现都是基于Spring的AOP,而AOP的实现是基于动态代理模式实现的。调用方法的是对象本身而不是代理对象,没有经过Spring容器。
解决方案
新建一个类,通过注解让Spring容器管理,然后在调用类中注入对象。
package com.nobody.domain;
import java.util.concurrent.TimeUnit;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
@Service
public class AsyncService {
@Async
public void asyncTask() {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("异步方法 Thread name:" + Thread.currentThread().getName());
}
}
package com.nobody.controller;
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 com.nobody.domain.AsyncService;
@RestController
@RequestMapping("demo")
public class DemoController {
@Autowired
private AsyncService asyncService;
@GetMapping("test")
public void test() {
asyncService.asyncTask();
System.out.println("主线程 Thread name:" + Thread.currentThread().getName());
}
}
输出结果:
主线程 Thread name:http-nio-9563-exec-1
异步方法 Thread name:task-1
如果异步方法非要写在同一个类中,可以懒加载注入本类的实例,进行调用,例如:
package com.nobody.controller;
import java.util.concurrent.TimeUnit;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.scheduling.annotation.Async;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("demo")
public class DemoController {
@Autowired
@Lazy // 懒加载注入,不加会报错
private DemoController demoController;
@GetMapping("test")
public void test() {
// 通过DemoController实例调用
demoController.asyncTask();
System.out.println("主线程 Thread name:" + Thread.currentThread().getName());
}
// 方法不能是private类型,不然还是串行单线程执行
@Async
public void asyncTask() {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("异步方法 Thread name:" + Thread.currentThread().getName());
}
}
输出结果:
主线程 Thread name:http-nio-9563-exec-1
异步方法 Thread name:task-1