Spring实现类私有方法的几个问题(亲测通用解决方案)
现实的业务场景中,可能需要对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实现类私有方法测试通用方案的详细内容,更多关于Spring类私有方法的资料请关注脚本之家其它相关文章!
相关文章
IntelliJ IDEA 详细图解最常用的配置(适合刚刚用的新人)
这篇文章主要介绍了IntelliJ IDEA 详细图解最常用的配置,本篇教程非常适合刚刚用的新人,本文图文并茂给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下2020-08-08浅谈java中String StringBuffer StringBuilder的区别
下面小编就为大家带来一篇浅谈java中String StringBuffer StringBuilder的区别。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧2016-06-06详解Spring Cloud Finchley版中Consul多实例注册的问题处理
这篇文章主要介绍了详解Spring Cloud Finchley版中Consul多实例注册的问题处理,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧2018-08-08SpringMVC中@controllerAdvice注解的详细解释
刚接触SpringMVC应该很少会见到这个注解,其实它的作用非常大,下面这篇文章主要给大家介绍了关于SpringMVC中@controllerAdvice注解的相关资料,需要的朋友可以参考下2022-02-02springboot整合mybatis-plus基于注解实现一对一(一对多)查询功能
这篇文章主要介绍了springboot整合mybatis-plus基于纯注解实现一对一(一对多)查询功能,因为本人采用的是spring-boot进行开发,本身springboot就提倡采用不用配置自动配置的方式,所以真心希望mybatis(不是mybatis-plus)这点需要继续努力2021-09-09
最新评论