【问题处理】—— SpringBoot2 动态代理问题排查

现象:springboot 1 -> 2 升级后业务异常

系统升级至springboot2 spring5 以后,系统大量接口报错,经定位,是在某一个方法切面中获取当前方法所在类的对象名时,获取到的是实现类,而我们需要类名的是接口类名,导致问题。

分析:动态代理获取类名有误

方法切面获取的是实现类,主要是因为切面生成的代理,采用的是CGLIB动态代理,这样获取的类就是实现类的名字,若采用spring自带的判断逻辑,有接口优先使用jdk动态代理,此时获取到的就是接口名了。

深入

主要原因就是动态代理选择的方式与原来不同,spring5的方法有接口优先选jdk动态代理,而springBoot2则指定默认的代理选择CGLIB,因此生成代理时全是CGLIB。

第一阶段:套用网上教程,结果无效

根据网上资料,在application.properties设定参数 spring.aop.proxy-target-class=false,但结果无效,继续分析原因
springBoot2采用的CGLIB的源码在其一个配置类AopAutoConfiguration=中,可以看到如果不配置spring.aop.proxy-target-class=false,确实会走Cglib代理,配了才会走jdk,和网传的一样,但为什么还是没起作用呢?
在这里插入图片描述

第二阶段:分析网上教程的实际作用,以及此处为什么无效

先看看如果不配spring.aop.proxy-target-class=false,为什么就会走cglib代理? 这个CglibAutoProxyConfiguration类我们可以看到是空的,除了类名什么也没有,因此关键不在于这个类,而是其上的 @EnableAspectJAutoProxy(proxyTargetClass = true)注解
我们可以看到,这个注解被设定了属性
proxyTargetClass = true

在这里插入图片描述
进入查看详情,发现这个注解自己也带有注解,这次是Import了AspectJAutoProxyRegistrar.class
在这里插入图片描述
这个AspectJAutoProxyRegistrar类我们不再继续深究,只简单看一眼,可以看到这个类实现了ImportBeanDefinitionRegistrar,表明这个类专门就是给别人import的,而且在被import的时候能获取别人的属性,并根据获取到的属性执行特定代码。
此处这个类就获取到了我们放在@EnableAspectJAutoProxy(proxyTargetClass = true)注解上的值proxyTargetClass = true,从而执行了一段代码,看名字就得知,该段代码就是强制代理创建使用CGLIB的方式。
在这里插入图片描述
那么问题来了,当我们配置了spring.aop.proxy-target-class=false,就不会走这段逻辑,不会强制使用CGLIb,但代码为什么还是强制用的CGlib呢?
我们可以在这个强制使用Cglib的方法里打个断点,然后启动项目
在这里插入图片描述

发现该方法被多次调用执行,通过栈去找调用的源头,发现除了SpringBoot2自己那个AopAutoConfiguration配置,还有很多注解都有类似的功能,比如我们经常用来开启事务的注解@EnableTransactionManagement

在这里插入图片描述

还有开启方法缓存的 @EnableCaching 注解都包含proxyTargetClass的设置,同时也都import了一个类,在这个类中会对设置的proxyTargetClass判断,若为true,则执行强制CGLIB的方法
在这里插入图片描述

最关键的是proxyTargetClass不是项目的参数,它并不唯一,它只是这些注解的属性,不同的注解,甚至同一个注解在不同位置的使用都可以独立的设值。而只要有一处设置为true,就会执行一段使用CGlib的代码,这就意味着,如果想要阻止其强制使用CGLIB,就要把上述所有注解的在所有位置的使用都设置成proxyTargetClass = false。我们项目中的使用可以自己控制,springboot2内置的也可以通过spring.aop.proxy-target-class=false控制,但有些注解的使用是在第三方包中,它设定了proxyTargetClass = true后,我们也没办法去改动,从而导致一定会执行该段代码

第三阶段:分析代理使用CGlib的原因

要想解决问题,还是得看这段代码执行了什么,为什么执行后就会强制CGlib,是否执行后还有抢救的余地?我们可以看到这个方法实际上是寻找一个叫做“org.springframework.aop.config.internalAutoProxyCreator”的Bean定义,为它增加一个proxyTargetClass = true的属性。但是,spring源码中其实并没有一个叫internalAutoProxyCreator的类,因此我们还必须搞清楚代理创建的真实位置。
在这里插入图片描述
在这里插入图片描述

通过debug发现,这个Bean实际类型是可变的,对应着三个类,由优先级控制,列表越后优先级越高,它可以是InfrastructureAdvisorAutoProxyCreator,也可以被后两者顶替,三者全是AbstractAutoProxyCreator的子类,因此带有后置处理器(SmartInstantiationAwareBeanPostProcessor),可以在初始化后对Bean进行处理
在这里插入图片描述

在这里插入图片描述

而且从名字可以看出来,这个类(AbstractAutoProxyCreator的某个子类)是基类,是帮助创建代理的,更具体的说,在为某一个对象创建代理时,会先新建一个代理的配置,此时这个类(AbstractAutoProxyCreator的某个子类)的属性会被复制到代理配置里。如下图,可以看到,代理配置里会根据这个属性生产代理,这里就是创建并直接返回了cglib代理
在这里插入图片描述
在这里插入图片描述

至此,问题引起的全流程,都已经完全清楚了,整体来说,就是在任一位置设定的proxyTargetClass = true 都会在“代理创建基类”(AbstractAutoProxyCreator的某个子类) 内增添一个proxyTargetClass = true的属性,且无法再设为false,而这个属性会在代理生成时使其走向某个分支,那么在这个分支内就会返回CGlib代理了,只是问题该如何解决呢

第四阶段:利用Bean创建流程解决问题

我们现在要做的事很明确,将“代理创建基类”(AbstractAutoProxyCreator的某个子类) 的proxyTargetClass属性设置为false,只有这样,当这个基类去创建动态代理时,才能采用JDK动态代理。
一般情况下,如果要在一个Bean被我们使用前去改它的属性,可以通过BeanPostProcessor(Bean后置处理器)去修改,但是如果这个Bean本身就是一个BeanPostProcessor,那他们的优先级就是一样的,自然无法相互作用。这个时候,如果我们想更改这种Bean的属性,就只能更加提前,在所有Bean(包括BeanPostProcessor这种特殊的Bean)都没实例化之前,去对其做修改。我们能现在能自然的想到就是BeanFactoryPostProcessor,或者其子接口BeanDefinitionRegistryPostProcessor,实现这两者,就可以在实例化之前直接修改任何Bean的定义,这样当他们实例化时,就带着我们设定的属性值了。

在这里插入图片描述

此处,可以自定义一个类,实现BeanDefinitionRegistryPostProcessor接口,这个接口可以在Bean定义阶段完成后,对Bean定义做自定义处理

结果

经测试,所有动态代理均被设置为jdk动态代理,保持了 springboot1 时的默认配置,项目上实现了兼容

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

战斧

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值