现实的业务场景中,可能需要对Spring的实现类的私有方法进行测试。

场景描述:

比如XXXService里有 两个函数a、函数b。

而实现类XXXServiceImpl中实现了函数a、函数b,还包含私有方法函数c和函数d。

要写一个XXXTestController来调用XXXServiceImpl的函数c。

面临几个问题:

1、如果注入接口,则无法调用实现类的私有类。

2、如果注入实现类,则需要将实现类里的私有方法改为公有的,而且需要设置@EnableAspectJAutoProxy(proxyTargetClass = true)使用CGLIB代理方式

如果单纯为了测试而接口中定义实现类的私有方法或者为了测试而将私有方法临时改为公有方法,显然不太合适。

解决方案:

可以通过CGLIB注入实现类的子类,如果是Gradle项目也可以使用Aspect插件,将切面代码在编译器织入实现类中注入的类型则为实现类,然后通过反射设置为可访问来调用私有方法。

方案一 使用BeanUtils.findDeclaredMethod反射方法

反射调用代码:

BeanInvokeUtil

public class BeanInvokeUtil {

  public class InvokeParams {

// 方法名称(私有)
private String methodName; // 参数列表类型数组
private Class<?>[] paramTypes;
// 调用的对象
private Object object; // 参数列表数组(如果不为null,需要和paramTypes对应)
private Object[] args; public InvokeParams() {
super();
} public InvokeParams(Object object, String methodName, Class<?>[] paramTypes, Object[] args) {
this.methodName = methodName;
this.paramTypes = paramTypes;
this.object = object;
this.args = args;
} public String getMethodName() {
return methodName;
} public void setMethodName(String methodName) {
this.methodName = methodName;
} public Class<?>[] getParamTypes() {
return paramTypes;
} public void setParamTypes(Class<?>[] paramTypes) {
this.paramTypes = paramTypes;
} public Object getObject() {
return object;
} public void setObject(Object object) {
this.object = object;
} public Object[] getArgs() {
return args;
} public void setArgs(Object[] args) {
this.args = args;
}
} public static Object invokePrivateMethod(InvokeParams invokeParams) throws InvocationTargetException, IllegalAccessException {
// 参数检查
checkParams(invokeParams);
// 调用
return doInvoke(invokeParams);
} private static Object doInvoke(InvokeParams invokeParams) throws InvocationTargetException, IllegalAccessException {
Object object = invokeParams.getObject();
String methodName = invokeParams.getMethodName();
Class<?>[] paramTypes = invokeParams.getParamTypes();
Object[] args = invokeParams.getArgs(); Method method;
if (paramTypes == null) {
method = BeanUtils.findDeclaredMethod(object.getClass(), methodName);
} else {
method = BeanUtils.findDeclaredMethod(object.getClass(), methodName, paramTypes);
}
method.setAccessible(true);
if (args == null) {
return method.invoke(object);
}
return method.invoke(object, args); } private static void checkParams(InvokeParams invokeParams) {
Object object = invokeParams.getObject();
if (object == null) {
throw new IllegalArgumentException("object can not be null");
}
String methodName = invokeParams.getMethodName();
if (StringUtils.isEmpty(methodName)) {
throw new IllegalArgumentException("methodName can not be empty");
} // 参数类型数组和参数数组要对应
Class<?>[] paramTypes = invokeParams.getParamTypes();
Object[] args = invokeParams.getArgs(); boolean illegal = true;
if (paramTypes == null && args != null) {
illegal = false;
}
if (args == null && paramTypes != null) {
illegal = false;
}
if (paramTypes != null && args != null && paramTypes.length != args.length) {
illegal = false;
}
if (!illegal) {
throw new IllegalArgumentException("paramTypes length != args length");
}
}
}

使用方式:

使用时通过CGLIB方式注入实现类或者将切面代码编译器织入实现类的方式,然后注入Bean。

@Autowired private XXXService xxxService;

然后填入调用的对象,待调用的私有方法,参数类型数组和参数数组。

BeanInvokeUtil.invokePrivateMethod(new BeanInvokeUtil()
.new InvokeParams(xxxService, "somePrivateMethod", null, null));

注意这时注入的xxxService的类型为 xxxServiceImpl。

如果需要返回值,可以获取该调用方法的返回值。

方案二:使用jdk和cglib工具获取真实对象

测试类

public class Test {
@Autowired
ServiceImpl serviceImpl; @Test
public void test() throws Exception {
Class<?> clazz = Class.forName("ServiceImpl");
Method method = clazz.getDeclaredMethod("MethodA");
method.setAccessible(true);
Object target = ReflectionUtil.getTarget(serviceImpl);
// 注意,这里不能直接用serviceImpl,因为它已经被spring管理,
// 变成serviceImpl真实实例的代理类,而代理类中并没有私有方法,所以需要先获取它的真实实例
method.invoke(target);
}
}

获取spring 代理对象的真实实例的工具类,适用于两种动态代理情况:jdk和cglib

