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

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

前言


本文标题包含有'靓丽'的字眼:Spring框架bug。相信有的小伙伴心里小九九就会说了:又是一篇标题党文章。

鉴于此,此处可以很负责任的对大伙说:本人所有文章绝不哗众取宠,除了干货只剩干货。


相信关注过我的小伙伴都是知道的,我只递送干货,绝不标题党来浪费大家的时间和精力~那无异于谋财害命(说得严重了,不喜勿喷)

关于标题党的好与坏、优与劣,此处我不置可否


本篇文章能让你知道exposeProxy=true真实作用和实际作用范围,从而能够在开发中更精准的使用到它。


背景


这篇文章可定位为是基于上篇文章的续文:

【小家Spring】使用@Async异步注解导致该Bean在循环依赖时启动报BeanCurrentlyInCreationException异常的根本原因分析,以及提供解决方案


本来一切本都那么平静,直到我用了@Async注解,好多问题都接踵而至(上篇文章已经解决大部分了)。在上篇文章中,为了解决@Async同类方法调用问题我提出了两个方向的解决方案:


  1. 自己注入自己,然后再调用接口方法(当然此处的一个变种是使用编程方式形如:AInterface a = applicationContext.getBean(AInterface.class);这样子手动获取也是可行的~~~本文不讨论这种比较直接简单的方式)
  2. 使用AopContext.currentProxy();方式


方案一上篇文章已花笔墨重点分析,毕竟方案一我认为更为重要些。本文分析使用方案二的方式,它涉及到AOP、代理对象的暴露,因此我认为本文的内容对你平时开发的影响是不容小觑,可以重点浏览咯~


我相信绝大多数小伙伴都遇到过这个异常:


 java.lang.IllegalStateException: Cannot find current proxy: Set 'exposeProxy' property on Advised to 'true' to make it available.
  at org.springframework.aop.framework.AopContext.currentProxy(AopContext.java:69)
  at com.fsx.dependency.B.funTemp(B.java:14)
  at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
  at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
  at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
  at java.lang.reflect.Method.invoke(Method.java:498)
  at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:343)
  at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:206)
  at com.sun.proxy.$Proxy44.funTemp(Unknown Source)
  ...

然后当你去靠度娘搜索解决方案时,发现无一例外都教你只需要这么做就成:

@EnableAspectJAutoProxy(exposeProxy = true)


本文我想说的可能又是一个技术敏感性问题,其实绝大多数情况下你按照这么做是可行的,直到你遇到了@Async也需要调用本类方法的时候,你就有点绝望了,然后本文或许会成为了你的救星~


本以为加了exposeProxy = true就能顺风顺水了,但它却出问题了:依旧报如上的异常信息。如果你看到这里也觉得不可思议,那么本文就更能体现它的价值所在~


此问题我个人把它归类为Spring的bug我觉得是无可厚非的,因为它的语义与实际表现出来的结果想悖了,so我把定义为Spring框架的bug。

对使用者来说,标注了exposeProxy = true,理论上就应该能够通过AopContext.currentProxy()拿到代理对象,可惜Spring这里却掉链子了,有点名不副实之感~


示例

本文将以多个示例来模拟不同的使用case,首先从直观的结果上先了解@EnableAspectJAutoProxy(exposeProxy = true)的作用以及它存在的问题。


备注:下面所有示例都建立在@EnableAspectJAutoProxy(exposeProxy = true)已经开启的前提下,形如:

@Configuration
@EnableAspectJAutoProxy(exposeProxy = true) // 暴露当前代理对象到当前线程绑定
public class RootConfig {
}


示例一

此示例大都用于解决事务不生效问题上(同类方法调用引起的事务不生效,关于Spring事务不生效的case,可以参考:【小家java】Spring事务不生效的原因大解读 )。


@Service
public class B implements BInterface {
    @Transactional
    @Override
    public void funTemp() {
        ...
        // 希望调用本类方法  但是它抛出异常,希望也能够回滚事务
        BInterface b = BInterface.class.cast(AopContext.currentProxy());
        System.out.println(b);
        b.funB();
    }
    @Override
    public void funB() {
        // ... 处理业务属于  
        System.out.println(1 / 0);
    }
}


结论:能正常work,事务也会生效~


示例二

同类内方法调用,希望异步执行被调用的方法(希望@Async生效)

@Service
public class B implements BInterface {
    @Override
    public void funTemp() {
        System.out.println("线程名称:" + Thread.currentThread().getName());
        // 希望调用本类方法  但是希望它去异步执行~
        BInterface b = BInterface.class.cast(AopContext.currentProxy());
        System.out.println(b);
        b.funB();
    }
    @Async
    @Override
    public void funB() {
        System.out.println("线程名称:" + Thread.currentThread().getName());
    }
}


结论:执行即报错


java.lang.IllegalStateException: Cannot find current proxy: Set 'exposeProxy' property on Advised to 'true' to make it available.


示例三

同类内方法调用,希望异步执行被调用的方法,并且在入口方法处使用事务

@Service
public class B implements BInterface {
    @Transactional
    @Override
    public void funTemp() {
        System.out.println("线程名称:" + Thread.currentThread().getName());
        // 希望调用本类方法  但是希望它去异步执行~
        BInterface b = BInterface.class.cast(AopContext.currentProxy());
        System.out.println(b);
        b.funB();
    }
    @Async
    @Override
    public void funB() {
        System.out.println("线程名称:" + Thread.currentThread().getName());
    }
}

