现实的业务场景中,可能需要对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. JSON数据和Java对象的相互转换

    JSON解析器: 常见的解析器: Jsonlib, Gson, fastjson, jackson 其中应用最广泛的是jackson,阿里的fastjson虽然比jackson快一点,但存在的问题比较 ...

  2. Sentry 官方 JavaScript SDK 简介与调试指南

    系列 1 分钟快速使用 Docker 上手最新版 Sentry-CLI - 创建版本 快速使用 Docker 上手 Sentry-CLI - 30 秒上手 Source Maps Sentry For ...

  3. 90-95年CPU功耗感知调度研究

    最近读了三篇1990-1995年的通过调度来降低cpu能耗的文章[1] [2] [3],简单总结一下该年代单核CPU功耗感知的调度策略. Motivation 随着便携式设备逐渐兴起,人们对降低其功耗 ...

  4. JVM-学习笔记持续更新

    1.Java虚拟机的基本结构 (1)类加载子系统与方法区: 类加载子系统负责从文件系统或者网络中加载Class信息,加载的类信息存放在一块称为方法区的内存空间.除了类的信息外,方法区中可能还会存放运行 ...

  5. [hdu7011]被EI加0了

    注意到仅关心于权值大小,预处理出$F_{i}(n)$​​​​表示$a_{1},a_{2},...,a_{n}$​​​​中恰填$i$​​​​​​种不同的数的方案数,那么显然答案即为$\sum_{i=1} ...

  6. [bzoj1071]组队

    题目即要求$Ah+Bv<=C+Aminh+Bminv$,如果同时枚举minh和minv,那么即要求$minh\le h$,$minv\le v$且$s\le C+Aminh+Bminv$从小到大 ...

  7. [cf1184E]Daleks' Invasion

    先求出任意一棵最小生成树,然后对边分类讨论1.非树边,答案即最小生成树的环上的最长边2.树边,反过来考虑,相当于对于每一个点对那条路经打上标记,取min对于1直接用倍增维护即可,对于2可以用树链剖分/ ...

  8. Elastic AMP监控.NET程序性能

    什么是Elastic AMP Elastic APM 是一个应用程序性能监控系统.它可以请求的响应时间.数据库查询.对缓存的调用.外部 HTTP 请求等的详细性能信息,可以实时监控软件服务和应用程序. ...

  9. 国内首家!腾讯云正式成为 FinOps 基金会顶级会员

    11月24日,腾讯云正式宣布加入FinOps基金会,作为国内首家FinOps基金会顶级会员,腾讯云将联合FinOps基金会,全面推进对FinOps标准和最佳实践的贡献,为企业提供云财务管理的最佳解决方 ...

  10. Vulnhub-Empire: Breakout题解

    Vulnhub-Empire: Breakout题解 这是Empire系列的靶机Breakout,地址:Vulnhub-EMPIRE: BREAKOUT 目标扫描 使用arp-scan 命令识别靶机I ...