public class ReflectionUtil {
/**
* 获取spring 代理对象的真实实例
* @param proxy 代理对象
* @return
* @throws Exception
*/
public static Object getTarget(Object proxy) throws Exception {
if(!AopUtils.isAopProxy(proxy)) {
return proxy;//不是代理对象
} if(AopUtils.isJdkDynamicProxy(proxy)) {
return getJdkDynamicProxyTargetObject(proxy);
} else { //cglib
return getCglibProxyTargetObject(proxy);
}
} private static Object getCglibProxyTargetObject(Object proxy) throws Exception {
Field h = proxy.getClass().getDeclaredField("CGLIB$CALLBACK_0");
h.setAccessible(true);
Object dynamicAdvisedInterceptor = h.get(proxy);
Field advised = dynamicAdvisedInterceptor.getClass().getDeclaredField("advised");
advised.setAccessible(true);
Object target = ((AdvisedSupport)advised.get(dynamicAdvisedInterceptor)).getTargetSource().getTarget();
return target;
} private static Object getJdkDynamicProxyTargetObject(Object proxy) throws Exception {
Field h = proxy.getClass().getSuperclass().getDeclaredField("h");
h.setAccessible(true);
AopProxy aopProxy = (AopProxy) h.get(proxy);
Field advised = aopProxy.getClass().getDeclaredField("advised");
advised.setAccessible(true);
Object target = ((AdvisedSupport)advised.get(aopProxy)).getTargetSource().getTarget();
return target;
}
}

另外还有一个更好的开源工具 PowerMock https://github.com/powermock/powermock,感兴趣的同学可以研究一下

Spring实现类私有方法测试通用方案的更多相关文章

  1. junit测试延伸--私有方法测试

    关于junit测试的延伸,这里有类概念级别的测试,继承类的测试,接口的测试,抽象类的测试,关于这些类级别的测试,这里我就不做多的赘述了. 关于上面的几个测试就是说,我们不应该单纯的去测试类中的一些方法 ...

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

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

  3. Junit3.8 私有方法测试

    1. 测试类的私有方法时可以采取两种方式:1) 修改方法的访问修饰符,将private修改为default或public(但不推荐采取这种方式).2) 使用反射在测试类中调用目标类的私有方法(推荐). ...

  4. junit单元测试中私有方法测试

    1.单元测试可以对系统逻辑进行每个单元模块的测试. 2.单元测试也可以作为回归测试的依据,可以避免升级完善功能时引入问题. 3.单元测试要求将代码写的更清晰,更易于测试. 4.有时单元测试需要测试私有 ...

  5. Python类私有方法的陷阱

    引言 Python不像C++.Java.C#等有明白的公共.私有或受保护的keyword来定义成员函数或属性,它使用约定的单下划线"_"和"__"双下划线作为函 ...

  6. [bug]spring项目通过反射测试私有方法时,注入对象异常

    背景 遇到问题:在进行Spring单元测试编写时,发现被测方法是一个私有方法,无法直接通过注入对象调用 解决思路:首先想到通过反射获取该私有方法的访问权限,并传入注入对象,最终调用对象的私有方法. 出 ...

  7. gRPC学习之四:实战四类服务方法

    欢迎访问我的GitHub https://github.com/zq2599/blog_demos 内容:所有原创文章分类汇总及配套源码,涉及Java.Docker.Kubernetes.DevOPS ...

  8. spring aop pointcut 切入点是类的公共方法(私有方法不行),还是接口的方法

    spring aop pointcut 切入点是类的公共方法(私有方法不行),还是接口的方法 类的公共方法可以,但是私有方法不行 测试一下接口的方法是否能够捕捉到

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

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

随机推荐

  1. webpack 之开发环境优化 HMR

    webpack 之开发环境优化 HMR // webpack.config.js /** * HMR hot module replacement 热模块替换 / 模块热替换 * 作用:一个模块发生变 ...

  2. C# WINFORM进销存系统开发(内涵免费源码+部分实操视频讲解)

    互联网的时代,电商火爆,大家都开始进行线上销售货品,那你是如何管理你的商品库存和进销问题?软积木--小敏用的是C# WINFORM进销存系统来管理我的数据,给我带来了很多便利. 它是高频需求项目,很多 ...

  3. [第二章]c++学习笔记6(复制构造函数在各个编译器中的表现)

    visual studio结果 dev c++结果 两者的输出有所不同 原因:dev c++编译对这个过程进行了优化,因为直接return对象给a,为节省时间所以不生成临时对象,所以结果为10. 注: ...

  4. windows 上搭建 sftp 服务器 -freesshd全过程( 在linux上部署逐浪CMS的必读教程)

    文章标题: windows 上搭建 sftp 服务器 - freesshd全过程 关键字 : freesshd 文章分类: 教程 创建时间: 2020年3月23日 缘由 动手 第一步:添加用户 第二步 ...

  5. Node常用内置模块

    node内置的fs模块就是文件系统模块,负责读写文件 fs同时提供了同步异步的方法 'use strict'; var fs = require('fs'); fs.readFile('test.tx ...

  6. Linux(kali)基础设置

    本笔记的友情链接 常用目录介绍 boot 存放启动文件 dev 存放设备文件 etc 存放配置文件 home 普通用户家目录,以/home/$username的方式存放 media 移动存储自动挂载目 ...

  7. Maven 依赖调解源码解析(四):传递依赖,第一声明者优先

    本文是系列文章<Maven 源码解析:依赖调解是如何实现的?>第四篇,主要介绍依赖调解的第二条原则:传递依赖,第一声明者优先.请按顺序阅读其他系列文章,系列文章总目录参见:https:// ...

  8. <C#任务导引教程>练习四

    //27,创建一个控制台应用程序,声明两个DateTime类型的变量dt,获取系统的当前日期时间,然后使用Format格式化进行规范using System;class Program{    sta ...

  9. bootstrap导入报错

  10. 洛谷 P6199 - [EER1]河童重工(点分治+虚树)

    洛谷题面传送门 神仙题. 首先看到这样两棵树的题目,我们肯定会往动态树分治的方向考虑.考虑每次找出 \(T_2\) 的重心进行点分治.然后考虑跨过分治中心的点对之间的连边情况.由于连边边权与两棵树都有 ...