对Java字节码有一定了解的朋友应该知道,Java 在编译的时候,默认不会保留方法参数名,因此我们无法在运行时获取参数名称。但是在使用 SpringMVC 的时候,我发现一个奇怪的现象:当我们需要接收请求参数的时候,相应的 Controller 方法只需要正常声明,就可以直接接收正确的参数,例如:

注:以下例子使用 maven 进行编译,且非 SpringBoot 项目,SpringBoot 已经自动解决了参数名解析的问题,后面咱们会讨论

@RestController@RequestMapping("calculator")public class CalculatorController { @GetMapping("add") public int add(int aNum, int bNum) { return aNum + bNum; }}

当接收到 /calculator/add?aNum=12&bNum=3 这样的请求时,会返回 15,即aNum 和 bNum 都能被正确解析。

然而,当我们使用 MyBatis 时,如果接口方法有多个参数而且我们没有打上 @Param 注解的话,执行的时候就会报错。例如,我们有如下的接口:

@Mapper

public interface AccountMapper {

Account getByNameAndMobilePhone(String name, String mobilePhone);

}

方法中包含两个参数,但是没有打上 @Param 注解,这时候如果调用这个方法,会报错:

org.apache.ibatis.binding.BindingException: Parameter 'name' not found.

Available parameters are [arg1, arg0, param1, param2]

从错误信息中可以看出,是因为 MyBatis 没有正确解析方法参数名称导致异常。

这就很奇怪了,为什么 Spring 可以正确解析方法参数名称,但是 MyBatis 却不行?Java编译的时候默认会将方法参数名抹除,但我并没有做特殊处理,Spring 又是从哪里找到方法参数名的呢?

带着这些问题,我开始进行研究和探索。

# 获取参数名的方式

通过查阅各种资料,我知道了获取参数名称的方式。

-g 参数

当我们对 Java 源码进行编译时,无论是直接使用命令行还是使用 IDE 为我们编译,实际上最终都是调用 javac 命令进行的,在编译的时候,我们如果添加上 -g 参数,即告诉编译器,我们需要调试信息,这时,生成的字节码当中就会包含局部变量表的信息(方法参数也是局部变量),于是我们就可以通过解析字节码获取参数名了。

我们用最最经典的 HelloWorld 程序中的 main 方法为例,看一下编译的效果:

public class HelloWorld{ public static void main(String[] argsName){ System.out.println("HelloWorld!"); }}

我们直接执行如下 javac 命令来编译并使用 javap 命令查看生成的字节码信息:

javac HelloWorld.javajavap -verbose HelloWorld.class

可以看到,我们的参数名 argsName 已经被抹掉了。而如果字节码中都没有我们所需要的信息,那么在运行时,反射或者是别的方法也都无能为力了,巧妇难为无米之炊呐。

接下来,我们试一下添加 -g 参数会发生什么:

javac -g HelloWorld.javajavap -verbose HelloWorld.class

可以看到,这里多了一个 LocalVariableTable,即局部变量表,其中就有我们的参数名称 argsName!

那么,我们如何在方法运行时从字节码信息中获取参数名称呢?你可以直接通过 javap 来获取字节码信息,然后自己去根据信息的格式去解析,然而这样太低效了,而且太繁琐了。

# ASM 框架

这时候如果我们请大名鼎鼎的 ASM 来当“导游”,带着我们游览字节码内部构造,实现起来就轻松多了。

这个 ASM 可牛了,它不仅可以查看字节码的信息,甚至可以动态修改类的定义或者新建一个原本没有的类!在各种框架中被广泛地使用,SpringAOP中使用的 CGLib 底层就是使用 ASM 来实现的。有兴趣可以查看官网:https://asm.ow2.io/ 之前我也写过一篇文章《Java用ASM写一个HelloWorld程序》,有兴趣可以看一下。

言归正传,如何通过 ASM 来获取参数名称呢? 直接上代码:

