Spring实现类私有方法的几个问题(亲测通用解决方案)

 更新时间:2021年06月16日 17:17:10   作者:satire  
现实的业务场景中,可能需要对Spring的实现类的私有方法进行测试。本文给大家分享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 详细图解最常用的配置(适合刚刚用的新人)

    这篇文章主要介绍了IntelliJ IDEA 详细图解最常用的配置,本篇教程非常适合刚刚用的新人,本文图文并茂给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2020-08-08
  • 浅谈java中String StringBuffer StringBuilder的区别

    浅谈java中String StringBuffer StringBuilder的区别

    下面小编就为大家带来一篇浅谈java中String StringBuffer StringBuilder的区别。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2016-06-06
  • 详解Spring Cloud Finchley版中Consul多实例注册的问题处理

    详解Spring Cloud Finchley版中Consul多实例注册的问题处理

    这篇文章主要介绍了详解Spring Cloud Finchley版中Consul多实例注册的问题处理,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2018-08-08
  • SpringMVC中@controllerAdvice注解的详细解释

    SpringMVC中@controllerAdvice注解的详细解释

    刚接触SpringMVC应该很少会见到这个注解,其实它的作用非常大,下面这篇文章主要给大家介绍了关于SpringMVC中@controllerAdvice注解的相关资料,需要的朋友可以参考下
    2022-02-02
  • JAVA找不到符号的三种解决方案

    JAVA找不到符号的三种解决方案

    这篇文章主要给大家介绍了关于JAVA找不到符号的三种解决方案, 找不到符号错误主要发生在我们试图引用一个未在我们正在编译的程序中声明的变量时,这意味着编译器不知道我们所引用的Java变量,需要的朋友可以参考下
    2024-03-03
  • Struts2学习笔记(2)-路径问题解决

    Struts2学习笔记(2)-路径问题解决

    本文主要介绍Struts2的路径问题,尽量不要使用相对路径,使用相对路径会让路径问题变得很繁琐很麻烦,推荐使用绝对路径,希望能给大家做一个参考。
    2016-06-06
  • mybatis Reflector反射类的具体使用

    mybatis Reflector反射类的具体使用

    Reflector类是MyBatis反射模块的核心,负责处理类的元数据,以实现属性与数据库字段之间灵活映射的功能,本文主要介绍了mybatis Reflector反射类的具体使用,感兴趣的可以了解一下
    2024-02-02
  • 集合嵌套之ArrayList嵌套ArrayList实例

    集合嵌套之ArrayList嵌套ArrayList实例

    下面小编就为大家带来一篇集合嵌套之ArrayList嵌套ArrayList实例。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-08-08
  • SpringCloud2020版本配置与环境搭建教程详解

    SpringCloud2020版本配置与环境搭建教程详解

    这篇文章主要介绍了SpringCloud2020版本配置与环境搭建教程详解,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2020-12-12
  • springboot整合mybatis-plus基于注解实现一对一(一对多)查询功能

    springboot整合mybatis-plus基于注解实现一对一(一对多)查询功能

    这篇文章主要介绍了springboot整合mybatis-plus基于纯注解实现一对一(一对多)查询功能,因为本人采用的是spring-boot进行开发,本身springboot就提倡采用不用配置自动配置的方式,所以真心希望mybatis(不是mybatis-plus)这点需要继续努力
    2021-09-09

最新评论