一、问题背景
1.1 自己遇到的问题
自己最近遇到的一个问题,SyncInfoTask是一个通过@Scheduled控制的一个定时任务,里面只有execute()这一个方法
在外部通过一个手动触发的方法来主动执行这个任务
public String userInfo() {
SyncInfoTask task = SpringUtils.getBean(SyncInfoTask.class);
task.execute();
return "OK";
}
突然有一天我再execute()这个方法上加了@Async这个注解之后,突然 userInfo()这个方法就报错了。在调用SpringUtils.getBean(SyncInfoTask.class);的时候报
org.springframework.beans.factory.NoSuchBeanDefinitionException:
No qualifying bean of type 'com.bitmart.agency.task.user.SyncInfoTask' available
意思也就是说根据SyncInfoTask.Class找不到这个bean。问题是之前是可用的。于是我就尝试换了一下根据bean的名称来调用SpringUtils.getBean(“syncInfoTask”),发现又可以了!于是这个问题引发了我的思考
二、问题排查
2.1 思考过程
首先我心想,根据class获取不到这个bean,那肯定是这个bean的类型已经不是SyncInfoTask.class的了。进一步排查发现@Async原理是spring的AOP。而AOP是我们熟知的概念,利用的是动态代理生成一个代理对象,因此此刻要解决这个问题,可以通过以下几个办法:
- 一是上面我提到过的,使用SpringUtils.getBean(String name)方法可以获得。毕竟这个bean肯定是在的
- 二是通过某种方法去获取某个bean的代理对象bean
当然是这两点都不是这篇的重点,重点是为什么会出现这种问题。
2.2 Spring中AOP的动态代理
AOP其实是一个老生常谈的八股文话题了。我们都知道有两种:JDK的动态代理以及cglib动态代理。
这两者主要区别有以下几个:
a、JDK动态代理
- 基于接口实现。也就是当前bean需要实现接口
- 利用反射技术生成匿名的代理类走 InvokeHandler回调方法实现增强
b、Cglib动态代理
- 利用 asm字节码技术生成一个子类 覆盖其中的方法实现增强
因此可以看出来,使用cglib动态代理生成的代理类,可以通过SpringUtils.getBean(Class class)获取。
因此之前被八股文的时候,记住了这么一句话:如果实现了接口则采用的 Jdk动态代理。没有实现接口就用cglib动态代理。乍一看,貌似这句话确实能解释现在这个问题,因为上面的这个类确实实现了接口,可以看做是被jdk代理了,因为不能根据class与获取bean
2.3 反常的一个思考
但是突然想起一个问题,我们平常写service类的时候,不都习惯于抽象一个接口,然后通过xxxServiceImpl来实现逻辑吗,我们平常通过SpringUtils.getBean(Class class)方式来获取service的bean很明显是可以的。可以看出平常的service,即便实现了接口,依然是通过cglib代理的,而不是jdk。所以这到底是怎么一回事呢?
继续查询,发现了结论:
2.3.1 针对Spring:Spring 中的动态代理,具体用哪种,分情况:
- 如果代理对象有接口,就用 JDK 动态代理,否则就是 Cglib 动态代理。
- 如果代理对象没有接口,那么就直接是 Cglib 动态代理。
可以看出,跟我们上面说的是一样的。Spring动态代理的策略是能用jdk就用jdk,不能则用cglib
然而,对于SpringBoot,却有点不同:
2.3.2 Spring Boot2.0 之前
@Configuration
@ConditionalOnClass({ EnableAspectJAutoProxy.class, Aspect.class, Advice.class })
@ConditionalOnProperty(prefix = "spring.aop", name = "auto", havingValue = "true", matchIfMissing = true)
public class AopAutoConfiguration {
@Configuration
@EnableAspectJAutoProxy(proxyTargetClass = false)
@ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "false",
matchIfMissing = true)
public static class JdkDynamicAutoProxyConfiguration {
}
@Configuration
@EnableAspectJAutoProxy(proxyTargetClass = true)
@ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "true",
matchIfMissing = false)
public static class CglibAutoProxyConfiguration {
}
}
可以得出结论:在application.properties文件中
- 如果设置了 spring.aop.proxy-target-class 为 false,则使用 JDK 代理。
- 如果开发者设置了 spring.aop.proxy-target-class 为 true,则使用 Cglib 代理
- 如果开发者一开始就没配置 spring.aop.proxy-target-class 属性,则使用 JDK 代理
可以看出来,默认是使用JDK代理
2.3.3 Spring Boot2.0 之后
@Configuration
@ConditionalOnClass({ EnableAspectJAutoProxy.class, Aspect.class, Advice.class,
AnnotatedElement.class })
@ConditionalOnProperty(prefix = "spring.aop", name = "auto", havingValue = "true", matchIfMissing = true)
public class AopAutoConfiguration {
@Configuration
@EnableAspectJAutoProxy(proxyTargetClass = false)
@ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "false", matchIfMissing = false)
public static class JdkDynamicAutoProxyConfiguration {
}
@Configuration
@EnableAspectJAutoProxy(proxyTargetClass = true)
@ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "true", matchIfMissing = true)
public static class CglibAutoProxyConfiguration {
}
}
可以看出,新版本最大的区别在于:
- 如果没有设置,则默认使用Cglib动态代理
现在,结论已经有了。我现在使用版本的springboot,默认是用cglib动态代理
2.4 矛盾点的地方
既然现在使用的springboot,默认是用cglib动态代理,为什么还是不能SpringUtils.getBean(Class class)获取bean呢,cglib代理都已经是生成子类了。这不是矛盾了吗?
最后,终于发现了问题:
原来,@Async默认值是false。也就是说这个注解,独立的片不用cglib代理。原因找到!
2.5 最后的解决方法
在主类上,添加这一行:
@EnableAsync(proxyTargetClass = true)
这样的话,@Async也会被强制使用cglib代理了,问题解决!