zoukankan      html  css  js  c++  java
  • [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

    参考文章:

  • 相关阅读:
    【梦话区】一直迷茫的net小伙
    【ASP.NET】登陆成功后如何跳转到上一个页面
    【C#】强类型DataSet实现登录次数限制
    【ASP.NET】ItemDataBound之repeater 和 listview
    【网页设计】框架的高度随框架里面的内容的多少而改变——转
    【连载】Scala程序设计:Java虚拟机多核编程实战——简介
    博客园图灵杯第五届博问大赛(2010.8.3~2010.9.2)
    【连载】高效人士的116个IT秘诀(第2版)——秘诀23早晨就来一次突破
    图灵2010.08书讯
    图灵五周年生日聚会圆满成功,多家媒体对此进行报道
  • 原文地址:https://www.cnblogs.com/shimmernight/p/15226505.html
Copyright © 2011-2022 走看看