聊聊在获取方法参数名方面|聊聊在获取方法参数名方面,Spring真的就比Mybatis强?

目录

  • 前言
  • 一、Spring是如何获取方法参数名称的?
    • Spring获取参数名称的两种方式
  • 二、Mybatis为什么没有向Spring学习?
    • Mybatis要获取的是接口方法的参数名称
  • 三、总结
    • 四、深入拓展
      • 1、从字节码说起
      • 2、看看普通类在不同参数编译下的.class字节码里面都有什么
      • 3、看看接口在不同参数编译下的.class字节码里面都有什么
    • 五、结束

      前言 在使用 Spring MVC 写Controller的时候,即使不使用注解,只要参数名和请求参数的key对应上了,就能自动完成数值的封装。
      但是在使用 Mybatis框架写接口方法向xml里的SQL语句传参时,必须使用@Param('')指定key值,在SQL中才可以取到。
      Spring可以做到,难道Mybatis做不到吗?难道Mybatis技术不行?

      一、Spring是如何获取方法参数名称的? Spring框架自己写了一个工具类,用来专门获取方法参数名称,DefaultParameterNameDiscoverer类是一个聚合类,维护了一个LinkedList集合,里面的是真正处理的业务的类对象。
      真正处理获取方法参数名称有三种情况:
      • 一种是处理Kotlin的情况(KotlinReflectionParameterNameDiscoverer)
      • 一种是通过java的反射方式获取(StandardReflectionParameterNameDiscoverer)
      • 最后是通过ASM字节码方式从LocalVariableTable中获取(LocalVariableTableParameterNameDiscoverer)
      public class DefaultParameterNameDiscoverer extends PrioritizedParameterNameDiscoverer {默认添加几个参数名称获取的工具 public DefaultParameterNameDiscoverer() {if (KotlinDetector.isKotlinReflectPresent() && !GraalDetector.inImageCode()) {addDiscoverer(new KotlinReflectionParameterNameDiscoverer()); }/// add 会添加到一个 LinkedList 集合中,是一个有序的集合///所以这里也就是按照优先级进行添加的StandardReflectionParameterNameDiscoverer 这个类要求JDK1.8以上的版本,且编译要加上 -parameters 参数///其实就是调用了method.getParameters() 方法addDiscoverer(new StandardReflectionParameterNameDiscoverer()); /// LocalVariableTableParameterNameDiscoverer 没有jdk版本要求,// 是通过ASM提供的通过字节码获取方法的参数名称// 但是依赖 javac 编译的时候 添加上 -g 参数addDiscoverer(new LocalVariableTableParameterNameDiscoverer()); }}


      Spring获取参数名称的两种方式
      1、StandardReflectionParameterNameDiscoverer
      这种是通过java的反射来后去参数名称的。但是通过反射获取方法参数名称是有前置条件的:
      1、要求jdk8以上
      2、编译的时候,必须加-parameters参数,例如:javac -parameters xxxx.java
      只有满足以上条件编译出来的.class,才能通过反射获取到方法参数的名称。
      2、LocalVariableTableParameterNameDiscoverer
      这种是通过ASM框架,解析字节码文件来得到方法参数名称的。同样,此种方法也有前置条件:
      1、编译的时候,必须添加-g参数,javac -g xxxx.java
      两种方式对比
      可以看出来,spring会先采用StandardReflectionParameterNameDiscoverer尝试去获取参数名称
      如果获取不到就尝试通过LocalVariableTableParameterNameDiscoverer后去参数名称。
      通过java反射获取参数名称的方法,前置条件比较严格,但是获取方式比较简单,直接通过反射的api调用就行,不依赖其他三方框架。
      而ASM解析字节码方式的前置条件相对比较宽松,只需要编译的时候添加 -g参数就行,缺点就是依赖于ASM框架。(Spring已经把asm框架通过源码的形式加入到spring框架中了,所以不需要单独在去引用asm框架)
      通常,我们
      聊聊在获取方法参数名方面|聊聊在获取方法参数名方面,Spring真的就比Mybatis强?
      文章图片


      二、Mybatis为什么没有向Spring学习?
      Mybatis要获取的是接口方法的参数名称
      Mybatis团队难道不知道ASM技术吗?这个肯定不是。
      其实真正原因在于,Mybatis框架需要获取的是接口的方法参数名称,而Spring需要获取的是类的方法的参数名称。这是类方法和接口方法是完全不一样的两个东西。
      Java 要获取接口或者抽象方法的参数的名称,必须的是JDK8以上,而且编译的时候加上-parameters参数,只有这种情况下编译出来的.class才能获取到参数的名称。无论你是通过java反射还是asm字节码技术,前面两个条件必须同时满足。
      所以当达到上述两个条件后,直接通过java的反射就可以直接获取参数名称,根本没必要通过asm技术去获取。
      所以!Mybatis不是拿不到参数名称,而是必须要 jdk8 以上而且还得是-parameters编译才可以,当满足这两个条件的时候,你也可以不加@Param('')注解。

      三、总结 最后总结一下:
      SpringMVC也不是什么时候都可以在不借助注解的情况下获取到参数名称,完成自动绑定的,只不过是要到达的前置条件比较宽松,需要编译的时候添加-g参数,而-g这个参数一般编译的时候都会加上,因为有了这个参数,在生成的class文件中,添加具体的 line,source,vars 信息。在输出日志的时候,才能看到行号等信息,如果发生错误了,就很容易定位。一般在idea这种开发工具中运行代码的时候,都是默认有这个参数的。所以你再idea写测试代码的时候,通过asm总是能获取到方法的参数名称,及时没有设置-g参数,那是因为开发工具编译的时候,就默认给你添加了。
      不信的话,你可以手动编译一个java文件,只通过javac 不添加任何参数,然后运行,asm框架也获取不到方法参数名称。
      maven打包好像是默认都会加-g参数,所以你碰到的绝大多数情况,下通过asm都是可以获取到参数名称的,给人一种asm一定能获取参数名称的错觉。
      所以在使用Spring框架的时候,一般不用加注解,通常情况下框架也是可以拿到参数名称的。
      Mybatis,一般情况下都要加注解@Param,是因为,Mybatis需要获取的是接口的方法参数名称,要想拿到接口方法的参数名称的话,就必须在jdk8以上的环境下,而且编译必须加-parameters参数的时候才能获取到,这种情况一般生产环境下不会有-parameters参数,所以在写Mybatis的接口的时候最好加上@Param参数,以保证程序正常运行。

      四、深入拓展
      1、从字节码说起
      java源文件经过java编译器编译为.class字节码文件,之后才能被JVM执行,所以我们可以获取到的信息都存在.class文件中,但是编译器编译.class的的文件默认不保留方法参数名。所以如果只是通过javac命令不添加任何参数情况下,编译出来的.class文件,是无论如何也不能获取到方法的参数名称的。

      2、看看普通类在不同参数编译下的.class字节码里面都有什么
      下面我们通过javac编译一个类,然后通过javap 看看里面的内容都有什么!
      public class TestBean {public String myMethod(String helloWord){return ""; }}

      我们分别用三种方式进行编译,然后通过javap -verbose TestBean.class 进行查看
      javac TestBean.javajavac -g TestBean.javajavac -parameters TestBean.java

      聊聊在获取方法参数名方面|聊聊在获取方法参数名方面,Spring真的就比Mybatis强?
      文章图片

      从上面截图可以看出来:
      添加-g参数后,.class会多出LocalVariableTable的信息,里面可以看到有方法参数的名字“helloWord”。
      添加-parameters参数后,.class会多出MethodParameters的信息,里面也可以看到有方法参数的名字“helloWord”。
      而javac不加任何参数的话,则在任何地方都找不到方法参数名“helloword”的信息。
      所以:也验证了前面所说的,默认情况下,无论是asm还是java反射都是无法获取方法参数名称的,因为字节码里面就没有参数名的信息。
      但是如果是有-g参数编译的话,字节码里会在LocalVariableTable将方法的参数名称进行保存,而ASM框架是基于字节码技术的,所以是可以解析出参数名称的。但是java的反射API并未提供获取LocalVariableTable信息的内容,所以-g产生的信息,只能通过自己解析字节码获取,所以asm是可以做到的。
      如果是有-parameters参数编译的话,字节码里面保存MethodParameters信息,里面就是方法参数名称的信息,java的反射api提供的接口可以直接获取到里面的信息。但是-parameters 是jdk8以后才提供的新特性,所以要想通过反射api获取的话,只能是jdk8及以上版本通过添加-parameters参数才可以。

      3、看看接口在不同参数编译下的.class字节码里面都有什么
      先写一个普通的接口
      public interface TestInterfaceBean {public String testInterfaceMethod (String iName); }

      然后再用三种方式进行编译,最后通过javap -verbose TestInterfaceBean.class 进行查看
      javac TestInterfaceBean.javajavac -g TestInterfaceBean.javajavac -parameters TestInterfaceBean.java

      聊聊在获取方法参数名方面|聊聊在获取方法参数名方面,Spring真的就比Mybatis强?
      文章图片

      从上面三张截图可以看出来,只有 javac -parameters 编译出来的.class字节码文件才带有 接口方法参数名的信息,-g 编译出来的和默认的都是没有的。
      所以嘛,对于接口而言,只有通过-parameters 编译的字节码才能有方法参数名称。这也就明白了为什么Mybatis的接口要加注解了吧,因为你如果不通过注解信息去获取的话,你不论是java反射也好,asm也好都拿不到参数名称,除非你编译的时候添加-parameters,而生产环境通常是不会加这个参数的。

      五、结束 看到这里应该弄清楚,获取参数名称的套路了吧!
      【聊聊在获取方法参数名方面|聊聊在获取方法参数名方面,Spring真的就比Mybatis强?】以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。

        推荐阅读