asm asm 3.3.1/*** 使用字节码工具ASM来获取方法的参数名*/public static String[] getMethodParamNames(final Method method) throws IOException { final int methodParameterCount = method.getParameterTypes().length; final String[] methodParametersNames = new String[methodParameterCount]; ClassReader cr = new ClassReader(method.getDeclaringClass().getName()); ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);  cr.accept(new ClassAdapter(cw) {  @Override public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {  MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions); final Type[] argTypes = Type.getArgumentTypes(desc);  //参数类型不一致 if (!method.getName().equals(name) || !matchTypes(argTypes, method.getParameterTypes())) { return mv; }  return new MethodAdapter(mv) { @Override public void visitLocalVariable(String name, String desc, String signature, Label start, Label end, int index) {  //如果是静态方法,第一个参数就是方法参数,非静态方法,则第一个参数是 this, 然后才是方法的参数 int methodParameterIndex = Modifier.isStatic(method.getModifiers()) ? index : index - 1; if (0 <= methodParameterIndex && methodParameterIndex < methodParameterCount) { methodParametersNames[methodParameterIndex] = name; } super.visitLocalVariable(name, desc, signature, start, end, index); } }; } }, 0); return methodParametersNames; }  /** * 比较参数是否一致 */private static boolean matchTypes(Type[] types, Class>[] parameterTypes) { if (types.length != parameterTypes.length) { return false; } for (int i = 0; i < types.length; i++) { if (!Type.getType(parameterTypes[i]).equals(types[i])) { return false; } } return true;}

简而言之,ASM使用了访问者模式,它就像一个导游,带着我们去游览字节码文件中的各个“景点”。我们实现不同的 Visitor 接口就像是手上握有不同景点门票,导游会带着 ClassVisitor 去总体参观类定义的景观,而类内部有方法,如果你想看一下方法内部的定义,需要"额外购票",即需要实现 MethodVisitor 才能跟着导游去参观方法定义这个景点。而在游览各个景点的时候,我们可以只游览我们感兴趣的部分,这就可以继承适配器(ClassAdapter和MethodAdapter分别是ClassVisitor和MethodVisitor的适配器)然后只实现我们感兴趣的方法即可。

这里对于类的定义,我们只对方法感兴趣,因此只实现 visitMethod 方法;在方法中,我们只对 LocalVariableTable 有兴趣,因此只实现 visitLocalVariable 方法。这样我们得到了局部变量表,再根据一些规则就可以拿到我们的参数名称了!是不是很棒!

顺便说一下,如果你使用 maven 来管理项目的话,这个 -g 参数会在编译的时候自动加上,因此我们不需要额外添加就可以通过字节码拿到,这也就是为什么 SpringMVC 可以拿到方法参数名称的原因。

但是这种方式对于接口和抽象方法是不管用的,因为抽象方法没有方法体,也就没有局部变量,自然也就没有局部变量表了:

MyBatis 是通过接口跟 SQL 语句绑定然后生成代理类来实现的,因此它无法通过解析字节码来获取方法参数名。

end:如果你觉得本文对你有帮助的话,记得关注点赞转发,你的支持就是我更新动力。

mybatis是什么_为什么SpringMVC可以正确解析方法参数名称,但MyBatis却不行?相关推荐

  1. java接口参数默认值_下面关于setMaxAge(int expires)方法参数默认值的描述中,正确的是(5.0分)_学小易找答案...

    [单选题]下面选项中,用于在web.xml中配置监听器的元素是(5.0分) [填空题]Cookie技术用于将会话过程中的数据保存到( )中,从而使浏览器和服务器可以更好地进行数据交互.(5.0分) [ ...

  2. 安全绳使用方法图解_安全绳的正确使用方法、使用注意事项及应用

    安全绳是一种用于连接安全带的辅助用绳,它是由合成纤维编织而成的,具有二重保护的作用,可以确保人在高空作业时的安全.安全绳的种类很多,每种不同的安全绳作用也是不一样的,今天小编就来为大家介绍一下安全绳的 ...

  3. java request body内容_解读@RequestBody的正确使用方法

    本文主要研究的是关于@RequestBody的正确使用方法的相关内容,具体如下. 最近在接收一个要离职同事的工作,接手的项目是用SpringBoot搭建的,其中看到了这样的写法: @RequestMa ...

  4. logback mysql 中文乱码_[Logback+slf4j]MysqlDBAppender正确配置方法以及错误处理

    第一必要条件:jar 包 所需要的包: logback-core-0.9.8.jar logback-classic-0.9.8.jar slf4j-api-1.6.8.jar 写该文时,最新版本为 ...

  5. 示波器表笔旁边的夹子是什么_示波器探头的正确使用方法?

    展开全部 本文将介绍各种探头的类型和用法 什么是探头: 示波32313133353236313431303231363533e78988e69d8331333433623763器是电子工程师最常用的测 ...

  6. java mvc 绑定_关于Java:Spring MVC:将请求属性绑定到控制器方法参数

    在Spring MVC中,很容易将请求参数绑定到处理请求的方法参数. 我只是使用@RequestParameter("name"). 但是我可以对request属性做同样的事情吗? ...

  7. 【SpringMVC入门】SpringMVC环境搭建、接收参数的几种方式、视图解析器、@ResponseBody

    一.SpringMVC 简介 1.SpringMVC 中重要组件 1.1 DispatcherServlet: 前端控制器,接收所有请求(如果配置/不包含jsp) 1.2 HandlerMapping ...

  8. controller调用controller的方法_你想过 Controller 这些方法里的参数是如何工作的吗?...

    点击上方"方志朋",选择"设为星标" 回复"666"获取新整理的面试文章 前言 SpringMVC是目前主流的Web MVC框架之一.Spr ...

  9. 详解SpringMVC中Controller的方法中参数的工作原理[附带源码分析]

    目录 前言 现象 源码分析 HandlerMethodArgumentResolver与HandlerMethodReturnValueHandler接口介绍 HandlerMethodArgumen ...

最新文章

  1. 【Leetcode】刷题之路3(python版)
  2. 我有点不喜欢分布式中的TCC模式了
  3. HDU 1231 最大连续子序列
  4. Intellij IDEA运行报Command line is too long解法
  5. Pat乙级 1040 有几个PAT
  6. 【LightOJ - 1027】A Dangerous Maze(概率dp,数学期望)
  7. 印花材料使用过程中的三个常见问题及解决方案
  8. 根据录入的计算公式计算_超全的铝材重量计算公式
  9. oracle使用between and边界问题
  10. KEIL C51出现 runtime error R6002 floating point support not loaded解决办法
  11. 氚云无代码搭建学习记录
  12. php实战视频教程 帝国cms二次开发,帝国cms7.5二次开发整合CKPlayer播放器教程
  13. 人类记忆系统之谜,或许是这样一回事
  14. 安卓CameraX基于虹软人脸识别程序开发
  15. 天大、中南、中山、北师、中科院地图学与地理信息系统GIS及遥感RS专业推免夏令营面试经历与题目汇总
  16. 智明星通 CEO 唐彬森:创业过程中的几笔学费
  17. 大白话给你讲明白UDS诊断(汽车诊断服务 实例应用图文讲解)(一)
  18. Symbian 开发知识
  19. lxc(1):lxc安装
  20. Scratch案例——彩色蜘蛛网

热门文章

  1. OkapiBarcode生成条形码
  2. Zookeeper在yarn框架中如何实现避免脑裂的?
  3. Visual Studio 2017 15.5预览版添加对F# Core及Standard的支持
  4. vagrant系列四:vagrant搭建redis与redis的监控程序redis-stat
  5. C++11 POD类型
  6. Spring4.3+Webscket 实现聊天、消息推送详解之具体实现(三)
  7. PHP 标准AES加密算法类
  8. MDT2010 Client PE 镜象通过PXELinux 或PXEGrub来启动
  9. 一步一步学Ruby(十一):控制语句
  10. windows2008下 IIS7 HTTP 错误 404.2 - Not Found 解决方法(图文)