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


    背景

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

    出现的异常

    运行时抛出空指针异常
    image

    定位问题

    1. 点击异常代码行打上断点,debug调试
      image
    2. 通过查看变量值发现roleMapper为空,从而导致空指针
    3. 而roleMapper是传入this对象的属性,因此,问题来自传入的对象

    分析问题

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

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

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

    • 子类拥有父类非 private 的属性、方法。
    1. 此时,尝试修改私有方法变成public,发现this对象恢复正常,由此锁定代理类和私有方法出现问题
      image
    2. 通过搜索cglib代理类私有方法发现原因:
      image
      image
    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成功消灭!
    image

    参考文章:

  • 相关阅读:
    iOS-25个小技巧
    iOS-UITableView的使用
    iOS-UIPickerView
    iOS-UIStoryboard和UIResponder
    javascript弹出层-DEMO001
    jQuery源码分析-02正则表达式-RegExp-常用正则表达式
    JSON动态生成树
    回顾码农历程总结2013 期待2014
    大数据量分页存储过程效率测试附代码
    关于对象序列化json 说说
  • 原文地址:https://www.cnblogs.com/shimmernight/p/15226505.html
Copyright © 2020-2023  润新知