作者:光闪
my.oschina.net/guangshan/blog/1807721
my.oschina.net/guangshan/blog/1807721
推荐:
前言
在我们使用Spring时,可能有前辈教导过我们,在bean中不要使用this来调用被@Async、@Transactional、@Cacheable等注解标注的方法,this下注解是不生效的。
那么大家可曾想过以下问题
通过本文,上面的疑问都可以解决,而且可以学到很多相关原理知识,信息量较大,那么就开始吧
现象
以@Async注解为例,@Async注解标记的方法,在执行时会被AOP处理为异步调用,调用此方法处直接返回,@Async标注的方法使用其他线程执行。
使用Spring Boot驱动
@SpringBootApplication
@EnableAsync
public class Starter {
public static void main(String[] args) {
SpringApplication.run(Starter.class, args);
}
}
@Component
public class AsyncService {
public void async1() {
System.out.println("1:" + Thread.currentThread().getName());
this.async2();
}
@Async
public void async2() {
System.out.println("2:" + Thread.currentThread().getName());
}
}
@RunWith(SpringRunner.class)
@SpringBootTest(classes = Starter.class)
public class BaseTest {
@Autowired
AsyncService asyncService;
@Test
public void testAsync() {
asyncService.async1();
asyncService.async2();
}
}
输出内容为:
1:main 2:main 2:SimpleAsyncTaskExecutor-2
第一行第二行对应async1()方法,第三行对应async2()方法,可以看到直接使用asyncService.async2()调用时使用的线程为SimpleAsyncTaskExecutor,而在async1()方法中使用this调用,结果却是主线程,原调用线程一致。这说明@Async在this调用时没有生效。
思考&猜测
已知对于AOP动态代理,非接口的类使用的是基于CGLIB的动态代理,而CGLIB的动态代理,是基于现有类创建一个子类,并实例化子类对象。在调用动态代理对象方法时,都是先调用子类方法,子类方法中使用方法增强Advice或者拦截器MethodInterceptor处理子类方法调用后,选择性的决定是否执行父类方法。
那么假设在调用async1方法时,使用的是动态生成的子类的实例,那么this其实是基于动态代理的子类实例对象,this调用是可以被Advice或者MethodInterceptor等处理逻辑拦截的,那么为何理论和实际不同呢?
这里大胆推测一下,其实async1方法中的this不是动态代理的子类对象,而是原始的对象,故this调用无法通过动态代理来增强。
关于上面AOP动态代理使用CGLIB相关的只是,可以参考:https://my.oschina.net/guangshan/blog/1797461
下面开始详细分析。
源码调试分析原理
首先要弄清楚@Async是如何生效的:
1.分析Async相关组件
从生效入口开始看,@EnableAsync注解上标注了@Import(AsyncConfigurationSelector.class)
@Import的作用是把后面的@Configuration类、ImportSelector类或者ImportBeanDefinitionRegistrar类中import的内容自动注册到ApplicationContext中。关于这三种可Import的类,这里先不详细说明,有兴趣的读者可以自行去Spring官网查看文档或者等待我的后续文章。
这里导入了AsyncConfigurationSelector,而AsyncConfigurationSelector在默认情况下,会选择出来ProxyAsyncConfiguration类进行导入,即把ProxyAsyncConfiguration类作为@Configuration类配置到ApplicationContext中。那么这里的关键就是ProxyAsyncConfiguration类,看代码
@Configuration
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public class ProxyAsyncConfiguration extends AbstractAsyncConfiguration {
@Bean(name = TaskManagementConfigUtils.ASYNC_ANNOTATION_PROCESSOR_BEAN_NAME)
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public AsyncAnnotationBeanPostProcessor asyncAdvisor() {
Assert.notNull(this.enableAsync, "@EnableAsync annotation metadata was not injected");
AsyncAnnotationBeanPostProcessor bpp = new AsyncAnnotationBeanPostProcessor();
Class? extends Annotation customAsyncAnnotation = this.enableAsync.getClass("annotation");
if (customAsyncAnnotation != AnnotationUtils.getDefaultValue(EnableAsync.class, "annotation")) {
bpp.setAsyncAnnotationType(customAsyncAnnotation);
}
if (this.executor != null) {
bpp.setExecutor(this.executor);
}
if (this.exceptionHandler != null) {
bpp.setExceptionHandler(this.exceptionHandler);
}
bpp.setProxyTargetClass(this.enableAsync.getBoolean("proxyTargetClass"));
bpp.setOrder(this.enableAsync.IntegergetNumber("order"));
return bpp;
}
}
这段代码的作用是把AsyncAnnotationBeanPostProcessor作为Bean注册到Context中。那么核心就是把AsyncAnnotationBeanPostProcessor这个BeanPostProcessor,也就是Spring大名鼎鼎的BPP。
在一个Bean实例生成后,会交给BPP的postProcessBeforeInitialization方法进行加工,此时可以返回与此Bean相兼容的其他Bean实例,例如最常见的就是在这里返回原对象的动态代理对象。
在这个方法执行后,会调用Bean实例的init相关方法。调用的方法是InitializingBean接口的afterPropertiesSet方法,以及@Bean声明中initMethod指定的初始化方法。
在调用init方法之后,会调用BPP的postProcessAfterInitialization方法进行后置处理。此时处理同postProcessBeforeInitialization,也可以替换原bean的实例。
我们看下这个Async相关的BPP做了什么操作:
// 潜质处理不做任何动作,可保证在调用bean的init之前,bean本身没有任何变化。
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) {
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) {
// 如果是AOP相关的基础组件bean,如ProxyProcessorSupport类及其子类,则直接返回。
if (bean instanceof AopInfrastructureBean) {
// Ignore AOP infrastructure such as scoped proxies.
return bean;
}
if (bean instanceof Advised) {
// 如果已经是Advised的,即已经是被动态代理的实例,则直接添加advisor。
Advised advised = (Advised) bean;
if (!advised.isFrozen() && isEligible(AopUtils.getTargetClass(bean))) {
// 如果没有被frozen(即冷冻,不再做改动的动态代理实例)且是Eligbile(合适的),则把其添加到advisor中。根据配置决定插入位置。
// Add our local Advisor to the existing proxy's Advisor chain...
if (this.beforeExistingAdvisors) {
advised.addAdvisor(0, this.advisor);
}
else {
advised.addAdvisor(this.advisor);
}
return bean;
}
}
if (isEligible(bean, beanName)) {
// 如果是Eligible合适的,且还不是被代理的类,则创建一个代理类的实例并返回。
ProxyFactory proxyFactory = prepareProxyFactory(bean, beanName);
if (!proxyFactory.isProxyTargetClass()) {
evaluateProxyInterfaces(bean.getClass(), proxyFactory);
}
proxyFactory.addAdvisor(this.advisor);
customizeProxyFactory(proxyFactory);
return proxyFactory.getProxy(getProxyClassLoader());
}
// No async proxy needed.
return bean;
}
// 准备ProxyFactory对象
protected ProxyFactory prepareProxyFactory(Object bean, String beanName) {
ProxyFactory proxyFactory = new ProxyFactory();
proxyFactory.copyFrom(this);
// 设置被代理的bean为target,这个bean是真实的bean。
proxyFactory.setTarget(bean);
return proxyFactory;
}
Spring在对一个类进行AOP代理后,会为此类加上Advised接口,返回的动态代理对象都会带上Advised接口修饰,那么第一段逻辑判断bean instanceof Advised的目的就是判断是否已经是被动态代理的类,如果是,则为其添加一个Advisor增强器。
如果不是动态代理的对象,因为@Async要为方法增加代理,并转换为异步执行,故需要把原始bean转换为被AOP动态代理的bean。也就是下面的逻辑。
有关上面这一段代码创建动态代理的详细原理,请参考:https://my.oschina.net/guangshan/blog/1797461
关于@Async再多提一点:上面注册进去的advisor类型是AsyncAnnotationAdvisor。其中包括了PointCut,类型是AnnotationMatchingPointcut,指定了只有@Async标记的方法或者类此AOP增强器才生效。还有一个Advice,用于增强@Async标记的方法,转换为异步,类型是AnnotationAsyncExecutionInterceptor,其中的invoke方法是真正调用真实方法的地方,大家有兴趣可以仔细研究其中的内容,这样就能摸清楚@Async方法的真实执行逻辑了。
相关组件上面都已经提及并进行了简单的分析,现在我们进入下一阶段,通过真正的执行逻辑来分析this调用不生效的原因。
2.深入真实调用逻辑
@Async大多数都是标记的类中的方法,故AOP的实现也多是基于CGLIB的,下面以CGLIB动态代理为例分析真实调用逻辑。
通过完全读懂Spring框架之AOP实现原理这篇文章,可以得知,一个基于CGLIB的AOP动态代理bean,真实的执行逻辑是在DynamicAdvisedInterceptor中:
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
Object oldProxy = null;
boolean setProxyContext = false;
Class? targetClass = null;
Object target = null;
try {
if (this.advised.exposeProxy) {
// 需要则暴露
// Make invocation available if necessary.
oldProxy = AopContext.setCurrentProxy(proxy);
setProxyContext = true;
}
// May be null. Get as late as possible to minimize the time we
// "own" the target, in case it comes from a pool...
// 重点:获取被代理的目标对象
target = getTarget();
if (target != null) {
targetClass = target.getClass();
}
// 获取拦截器链
ListObject chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
Object retVal;
// Check whether we only have one InvokerInterceptor: that is,
// no real advice, but just reflective invocation of the target.
if (chain.isEmpty() && Modifier.isPublic(method.getModifiers())) {
// We can skip creating a MethodInvocation: just invoke the target directly.
// Note that the final invoker must be an InvokerInterceptor, so we know
// it does nothing but a reflective operation on the target, and no hot
// swapping or fancy proxying.
// 如果链是空且是public方法,则直接调用
Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);
retVal = methodProxy.invoke(target, argsToUse);
}
else {
// We need to create a method invocation...
// 否则创建一个CglibMethodInvocation以便驱动拦截器链
retVal = new CglibMethodInvocation(proxy, target, method, args, targetClass, chain, methodProxy).proceed();
}
// 处理返回值,同JDK动态代理
retVal = processReturnType(proxy, target, method, retVal);
return retVal;
}
finally {
if (target != null) {
releaseTarget(target);
}
if (setProxyContext) {
// Restore old proxy.
AopContext.setCurrentProxy(oldProxy);
}
}
}
注意上面真实调用的部分,在没有advisor的情况下,使用的其实是:
methodProxy.invoke(target, argsToUse)
在有代理的情况下,使用的是:
new CglibMethodInvocation(proxy, target, method, args, targetClass, chain, methodProxy).proceed();
而在CglibMethodInvocation中,检查到调用链执行完之后,会调用真实的方法:invokeJoinpoint。在CglibMethodInvocation中,该方法的实现是
// CglibMethodInvocation中的实现
protected Object invokeJoinpoint() throws Throwable {
if (this.publicMethod) {
return this.methodProxy.invoke(this.target, this.arguments);
}
else {
return super.invokeJoinpoint();
}
}
// 父类实现是
protected Object invokeJoinpoint() throws Throwable {
return AopUtils.invokeJoinpointUsingReflection(this.target, this.method, this.arguments);
}
可以看到调用方法时,传入的实例都是target,这个target是从DynamicAdvisedInterceptor的getTarget方法中获得的,代码如下
protected Object getTarget() throws Exception {
return this.advised.getTargetSource().getTarget();
}
而这个advised的target则是在ProxyFactory的实例方法中设置的:proxyFactory.setTarget(bean);
也就是说这个target其实是真实的被代理的bean。
通过上面的分析,我们可以得到结论,在一个被动态代理的对象,在执行完AOP所有的增强逻辑之后,最终都会使用被代理对象作为实例调用真实的方法,即相当于调用了:target.method()方法。由此得出结论,在target.method()方法中,this引用必然是target自身,而不是生成的动态代理对象实例。
补充一下,Spring在创建一个Bean之后,对其包装并生成动态代理对象都是后置的举动,故会先生成真实类的实例bean,再动态创建动态代理bean,在动态代理bean中,会持有真实的bean的实例。
就拿最上面的@Async代码实例举例,我们可以看到this其实是AsyncService的原始实例,而不是代理对象
实例:
总结:
因为AOP动态代理的方法真实调用,会使用真实被代理对象实例进行方法调用,故在实例方法中通过this获取的都是被代理的真实对象的实例,而不是代理对象自身。
3.解决this调用的几个替代方法
既然已知原因,那么解决的方法就有定向了,核心就是如何获得动态代理对象,而不是使用this去调用。
提供以下几种方法:
1.通过ApplicationContext来获得动态代理对象
@Component
public class AsyncService implements ApplicationContextAware {
private ApplicationContext applicationContext;
public void async1() {
System.out.println("1:" + Thread.currentThread().getName());
// 使用AppicationContext来获得动态代理的bean
this.applicationContext.getBean(AsyncService.class).async2();
}
@Async
public void async2() {
System.out.println("2:" + Thread.currentThread().getName());
}
// 注入ApplicationContext
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
}
执行结果是:
1:main 2:SimpleAsyncTaskExecutor-2 2:SimpleAsyncTaskExecutor-3
可以看到完美达到了我们的目的。同理是用BeanFactoryAware可达到同样的效果。
未完…..
点击图片加入Spring交流群
↓↓↓
看完本文有收获?请转发分享给更多人