背景

遇到问题:在进行Spring单元测试编写时,发现被测方法是一个私有方法,无法直接通过注入对象调用

解决思路:首先想到通过反射获取该私有方法的访问权限,并传入注入对象,最终调用对象的私有方法。

出现的异常

运行时抛出空指针异常

定位问题

  1. 点击异常代码行打上断点,debug调试

  2. 通过查看变量值发现roleMapper为空,从而导致空指针
  3. 而roleMapper是传入this对象的属性,因此,问题来自传入的对象

分析问题

  1. 通过分析this对象,可以发现它是一个被Cglib代理后的实例,由此可知,该类方法上必定有@Transactional事务注解或AOP注解修饰,从而被SpringCglib代理

  2. 查看cglib原理:

动态生成一个要代理类的子类,子类重写要代理的类的所有不是final的方法。在子类中采用方法拦截的技术拦截所有父类方法的调用,顺势织入横切逻辑。它比使用java反射的JDK动态代理要快。

其中重要的一点,代理类是被代理类的子类,回想关于Java中的继承,有一条很重要的特性就是:

  • 子类拥有父类非 private 的属性、方法。
  1. 此时,尝试修改私有方法变成public,发现this对象恢复正常,由此锁定代理类和私有方法出现问题

  2. 通过搜索cglib代理类私有方法发现原因:



  3. 由此可知,此处注入的cglib代理对象中不包含private方法!
  4. 那为啥同样传入的代理对象,调用public方法就成功,而调用private方法就失败呢?
  1. 如果是私有方法,那么在代理类中,不会包含这个方法。此时通过Method.invoke()来调用目标方法,传入的实例对象是userController的代理类,而这个代理类中的userService为NULL,所以,执行的时候,才会看到userService没有注入,导致空指针异常。
  2. 如果是公共方法,在代理类中,就有它的子类实现,则会先调用到代理类的拦截器MethodInterceptor。拦截器负责链式调用AOP方法和目标方法。在拦截器执行过程中,又调用了方法。但不同的是,此时传入的实例对象并不是代理类,而是代理类的目标对象。

结论:可以发现代理类正常情况下,执行到原方法时是通过代理的目标对象(即原始对象)来执行,而当代理类发现没有代理对应的private方法时,则直接通过代理对象(即上文的this)执行目标方法。

解决方法

既然我们需要的是只原始对象执行私有方法,只要通过代理类获取原始的目标对象即可。

// 由于cglib类是通过继承代理,无法代理私有方法,因此无法通过原始对象执行方法
if (AopUtils.isCglibProxy(menuService)) {
// 如果是cglib代理对象,则转为原始对象
menuService = (MenuServiceImpl)AopProxyUtils.getSingletonTarget(menuService);
}

此时得到的对象即为原始对象,bug成功消灭!

参考文章:

[bug]spring项目通过反射测试私有方法时,注入对象异常的更多相关文章

  1. JUnit 3.8 通过反射测试私有方法

    测试私有(private)的方法有两种: 1)把目标类的私有方法(修饰符:private)修改为(public),不推荐,因为修改了源程序不佳 2)通过反射 (推荐) 代码演示: 目标程序 Priva ...

  2. 无所不能的PowerMock,mock私有方法,静态方法,测试私有方法,final类

    1.为什么要用mock 我的一本书的解释: (1)创建所需的DB数据可能需要很长时间,如:调用别的接口,模拟很多数据 (2)调用第三方API接口,测试很慢, (3)编写满足所有外部依赖的测试可能很复杂 ...

  3. Android Studio 重写方法时参数命名异常

    Android Studio 重写方法时参数命名异常 Android Studio 重写方法时参数名称乱掉可以通过下载相应源码解决

  4. 使用PowerMockito和Mockito进行模拟测试,包括静态方法测试,私有方法测试等,以及方法执行的坑或者模拟不成功解决

    依赖:这个很重要,不同版本用法也有点区别: <dependency> <groupId>org.mockito</groupId> <artifactId&g ...

  5. java中用反射访问私有方法和私有成员[转]

    转自: http://zhouyangchenrui.iteye.com/blog/470521 java的反射可以绕过访问权限,访问到类的私有方法和成员.可能这点会引起安全性的讨论.反射的使用帮助解 ...

  6. Java 反射获取私有方法

    通常我们创建一个类时,它的私有方法在类外是不可见的,但是可以通过反射机制来获取调用.具体的反射机制的介绍大家自己百度. 所以反射可能会破坏我们的单例模式,当然解决方案也是有的,就是做个标记记录次数,第 ...

  7. c# 通过反射获取私有方法

    class Program { static void Main(string[] args) { //通过反射来调私有的成员 Type type = typeof(Person); //Bindin ...

  8. Java开发笔记(八十)利用反射技术操作私有方法

    前面介绍了如何利用反射技术读写私有属性,不单是私有属性,就连私有方法也能通过反射技术来调用.为了演示反射的逆天功能,首先给Chicken鸡类增加下列几个私有方法,简单起见弄来了set***/get** ...

  9. java反射调用私有方法和修改私有属性

    //调用私有方法package com.java.test; public class PrivateMethod { private String sayHello(String name) { r ...

随机推荐

  1. 离线安装rpm包并解决依赖(升级vsftpd为例)

    背景  实际开发中,我们的linux服务器是处理离线状态的,并不能访问互联网.如果此时要在linux上安装或者升级软件,就只能通过rpm包的安装方式.rpm包安装有一个缺陷,就是不能处理安装包的依赖问 ...

  2. SpringMVC 参数中接收之一 List

    作者:张艳涛 time:2020-07-31 SpingMVC 一.前台传数组,SpingMVC用addusers(@RequestBody List<UserPojo> userlist ...

  3. angular5采坑之旅

    开始尝试angular5,在此记录下踩过的坑以备查询 1.按照element-angular的文档引入后报错 is not part of the compilation output.解决方法--在 ...

  4. DC-1 靶机渗透测试

    DC-1靶机渗透测试 对着镜子大喊三声"太菜了""太菜了""太菜了" DC系列靶机的第一篇.边学习边日靶机边进步,摸爬滚打着前行. 内容不只 ...

  5. OpenGL学习笔记(六)坐标系统

    目录 一.衔接 二.概述 三.各个坐标系统 局部空间 世界空间 观察空间 裁剪空间 四.两种投影矩阵 正射投影 透视投影 五.把它们都组合到一起 六.编码实现 1. 实现卡片旋转 2. 实现正方体旋转 ...

  6. 以TiDB热点问题来谈Region的调度流程

    什么是热点问题 说这个话题之前我们先回顾一下TiDB的主要结构和概念. TiDB的核心架构分为TiDB.TiKV.PD三个部分,其中TiKV是一个分布式数据存储引擎用来存储真实的数据,在TiKV中又对 ...

  7. Compile Java Codes in Linux Shell instead of Ant Script

    The following is frequently used ant script, compile some java source codes with a libary path, then ...

  8. TreeUtil.java

    package com.infish.util; import java.lang.reflect.Method; import java.util.ArrayList; import java.ut ...

  9. iOS开发之HTTP断点续传

    前言 在APP中经常会遇到文件下载,鉴于用户体验和流量控制,就需要用到断点续传.本文主要对断点续传进行了多线程封装. 效果图 原理 HTTP实现断点续传是通过HTTP报文头部header里面设置的两个 ...

  10. Pikachu-CSRF模块

    一.概述 Cross-site request forgery 简称为"CSRF",在CSRF的攻击场景中攻击者会伪造一个请求(这个请求一般是一个链接),然后欺骗目标用户进行点击, ...