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

简介: 【问题处理】—— 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,和网传的一样,但为什么还是没起作用呢?


866fea9f2f7c4a4b81be233040aff1a6.png

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

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

我们可以看到,这个注解被设定了属性proxyTargetClass = true,

048a39bed48042cdbfbbd8f323c08ed6.png

进入查看详情,发现这个注解自己也带有注解,这次是Import了AspectJAutoProxyRegistrar.class,

3297a56794d4450582b57cd4c29d0ce3.png

这个AspectJAutoProxyRegistrar类我们不再继续深究,只简单看一眼,可以看到这个类实现了ImportBeanDefinitionRegistrar,表明这个类专门就是给别人import的,而且在被import的时候能获取别人的属性,并根据获取到的属性执行特定代码。

此处这个类就获取到了我们放在@EnableAspectJAutoProxy(proxyTargetClass = true)注解上的值proxyTargetClass = true,从而执行了一段代码,看名字就得知,该段代码就是强制代理创建使用CGLIB的方式。

273cfe6efcc442c58499f0c747225695.png

那么问题来了,当我们配置了spring.aop.proxy-target-class=false,就不会走这段逻辑,不会强制使用CGLIb,但代码为什么还是强制用的CGlib呢?

我们可以在这个强制使用Cglib的方法里打个断点,然后启动项目

55061239f85c44e8884e887e1f1c5722.png


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

2599a0bc536c4061a1a9e0f11e41351a.png



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

aba7f20a42904d64840ec08229634a54.png


最关键的是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的类,因此我们还必须搞清楚代理创建的真实位置。


dd57efdb13024a389bcf67715a8ccd67.png


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


05b6140826a54a83b31ed03817c8e62d.png

bf010249500e4cfca7d0a62792bbde58.png


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

66e11d69252843b8abdcec14b412fd04.png

70968243ee6247269bd03c3d9c809a05.png


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


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

我们现在要做的事很明确,将“代理创建基类”(AbstractAutoProxyCreator的某个子类) 的proxyTargetClass属性设置为false,只有这样,当这个基类去创建动态代理时,才能采用JDK动态代理。

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

f6c24597ba1c4f438655fec702dcf70a.png


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


结果

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


目录
相关文章
|
8月前
|
设计模式 监控 前端开发
SpringBoot拦截器和动态代理有什么区别?
SpringBoot拦截器和动态代理有什么区别?
62 0
|
8天前
|
设计模式 安全 Java
深入理解Spring Boot AOP:CGLIB代理与JDK动态代理的完全指南
深入理解Spring Boot AOP:CGLIB代理与JDK动态代理的完全指南
417 1
|
8天前
|
Java 应用服务中间件 Maven
Spring Boot项目打war包(idea:多种方式)
Spring Boot项目打war包(idea:多种方式)
30 1
|
8天前
|
Java Linux
Springboot 解决linux服务器下获取不到项目Resources下资源
Springboot 解决linux服务器下获取不到项目Resources下资源
|
8天前
|
Java API Spring
SpringBoot项目调用HTTP接口5种方式你了解多少?
SpringBoot项目调用HTTP接口5种方式你了解多少?
114 2
|
8天前
|
前端开发 JavaScript Java
6个SpringBoot 项目拿来就可以学习项目经验接私活
6个SpringBoot 项目拿来就可以学习项目经验接私活
43 0
|
6天前
|
Java Maven
SpringBoot项目的用maven插件打包报Test错误
SpringBoot项目的用maven插件打包报Test错误
|
2天前
|
安全 JavaScript Java
装饰工程|装饰工程管理系统-项目立项子系统的设计与实现|基于Springboot的装饰工程管理系统设计与实现(源码+数据库+文档)
装饰工程|装饰工程管理系统-项目立项子系统的设计与实现|基于Springboot的装饰工程管理系统设计与实现(源码+数据库+文档)
5 0
|
5天前
|
前端开发 JavaScript Java
Java网络商城项目 SpringBoot+SpringCloud+Vue 网络商城(SSM前后端分离项目)五(前端页面
Java网络商城项目 SpringBoot+SpringCloud+Vue 网络商城(SSM前后端分离项目)五(前端页面
Java网络商城项目 SpringBoot+SpringCloud+Vue 网络商城(SSM前后端分离项目)五(前端页面