前言
在使用 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框架)
通常,我们
二、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.java
javac -g TestBean.java
javac -parameters TestBean.java
从上面截图可以看出来:
添加-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.java
javac -g TestInterfaceBean.java
javac -parameters TestInterfaceBean.java
从上面三张截图可以看出来,只有 javac -parameters 编译出来的.class字节码文件才带有 接口方法参数名的信息,-g 编译出来的和默认的都是没有的。
所以嘛,对于接口而言,只有通过-parameters 编译的字节码才能有方法参数名称。这也就明白了为什么Mybatis的接口要加注解了吧,因为你如果不通过注解信息去获取的话,你不论是java反射也好,asm也好都拿不到参数名称,除非你编译的时候添加-parameters,而生产环境通常是不会加这个参数的。
五、结束
看到这里应该弄清楚,获取参数名称的套路了吧!
以上为个人经验,希望能给大家一个参考,也希望大家多多支持编程网。