使用思路
异步场景及优势在此不多赘述。异步思路无非是在原本请求链路执行到某个环节时,将部分无需同步执行的操作交由主线程以外的其它线程执行。因此针对标题中两个注解的使用,我们常常会将异步执行内容单独封装一个方法并通过@Async修饰
,然后在启动类/配置类(在异步方法所在类等其它地方也行,但规范要求是写在这两处)开启@EnableAsync
@SpringBootApplication
@EnableAsync
@ComponentScan(value = {"com.ivan.*"})
public class Main {
public static void main(String[] args) {
SpringApplication.run(Main.class, args);
}
}
public interface DemoService {
String testOne() throws InterruptedException;
String testTwo() throws InterruptedException;
String testThree();
}
@RestController
@RequestMapping("/test")
public class DemoServiceImpl implements DemoService {
@Autowired
ApplicationContext applicationContext;
@Resource
@Lazy
private DemoServiceImpl demoServiceImpl;
@Override
@GetMapping("/test_one")
public String testOne() throws InterruptedException {
//获取HelloService代理对象
DemoServiceImpl bean = applicationContext.getBean(DemoServiceImpl.class);
System.out.println("当前对象是否是代理对象:" + AopUtils.isAopProxy(bean));
System.out.println("是否是cglib代理对象:" + AopUtils.isCglibProxy(bean));
System.out.println("是否是jdk代理对象:" + AopUtils.isJdkDynamicProxy(bean));
System.out.println(bean == this);
//调用代理对象的异步方法
bean.asyncMethod();
return LocalDateTime.now().toString();
}
@Override
@GetMapping("/test_two")
public String testTwo() throws InterruptedException {
// asyncMethod();
System.out.println("当前对象是否是代理对象:" + AopUtils.isAopProxy(demoServiceImpl));
System.out.println("是否是cglib代理对象:" + AopUtils.isCglibProxy(demoServiceImpl));
System.out.println("是否是jdk代理对象:" + AopUtils.isJdkDynamicProxy(demoServiceImpl));
demoServiceImpl.asyncMethod();
return LocalDateTime.now().toString();
}
@Override
@GetMapping("/test_three")
public String testThree() {
new Thread(() -> {
try {
Thread.sleep(10000);
System.out.println("睡眠结束");
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}).start();
return LocalDateTime.now().toString();
}
@Async
//不能声明为private
//返回值只能为void或者Future
public void asyncMethod() throws InterruptedException {
Thread.sleep(10000);
System.out.println("睡眠结束");
}
}
基本使用规范
- @Async标注的异步方法,返回值只能为void或者Future,且方法不能声明为private,否则注解会失效。当其它线程调用这个方法时,就会开启一个新的子线程去异步处理该业务逻辑。
切记:异步方法返回值为Future时,在调用方法的同时不要直接
.get()
,否则虽然开启了额外的线程,但主方法其实也堵塞在get()这行代码了,相当于就还是同步方法了。应当先调用异步方法返回Future<Object>
在通过Future对象.get()获取结果数据 - 使用此注解的方法的类对象,必须是spring管理下的bean对象 (如被@Service、@Component等修饰的Bean对象)
- @Async注解还可用于修饰类,表示该类中的所有方法都是异步的
- 在Spring项目中需要进行异步编程时,一定要开启异步。在启动类/配置类添加@EnableAsync
常见问题——@Async无效
- 最常见情况——忘记加@EnableAsync注解
- @Async标注的异步方法声明为private
- 没有走Spring的代理类。(即Service里面方法A调用方法B,会不生效!!) 方法一定要从另一个类中调用,也就是从类的外部调用,类的内部调用是无效的,
需要先获取其代理类,通过代理类调用异步方法
——异步方法所在类使用了异步注解@Async 但是没有实现接口的时候,代码底层走 Cglib 动态代理,DemoServiceImpl控制类注入到 SpringMVC 容器中。Cglib 动态代理生成的代理对象是采用继承目标对象(DemoServiceImpl)的模式生成代理类的,而目标对象(DemoServiceImpl)中有@RestController 注解,所以注解会被继承过来,这样Cglib 生成的代理对象就可以注入到SpringMVC 容器中。因为@Async注解方法与控制类在同一个类中导致没有走代理类,所以@Async 注解失效。解决方法一:新建一个类,将@Async标注的异步方法放入该类
解决方法二:从spring上下文中取得代理对象DemoServiceImpl bean = applicationContext.getBean(DemoServiceImpl.class);
解决方法三: 在同一个类内部使用self-invocation的方式来调用被@Async修饰的方法
@Resource
@Lazy
private DemoServiceImpl demoServiceImpl;
常见问题——添加@EnableAsync注解后接口404
异步方法所在类实现了接口且使用了异步注解@Async,代码底层会走 JDK 动态代理,导致 OrderServiceImpl 控制类没有注入到 SpringMVC 容器中。因为 JDK 动态代理技术是基于接口实现的,而接口中没有@RestController注解,所以导致代理对象无法注册到SpringMVC容器中。DemoServiceImpl做为代理类实现 DemoService接口,代理类(DemoServiceImpl)发现 DemoService接口上面没有 RestController 注解,所以导致了代理类(DemoServiceImpl)不会注入到SpringMVC容器中。
- @Async标注的异步方法所在类实现了接口并重写了方法且@EnableAsync注解没有手动注明属性proxyTargetClass = true
解决方法:
@EnableAsync(proxyTargetClass = true)
Spring源码:
/** 初始化 bean 对象
* Detects handler methods at initialization.
* @see #initHandlerMethods
*/
@Override
public void afterPropertiesSet() {
initHandlerMethods();
}
/**
* Scan beans in the ApplicationContext, detect and register handler methods.
* @see #getCandidateBeanNames() 获取到所有的 bean 对象
* @see #processCandidateBean
* @see #handlerMethodsInitialized
*/
protected void initHandlerMethods() {
for (String beanName : getCandidateBeanNames()) {
if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {
processCandidateBean(beanName);
}
}
handlerMethodsInitialized(getHandlerMethods());
}
/**
* Determine the type of the specified candidate bean and call
* {@link #detectHandlerMethods} if identified as a handler type.
* <p>This implementation avoids bean creation through checking
* {@link org.springframework.beans.factory.BeanFactory#getType}
* and calling {@link #detectHandlerMethods} with the bean name.
* @param beanName the name of the candidate bean
* @since 5.1
* @see #isHandler
* @see #detectHandlerMethods
*/
protected void processCandidateBean(String beanName) {
Class<?> beanType = null;
try {
beanType = obtainApplicationContext().getType(beanName);
}
catch (Throwable ex) {
// An unresolvable bean type, probably from a lazy bean - let's ignore it.
if (logger.isTraceEnabled()) {
logger.trace("Could not resolve type for bean '" + beanName + "'", ex);
}
}
if (beanType != null && isHandler(beanType)) {
detectHandlerMethods(beanName);
}
}
/**
* {@inheritDoc} 判断类上面是否有 Controller、RequestMapping 注解。
* <p>Expects a handler to have either a type-level @{@link Controller}
* annotation or a type-level @{@link RequestMapping} annotation.
*/
@Override
protected boolean isHandler(Class<?> beanType) {
return (AnnotatedElementUtils.hasAnnotation(beanType, Controller.class) ||
AnnotatedElementUtils.hasAnnotation(beanType, RequestMapping.class));
}
参考文章
【异步任务】@Async注解使用方法及注解失效解决办法
SpringBoot中@EnableAsync和@Async注解的使用
@Async 注解失效解析