从@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()获取当前代理对象了。

相关文章
|
2月前
|
Java Spring
聊聊你对SpringBoot框架的理解 ?
SpringBoot是Spring家族中流行的子项目,旨在简化Spring框架开发的繁琐配置。它主要提供三大功能:starter起步依赖简化依赖管理,自动配置根据条件创建Bean,以及内嵌Web服务器支持Jar包运行,极大提升了开发效率。
121 0
|
2月前
|
NoSQL Java 数据库连接
SpringBoot框架
Spring Boot 是 Spring 家族中最流行的框架,旨在简化 Spring 应用的初始搭建与开发。它通过自动配置、起步依赖和内嵌服务器三大核心功能,大幅减少配置复杂度,提升开发效率。开发者可快速构建独立运行的 Web 应用,并支持多种数据访问技术和第三方集成。
|
3月前
|
Java API 网络架构
基于 Spring Boot 框架开发 REST API 接口实践指南
本文详解基于Spring Boot 3.x构建REST API的完整开发流程,涵盖环境搭建、领域建模、响应式编程、安全控制、容器化部署及性能优化等关键环节,助力开发者打造高效稳定的后端服务。
443 1
|
2月前
|
缓存 安全 Java
Spring 框架核心原理与实践解析
本文详解 Spring 框架核心知识,包括 IOC(容器管理对象)与 DI(容器注入依赖),以及通过注解(如 @Service、@Autowired)声明 Bean 和注入依赖的方式。阐述了 Bean 的线程安全(默认单例可能有安全问题,需业务避免共享状态或设为 prototype)、作用域(@Scope 注解,常用 singleton、prototype 等)及完整生命周期(实例化、依赖注入、初始化、销毁等步骤)。 解析了循环依赖的解决机制(三级缓存)、AOP 的概念(公共逻辑抽为切面)、底层动态代理(JDK 与 Cglib 的区别)及项目应用(如日志记录)。介绍了事务的实现(基于 AOP
111 0
|
2月前
|
存储 缓存 NoSQL
Spring Cache缓存框架
Spring Cache是Spring体系下的标准化缓存框架,支持多种缓存(如Redis、EhCache、Caffeine),可独立或组合使用。其优势包括平滑迁移、注解与编程两种使用方式,以及高度解耦和灵活管理。通过动态代理实现缓存操作,适用于不同业务场景。
278 0
|
2月前
|
消息中间件 NoSQL Java
SpringBoot框架常见的starter你都用过哪些 ?
本节介绍常见的Spring Boot Starter,分为官方(如Web、AOP、Redis等)与第三方(如MyBatis、MyBatis Plus)两类,用于快速集成Web开发、数据库、消息队列等功能。
202 0
|
2月前
|
缓存 安全 Java
第五章 Spring框架
第五章 Spring框架
|
2月前
|
缓存 Java 数据库
第五章 Spring框架
第五章 Spring框架
|
2月前
|
SQL XML Java
配置Spring框架以连接SQL Server数据库
最后,需要集成Spring配置到应用中,这通常在 `main`方法或者Spring Boot的应用配置类中通过加载XML配置或使用注解来实现。
237 0