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

前言

在使用 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,而生产环境通常是不会加这个参数的。

五、结束

看到这里应该弄清楚,获取参数名称的套路了吧!

以上为个人经验,希望能给大家一个参考,也希望大家多多支持。

以上是 聊聊在获取方法参数名方面,Spring真的就比Mybatis强? 的全部内容, 来源链接: utcz.com/p/251563.html

回到顶部