Aop踩坑!记一次模板类调用注入属性为空的问题
问题起因
在做一个需求的时候,发现原来的代码逻辑都是基于模板+泛型的设计模式,模板用于规整逻辑处理流程,泛型用来转换参数和选取实现类。听上去是不是很nice!
- 类目录结构
- AbstractTestAop:顶层抽象类,定义骨架和执行顺序,内部通过Autowired注入了TopClassBean的实例对象。
- AbstractTestCglibAop:二级抽象类,继承自AbstractTestAop,空类无实现。
- TestCglibAopExample:具体子类,类上添加了@Component注解,空类无实现。
- TestAopRemoteEntrance:调用入口,它是一个Bean。
- TopClassBean:实例对象,内部提供一个方法用来表示被调用。
- AsyncExportLogAspect:方法切面(路径可以自己配置,此处对切面路径做了处理所以飘红)
单元测试
单测结果:
很明显:顶层接口内部实例引用的TopClassBean对象未注入,属性为空,导致空指针!
排查
方法debug
- 获取bean
可以看到此时获取到的Bean类型为一个代理类,继续往下,进入到invoke方法
2. before()
可以发现进入到protected
修饰的Before
方法的时候由代理转变为实际的类方法调用了
- myDo()
进入到final
修饰的Mydo
方法的时候又由实际类切换到代理类调用了,这时候内部引用topClassBean
为空,最后NPE
总结:
由上可知,cglib动态代理可以代理目标类非final和private方法,当调用final或者private方法时,由于目标类中不存在此方法,所以还是使用代理类进行调用。
下面我们可以进行源码debug,主要解决两个问题:
- 为什么会发生代理
- 代理类为啥属性为空
源码debug
通常代理都是发生在Bean实例化完成之后,对成品的Bean进行代理,多发生在BeanProcess后置处理中
按照这个思路咱们开始走断点debug:
- 实例化完成情况
我们发现实例化完成内部属性是有引用值的,不等于null,所以问题不在这,往下看
2. 后置处理器
重点:从这里我们发现Bean变成了代理对象,并且内部引用变成了null,证实了我们的猜想,由此可断定问题出现在BeanProcess的后置处理中
- 跟随断点进入
AbstractAutowireCapableBeanFactory#applyBeanPostProcessorsAfterInitialization
方法查看
发现经历了
AbstractAutoProxyCreator#postProcessAfterInitialization
方法后就发生了代理改变,我们继续往下
- 在方法中
AbstractAutoProxyCreator#wrapIfNecessary
判断了是否存在代理,此处生成了代理对象
在此处我们发现了因为aop切面存在,所以导致启用了代理
问题一解决
- 代理生成
因为没有接口,所以使用cglib代理
- 代理实现
这里我们可以很清楚的看到是使用new构造生成出来的代理类,所以实例属性值为空就解释的通了,
问题二解决
总结:
由于AOP切面存在,导致目标类发生代理,生成了目标子类的代理Bean,代理类是通过 objenesis.newInstance(proxyClass, enhancer.getUseCache())
构造出来的,所以不存在相关属性,联系到cglib代理原理---通过ASM字节码框架在运行期写入字节码跳过了编译期,可以佐证咱们的定论。
针对上面两个问题结论如下:
- 由于方法切面导致目标类发生代理
- 代理类是在运行期通过构造new出来的,属性值为空,所以代理类进行实例调用,会报NPE
我们对整个问题进行一个完整性总结:
由于AOP切面代理的原因,导致内部final方法调用走的代理类调用,代理类实例属性为空,导致NPE。
模板顶层为抽象类,未实现接口,导致选择cglib代理,cglib通过构造new实现代理类,内部属性均为空,由于通过继承实现,final和private方法无法被代理,所以当不可继承方法被调用时,当前对象为代理类,否则为目标类。
解决方案
- 顶层实现接口,避免cglib代理
- 方法访问修饰变更,可被继承代理
- 手动getBean,指定目标类对象调用
在调试的过程还发现一个有意思的现象:
整个引用调用链的方法栈上只要有一个方法被代理,调用链后端的所有方法都将使用目标类调用,不会导致NPE。
举个例如下:invoke(final) -> myDo1(非final) -> myDo(final),此时不会产生NPE,因为这个时候执行Mydo方法的时候仍然是目标类。
有兴趣的同学可以去翻一下源码,一起交流
附:代理类
从代理类上面我们可以看出:
- 代理类继承具体子类
TestCglibAopExample
,所以final或者private相关方法,即Mydo()和invoke()方法代理类未提供实现,无法被代理。
获取代理类class文件命令,在idea启动参数中添加
-Dsun.misc.ProxyGenerator.saveGeneratedFiles=true
-Dcglib.debugLocation=/Users/xxx
关注我的公众号一起交流吧!
Aop踩坑!记一次模板类调用注入属性为空的问题的更多相关文章
- Springcloud踩坑记---使用feignclient远程调用服务404
公司项目进行微服务改造,由之前的dubbo改用SpringCloud,微服务之间通过FeignClient进行调用,今天在测试的时候,eureka注册中心有相应的服务,但feignclient就是无法 ...
- 【踩坑记】从HybridApp到ReactNative
前言 随着移动互联网的兴起,Webapp开始大行其道.大概在15年下半年的时候我接触到了HybridApp.因为当时还没毕业嘛,所以并不清楚自己未来的方向,所以就投入了HybridApp的怀抱. Hy ...
- Spark踩坑记——共享变量
[TOC] 前言 Spark踩坑记--初试 Spark踩坑记--数据库(Hbase+Mysql) Spark踩坑记--Spark Streaming+kafka应用及调优 在前面总结的几篇spark踩 ...
- Spark踩坑记——从RDD看集群调度
[TOC] 前言 在Spark的使用中,性能的调优配置过程中,查阅了很多资料,之前自己总结过两篇小博文Spark踩坑记--初试和Spark踩坑记--数据库(Hbase+Mysql),第一篇概况的归纳了 ...
- EOS踩坑记
[EOS踩坑记] 1.每个account只能更新自己的contract,即使两个account的秘钥相同,也不允许. 如下,使用alice的权限来更新james的contract.会返回 Missin ...
- Spring @Transactional踩坑记
@Transactional踩坑记 总述 Spring在1.2引入@Transactional注解, 该注解的引入使得我们可以简单地通过在方法或者类上添加@Transactional注解,实现事务 ...
- Vue + TypeScript + Element 搭建简洁时尚的博客网站及踩坑记
前言 本文讲解如何在 Vue 项目中使用 TypeScript 来搭建并开发项目,并在此过程中踩过的坑 . TypeScript 具有类型系统,且是 JavaScript 的超集,TypeScript ...
- WinUI 3 踩坑记:第一个窗口
本文是 WinUI 3 踩坑记 的一部分,该系列发布于 GitHub@Scighost/WinUI3Keng,文中的代码也在此仓库中,若内容出现冲突以 GitHub 上的为准. WinUI 3 应用的 ...
- Spark踩坑记——Spark Streaming+Kafka
[TOC] 前言 在WeTest舆情项目中,需要对每天千万级的游戏评论信息进行词频统计,在生产者一端,我们将数据按照每天的拉取时间存入了Kafka当中,而在消费者一端,我们利用了spark strea ...
随机推荐
- path()和re_path()用法&区别
path() 参数列表: 参数1:字符串类型,用来匹配请求路径 参数2:指定路径所对应的视图函数名 参数3:关键字参数 实际用的不多 参数4... # urls.py # 创建子应用的路由文件 fro ...
- position 的值absolute、fixed、relative和static的定位原点是什么
position 的值absolute.fixed.relative和static的定位原点是什么 absolute 成绝对定位的元素,相对于值不为static的第一个父元素进行定位,也可以理解为离自 ...
- java继承:extends
继承:extends 1.java只支持单继承,不支持多继承 2.java支持多层继承(继承体系) 3.子类不能继承父类所有非私有的成员(成员方法和成员变量) 4.子类不能继承父类的构造方法,但是可以 ...
- MyBatis与Hibernate有哪些不同?
(1)Mybatis和hibernate不同,它不完全是一个ORM框架,因为MyBatis需要程序员自己编写Sql语句. (2)Mybatis直接编写原生态sql,可以严格控制sql执行性能,灵活 ...
- Vue手动集成less预编译器
less是一门css预处理语言,简单的说就是在css的基础上提升为可编程性的预编译器 需要在项目中安装 less ,less-loader 2个插件,语法为:npm i -D less less-lo ...
- Redis 的持久化机制是什么?各自的优缺点?
Redis 提供两种持久化机制 RDB 和 AOF 机制: 1.RDBRedis DataBase)持久化方式: 是指用数据集快照的方式半持久化模式) 记录 redis 数据库的所有键值对,在某个时间 ...
- springboot远程debug调试
案例代码: https://www.cnblogs.com/youxiu326/p/sb_promotion.html 1.首先去编辑器打开项目 2.打开Edit Configurations ...
- fsdfd
static int kWeiOfVal(int val, int k) { int n = 1; int temVal = val; int result; while (1) { temVal = ...
- 小白上学のcanvas零基础
<canvas> 元素 <canvas> 看起来和 <img> 元素很相像,唯一的不同就是它并没有 src 和 alt 属性.实际上,<canvas> ...
- maven项目中各文件都没有报错,但是项目名称有红叉
项目报错可以看到Problems(可以在Window--Show View--other--输入Problems找到) 一.可能原因 1.项目中各文件没有报错,但是项目名称中 ...