示例六
偷懒做法:直接在实现类里写个方法(public/private)然后注解上@Async
我发现我司同事有大量这样的写法,所以专门拿出作为示例,以儆效尤~
@Service public class B implements BInterface { ... @Async public void fun2(){ System.out.println("线程名称:" + Thread.currentThread().getName()); } }
结论:因为方法不在接口上,因此肯定无法通过获取代理对象调用它。
需要注意的是:即使该方法不属于接口方法,但是标注了@Async所以最终生成的还是B的代理对象~(哪怕是private访问权限也是代理对象)
可能有的小伙伴会想通过context.getBean()获取到具体实现类再调用方法行不行。咋一想可行,实际则不是不行的。
这里再次强调一次,若你是AOP是JDK的动态代理的实现,这样100%报错的:
BInterface bInterface = applicationContext.getBean(BInterface.class); // 正常获取到容器里的代理对象 applicationContext.getBean(B.class); //报错 NoSuchBeanDefinitionException // 原因此处不再解释了,若是CGLIB代理,两种获取方式均可~
备注:虽说CGLIB代理方式用实现类方式可以获取到代理的Bean,但是强烈不建议依赖于代理的具体实现而书写代码,这样移植性会非常差的,而且接手的人肯定也会一脸懵逼、二脸懵逼…
因此当你看到你同事就在本类写个方法标注上@Async然后调用,请制止他吧,做的无用功~~~(关键自己还以为有用,这是最可怕的深坑~)
原因大剖析
找错的常用方法:逆推法。
首先我们找到报错的最直接原因:AopContext.currentProxy()这句代码报错的,因此有必要看看AopContext这个工具类:
// @since 13.03.2003 public final class AopContext { private static final ThreadLocal<Object> currentProxy = new NamedThreadLocal<>("Current AOP proxy"); private AopContext() { } // 该方法是public static方法,说明可以被任意类进行调用 public static Object currentProxy() throws IllegalStateException { Object proxy = currentProxy.get(); // 它抛出异常的原因是当前线程并没有绑定对象 // 而给线程版定对象的方法在下面:特别有意思的是它的访问权限是default级别,也就是说只能Spring内部去调用~ if (proxy == null) { throw new IllegalStateException("Cannot find current proxy: Set 'exposeProxy' property on Advised to 'true' to make it available."); } return proxy; } // 它最有意思的地方是它的访问权限是default的,表示只能给Spring内部去调用~ // 调用它的类有CglibAopProxy和JdkDynamicAopProxy @Nullable static Object setCurrentProxy(@Nullable Object proxy) { Object old = currentProxy.get(); if (proxy != null) { currentProxy.set(proxy); } else { currentProxy.remove(); } return old; } }
从此工具源码可知,决定是否抛出所示异常的直接原因就是请求的时候setCurrentProxy()方法是否被调用过。通过寻找发现只有两个类会调用此方法,并且都是Spring内建的类且都是代理类的处理类:CglibAopProxy和JdkDynamicAopProxy
说明:本文所有示例,都基于接口的代理,所以此处只以JdkDynamicAopProxy作为代表进行说明即可
我们知道在执行代理对象的目标方法的时候,都会交给InvocationHandler处理,因此做事情的在invoke()方法里:
final class JdkDynamicAopProxy implements AopProxy, InvocationHandler, Serializable { ... @Override @Nullable public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { ... if (this.advised.exposeProxy) { // Make invocation available if necessary. oldProxy = AopContext.setCurrentProxy(proxy); setProxyContext = true; } ... finally { if (setProxyContext) { // Restore old proxy. AopContext.setCurrentProxy(oldProxy); } } } }
so,最终决定是否会调用set方法是由this.advised.exposeProxy这个值决定的,因此下面我们只需要关心ProxyConfig.exposeProxy这个属性值什么时候被赋值为true的就可以了。
ProxyConfig.exposeProxy这个属性的默认值是false。其实最终调用设置值的是同名方法Advised.setExposeProxy()方法,而且是通过反射调用的
关于Spring AOP以及自动代理创建器的详细,本文将不会作为重点讲解,有需要充电的可以参考:
【小家Spring】面向切面编程Spring AOP创建代理的方式:ProxyFactoryBean、ProxyFactory、AspectJProxyFactory(JDK Proxy和CGLIB)
【小家Spring】Spring AOP的核心类:AbstractAdvisorAutoProxy自动代理创建器深度剖析(AnnotationAwareAspectJAutoProxyCreator)
@EnableAspectJAutoProxy(exposeProxy = true)的作用
此注解它导入了AspectJAutoProxyRegistrar,最终设置此注解的两个属性的方法为:
public abstract class AopConfigUtils { public static void forceAutoProxyCreatorToUseClassProxying(BeanDefinitionRegistry registry) { if (registry.containsBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME)) { BeanDefinition definition = registry.getBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME); definition.getPropertyValues().add("proxyTargetClass", Boolean.TRUE); } } public static void forceAutoProxyCreatorToExposeProxy(BeanDefinitionRegistry registry) { if (registry.containsBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME)) { BeanDefinition definition = registry.getBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME); definition.getPropertyValues().add("exposeProxy", Boolean.TRUE); } } }
看到此注解标注的属性值最终都被设置到了internalAutoProxyCreator身上,也就是进而重要的一道菜:自动代理创建器。
在此各位小伙伴需要先明晰的是:@Async的代理对象并不是由自动代理创建器来创建的,而是由AsyncAnnotationBeanPostProcessor一个单纯的BeanPostProcessor实现的。
示例结论分析
本章节在掌握了一定的理论的基础上,针对上面的各种示例进行结论性分析。
示例一分析
本示例目的是事务,可以参考开启事务的注解@EnableTransactionManagement。该注解向容器注入的是自动代理创建器InfrastructureAdvisorAutoProxyCreator,所以exposeProxy = true对它的代理对象都是生效的,因此可以正常work~
备注:@EnableCaching注入的也是自动代理创建器~so exposeProxy = true对它也是有效的
示例二分析
很显然本例是执行AopContext.currentProxy()这句代码的时候报错了。报错的原因相信我此处不说,小伙伴应该个大概了。
@EnableAsync给容器注入的是AsyncAnnotationBeanPostProcessor,它用于给@Async生成代理,但是它仅仅是个BeanPostProcessor并不属于自动代理创建器,因此exposeProxy = true对它无效。
所以AopContext.setCurrentProxy(proxy);这个set方法肯定就不会执行,so但凡只要业务方法中调用AopContext.currentProxy()方法就铁定抛异常~~
示例三分析
这个示例的结论,相信是很多小伙伴都没有想到的。仅仅只是加入了事务,@Asycn
竟然就能够完美的使用AopContext.currentProxy()
获取当前代理对象了。