从@Async案例找到Spring框架的bug:exposeProxy=true不生效原因大剖析+最佳解决方案【享学Spring】(中)

简介: 从@Async案例找到Spring框架的bug:exposeProxy=true不生效原因大剖析+最佳解决方案【享学Spring】(中)

示例六


偷懒做法:直接在实现类里写个方法(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()获取当前代理对象了。

相关文章
|
20天前
|
缓存 Java 开发工具
Spring是如何解决循环依赖的?从底层源码入手,详细解读Spring框架的三级缓存
三级缓存是Spring框架里,一个经典的技术点,它很好地解决了循环依赖的问题,也是很多面试中会被问到的问题,本文从源码入手,详细剖析Spring三级缓存的来龙去脉。
Spring是如何解决循环依赖的?从底层源码入手,详细解读Spring框架的三级缓存
|
10天前
|
人工智能 开发框架 Java
重磅发布!AI 驱动的 Java 开发框架:Spring AI Alibaba
随着生成式 AI 的快速发展,基于 AI 开发框架构建 AI 应用的诉求迅速增长,涌现出了包括 LangChain、LlamaIndex 等开发框架,但大部分框架只提供了 Python 语言的实现。但这些开发框架对于国内习惯了 Spring 开发范式的 Java 开发者而言,并非十分友好和丝滑。因此,我们基于 Spring AI 发布并快速演进 Spring AI Alibaba,通过提供一种方便的 API 抽象,帮助 Java 开发者简化 AI 应用的开发。同时,提供了完整的开源配套,包括可观测、网关、消息队列、配置中心等。
529 6
|
7天前
|
Java 开发工具 对象存储
简化配置管理:Spring Cloud Config与Netflix OSS中的动态配置解决方案
简化配置管理:Spring Cloud Config与Netflix OSS中的动态配置解决方案
18 2
|
8天前
|
前端开发 Java Spring
【非降版本解决】高版本Spring boot Swagger 报错解决方案
【非降版本解决】高版本Spring boot Swagger 报错解决方案
|
7天前
|
XML 前端开发 Java
控制spring框架注解介绍
控制spring框架注解介绍
|
7天前
|
存储 NoSQL Java
Spring Session框架
Spring Session 是一个用于在分布式环境中管理会话的框架,旨在解决传统基于 Servlet 容器的会话管理在集群和云环境中的局限性。它通过将用户会话数据存储在外部介质(如数据库或 Redis)中,实现了会话数据的跨服务器共享,提高了应用的可扩展性和性能。Spring Session 提供了无缝集成 Spring 框架的 API,支持会话过期策略、并发控制等功能,使开发者能够轻松实现高可用的会话管理。
Spring Session框架
|
15天前
|
Java 应用服务中间件 开发者
深入探索并实践Spring Boot框架
深入探索并实践Spring Boot框架
25 2
|
15天前
|
机器学习/深度学习 数据采集 JavaScript
ADR智能监测系统源码,系统采用Java开发,基于SpringBoot框架,前端使用Vue,可自动预警药品不良反应
ADR药品不良反应监测系统是一款智能化工具,用于监测和分析药品不良反应。该系统通过收集和分析病历、处方及实验室数据,快速识别潜在不良反应,提升用药安全性。系统采用Java开发,基于SpringBoot框架,前端使用Vue,具备数据采集、清洗、分析等功能模块,并能生成监测报告辅助医务人员决策。通过集成多种数据源并运用机器学习算法,系统可自动预警药品不良反应,有效减少药害事故,保障公众健康。
ADR智能监测系统源码,系统采用Java开发,基于SpringBoot框架,前端使用Vue,可自动预警药品不良反应
|
5月前
|
安全 Java 调度
Spring中的多线程魔法:探索@Async注解的妙用
Spring中的多线程魔法:探索@Async注解的妙用
72 0
|
Java 开发者 Spring
spring中@Async注解
@Async注解是spring中用来标注此方法是通过另外一个线程异步调用的,调用者将在调用时立即返回,方法的实际执行将提交给Spring TaskExecutor的任务中,由指定的线程池中的线程执行。
938 0
下一篇
无影云桌面