结论:正常work没有报错,@Async异步生效、事务也生效


示例四


示例三的唯一区别是把事务注解@Transactional标注在被调用的方法处(和@Async同方法):


@Service
public class B implements BInterface {
    @Override
    public void funTemp() {
        System.out.println("线程名称:" + Thread.currentThread().getName());
        // 希望调用本类方法  但是希望它去异步执行~
        BInterface b = BInterface.class.cast(AopContext.currentProxy());
        System.out.println(b);
        b.funB();
    }
    @Transactional
    @Async
    @Override
    public void funB() {
        System.out.println("线程名称:" + Thread.currentThread().getName());
    }
}


结论:同示例三


示例五


@Async标注在入口方法上:

@Service
public class B implements BInterface {
    @Transactional
    @Async
    @Override
    public void funTemp() {
        System.out.println("线程名称:" + Thread.currentThread().getName());
        BInterface b = BInterface.class.cast(AopContext.currentProxy());
        System.out.println(b);
        b.funB();
    }
    @Override
    public void funB() {
        System.out.println("线程名称:" + Thread.currentThread().getName());
    }
}


结论:请求即报错


java.lang.IllegalStateException: Cannot find current proxy: Set 'exposeProxy' property on Advised to 'true' to make it available.
  at org.springframework.aop.framework.AopContext.currentProxy(AopContext.java:69)


相关文章
|
2月前
|
Java Spring
聊聊你对SpringBoot框架的理解 ?
SpringBoot是Spring家族中流行的子项目,旨在简化Spring框架开发的繁琐配置。它主要提供三大功能:starter起步依赖简化依赖管理,自动配置根据条件创建Bean,以及内嵌Web服务器支持Jar包运行,极大提升了开发效率。
110 0
|
2月前
|
NoSQL Java 数据库连接
SpringBoot框架
Spring Boot 是 Spring 家族中最流行的框架,旨在简化 Spring 应用的初始搭建与开发。它通过自动配置、起步依赖和内嵌服务器三大核心功能,大幅减少配置复杂度,提升开发效率。开发者可快速构建独立运行的 Web 应用,并支持多种数据访问技术和第三方集成。
|
2月前
|
缓存 安全 Java
Spring 框架核心原理与实践解析
本文详解 Spring 框架核心知识,包括 IOC(容器管理对象)与 DI(容器注入依赖),以及通过注解(如 @Service、@Autowired)声明 Bean 和注入依赖的方式。阐述了 Bean 的线程安全(默认单例可能有安全问题,需业务避免共享状态或设为 prototype)、作用域(@Scope 注解,常用 singleton、prototype 等)及完整生命周期(实例化、依赖注入、初始化、销毁等步骤)。 解析了循环依赖的解决机制(三级缓存)、AOP 的概念(公共逻辑抽为切面)、底层动态代理(JDK 与 Cglib 的区别)及项目应用(如日志记录)。介绍了事务的实现(基于 AOP
|
2月前
|
存储 缓存 NoSQL
Spring Cache缓存框架
Spring Cache是Spring体系下的标准化缓存框架,支持多种缓存(如Redis、EhCache、Caffeine),可独立或组合使用。其优势包括平滑迁移、注解与编程两种使用方式,以及高度解耦和灵活管理。通过动态代理实现缓存操作,适用于不同业务场景。
241 0
|
2月前
|
消息中间件 NoSQL Java
SpringBoot框架常见的starter你都用过哪些 ?
本节介绍常见的Spring Boot Starter,分为官方(如Web、AOP、Redis等)与第三方(如MyBatis、MyBatis Plus)两类,用于快速集成Web开发、数据库、消息队列等功能。
193 0
|
2月前
|
缓存 安全 Java
第五章 Spring框架
第五章 Spring框架
|
2月前
|
缓存 Java 数据库
第五章 Spring框架
第五章 Spring框架
|
2月前
|
Java Spring 容器
SpringBoot自动配置的原理是什么?
Spring Boot自动配置核心在于@EnableAutoConfiguration注解,它通过@Import导入配置选择器,加载META-INF/spring.factories中定义的自动配置类。这些类根据@Conditional系列注解判断是否生效。但Spring Boot 3.0后已弃用spring.factories,改用新格式的.imports文件进行配置。
704 0
|
6月前
|
前端开发 Java 数据库
微服务——SpringBoot使用归纳——Spring Boot集成Thymeleaf模板引擎——Thymeleaf 介绍
本课介绍Spring Boot集成Thymeleaf模板引擎。Thymeleaf是一款现代服务器端Java模板引擎,支持Web和独立环境,可实现自然模板开发,便于团队协作。与传统JSP不同,Thymeleaf模板可以直接在浏览器中打开,方便前端人员查看静态原型。通过在HTML标签中添加扩展属性(如`th:text`),Thymeleaf能够在服务运行时动态替换内容,展示数据库中的数据,同时兼容静态页面展示,为开发带来灵活性和便利性。
286 0
|
2月前
|
缓存 JSON 前端开发
第07课:Spring Boot集成Thymeleaf模板引擎
第07课:Spring Boot集成Thymeleaf模板引擎
340 0
第07课:Spring Boot集成Thymeleaf模板引擎

热门文章

最新文章