【方向盘】为何Spring MVC可获取到方法参数名,而MyBatis却不行?(上)

简介: 【方向盘】为何Spring MVC可获取到方法参数名,而MyBatis却不行?(上)

前言


Spring MVC和MyBatis作为当下最为流行的两个框架,大家平时开发中都在用。如果你往深了一步去思考,你应该会有这样的疑问:


  • 在使用Spring MVC的时候,你即使不使用注解,只要参数名和请求参数的key对应上了,就能自动完成数值的封装
  • 在使用MyBatis(接口模式)时,接口方法向xml里的SQL语句传参时,必须(当然不是100%的必须,特殊情况此处不做考虑)使用@Param('')指定key值,在SQL中才可以取到


我敢相信这绝不是我一个人的疑问,因为我在第一次使用MyBatis的时候就产生过这个疑问并且也尝试过去掉@Param注解,因为我觉得一个名称让我写两次是有点多此一举的(我太懒了)。

和Spring MVC人性化处理比起来,当时觉得MyBatis对这块的处理简直弱爆了。费解了这么长时间,今天我终于可以解释这个现象了,来揭开它的面纱~


问题发现


java使用者都知道,.java文件属于源码文件,它需要经过了javac编译器编译为.class字节码文件才能被JVM执行的。

对.class字节码稍微有点了解的小伙伴应该也知道这一点:Java在编译的时候对于方法,默认是不会保留方法参数名,因此如果我们在运行期想从.class字节码里直接拿到方法的参数名是做不到的。


如下案例,很明显就是获取不到真实参数名喽:

public static void main(String[] args) throws NoSuchMethodException {
    Method method = Main.class.getMethod("test1", String.class, Integer.class);
    int parameterCount = method.getParameterCount();
    Parameter[] parameters = method.getParameters();
    // 打印输出:
    System.out.println("方法参数总数:" + parameterCount);
    Arrays.stream(parameters).forEach(p -> System.out.println(p.getType() + "----" + p.getName()));
}


打印内容:

方法参数总数:2
class java.lang.String----arg0
class java.lang.Integer----arg1


从结果中可以看到我们并不能获取到真实方法参数名(获取到的是无意义的arg0、arg1等),这个结果符合我们的理论知识以及预期。

若你有一定技术敏感性,这个时候你应该有这样的疑问:在使用Spring MVC的时候,Controller的方法中不使用注解一样可以自动封装啊,形如这样:

@GetMapping("/test")
public Object test(String name, Integer age) {
    String value = name + "---" + age;
    System.out.println(value);
    return value;
}


请求:/test?name=fsx&age=18。控制台输出:

fsx---18


从结果中可见:看似办不到的case,Spring MVC竟然给做到了(获取到了方法参数名,进而完成封装),是不是有点不可思议???


再看此例(还原Spring MVC获取参数名的场景):


public static void main(String[] args) throws NoSuchMethodException {
    Method method = Main.class.getMethod("test1", String.class, Integer.class);
    MethodParameter nameParameter = new MethodParameter(method, 0);
    MethodParameter ageParameter = new MethodParameter(method, 1);
    // 打印输出:
    // 使用Parameter输出
    Parameter nameOriginParameter = nameParameter.getParameter();
    Parameter ageOriginParameter = ageParameter.getParameter();
    System.out.println("===================源生Parameter结果=====================");
    System.out.println(nameOriginParameter.getType() + "----" + nameOriginParameter.getName());
    System.out.println(ageOriginParameter.getType() + "----" + ageOriginParameter.getName());
    System.out.println("===================MethodParameter结果=====================");
    System.out.println(nameParameter.getParameterType() + "----" + nameParameter.getParameterName());
    System.out.println(ageParameter.getParameterType() + "----" + ageParameter.getParameterName());
    System.out.println("==============设置上ParameterNameDiscoverer后MethodParameter结果===============");
    ParameterNameDiscoverer parameterNameDiscoverer = new DefaultParameterNameDiscoverer();
    nameParameter.initParameterNameDiscovery(parameterNameDiscoverer);
    ageParameter.initParameterNameDiscovery(parameterNameDiscoverer);
    System.out.println(nameParameter.getParameterType() + "----" + nameParameter.getParameterName());
    System.out.println(ageParameter.getParameterType() + "----" + ageParameter.getParameterName());
}


输出结果:


===================源生Parameter结果=====================
class java.lang.String----arg0
class java.lang.Integer----arg1
===================MethodParameter结果=====================
class java.lang.String----null
class java.lang.Integer----null
==============设置上ParameterNameDiscoverer后MethodParameter结果===============
class java.lang.String----name
class java.lang.Integer----age


从结果能看出来:Spring MVC借助ParameterNameDiscoverer完成了方法参数名的获取,进而完成数据封装。关于ParameterNameDiscoverer它的讲解,可先行参阅:【小家Spring】Spring标准处理组件大合集(ParameterNameDiscoverer、AutowireCandidateResolver、ResolvableType。。。)


该问介绍了ParameterNameDiscoverer的基本使用和提供的能力,但并没有深入分析。那么本文就分析为何Spring MVC为何可以正确的解析到方法参数名称这个问题,从字节码角度深入分析其缘由~


为了便于理解,先简单说说字节码中的两个概念:LocalVariableTable和LineNumberTable。它哥俩经常被拿出来一起说,当然本文关注的焦点是LocalVariableTable,但也借此机会一笔带过LineNumberTable。


LineNumberTable


你是否曾经疑问过:线上程序抛出异常时显示的行号,为啥就恰好就是你源码的那一行呢?有这疑问是因为JVM执行的是.class文件,而该文件的行和.java源文件的行肯定是对应不上的,为何行号却能在.java文件里对应上?

这就是LineNumberTable它的作用了:LineNumberTable属性存在于代码(字节码)属性中, 它建立了字节码偏移量到源代码行号之间的联系


LocalVariableTable


LocalVariableTable属性建立了方法中的局部变量与源代码中的局部变量之间的对应关系。这个属性也是存在于代码(字节码)中~

从名字可以看出来:它是局部变量的一个集合。描述了局部变量和描述符以及和源代码的对应关系。


下面我使用javac和javap命令来演示一下这个情况:

.java源码如下:


package com.fsx.maintest;
public class MainTest2 {
    public String testArgName(String name,Integer age){
        return null;
    }
}


说明:源码我都是顶头写的,所以请注意行号~


使用javac MainTest2.java编译成.class字节码,然后使用javap -verbose MainTest2.class查看该字节码信息如下:


image.png


从图中可看到,我红色标注出的行号和源码处完全一样,这就解答了我们上面的行号对应的疑问了:LineNumberTable它记录着在源代码处的行号。

Tips:此处并没有,并没有,并没有LocalVariableTable。


源码不变,我使用javac -g MainTest2.java来编译,再看看对应的字节码信息如下(注意和上面的区别):


image.png


这里多了一个LocalVariableTable,即局部变量表,就记录着我们方法入参的形参名字。既然记录着了,这样我们就可以通过分析字节码信息来得到这个名称了~


说明:javac的调试选项主要包含了三个子选项:lines,source,vars

如果不使用-g来编译,只保留源文件和行号信息;如果使用-g来编译那就都有了~


和-parameters有什么区别??


知道-g编译参数的少,反倒对Java8新推出的-parameters知道的人更多一些。那么它和-g参数有什么区别呢???


百闻不如一见,我比较喜欢自己搞个例子来说明问题,.java源代码如下:


import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
public class MainTest2 {
    public static void main(String[] args) throws NoSuchMethodException {
        Method method = MainTest2.class.getMethod("testArgName", String.class, Integer.class);
        System.out.println("paramCount:" + method.getParameterCount());
        for (Parameter parameter : method.getParameters()) {
            System.out.println(parameter.getType().getName() + "-->" + parameter.getName());
        }
    }
    public String testArgName(String name, Integer age) {
        return null;
    }
}


下面分别用javac、javac -g、javac -parameters来编译后再执行,结果图如下:


image.png


从分别编译、再运行打印的结果图中看,结果以及他们的区别已经很清晰了,我就不再笔墨,有疑问的可以给我留言。


另外附上-parameters编译后的字节码信息,方便你做分析对比:


image.png




相关文章
|
27天前
|
前端开发 Java Spring
Spring MVC源码分析之DispatcherServlet#getHandlerAdapter方法
`DispatcherServlet`的 `getHandlerAdapter`方法是Spring MVC处理请求的核心部分之一。它通过遍历预定义的 `HandlerAdapter`列表,找到适用于当前处理器的适配器,并调用适配器执行具体的处理逻辑。理解这个方法有助于深入了解Spring MVC的工作机制和扩展点。
31 1
|
1月前
|
存储 安全 Java
|
28天前
|
前端开发 Java Spring
Spring MVC源码分析之DispatcherServlet#getHandlerAdapter方法
`DispatcherServlet`的 `getHandlerAdapter`方法是Spring MVC处理请求的核心部分之一。它通过遍历预定义的 `HandlerAdapter`列表,找到适用于当前处理器的适配器,并调用适配器执行具体的处理逻辑。理解这个方法有助于深入了解Spring MVC的工作机制和扩展点。
27 1
|
1月前
|
JSON 前端开发 Java
SSM:SpringMVC
本文介绍了SpringMVC的依赖配置、请求参数处理、注解开发、JSON处理、拦截器、文件上传下载以及相关注意事项。首先,需要在`pom.xml`中添加必要的依赖,包括Servlet、JSTL、Spring Web MVC等。接着,在`web.xml`中配置DispatcherServlet,并设置Spring MVC的相关配置,如组件扫描、默认Servlet处理器等。然后,通过`@RequestMapping`等注解处理请求参数,使用`@ResponseBody`返回JSON数据。此外,还介绍了如何创建和配置拦截器、文件上传下载的功能,并强调了JSP文件的放置位置,避免404错误。
|
24天前
|
前端开发 Java Spring
Spring MVC源码分析之DispatcherServlet#getHandlerAdapter方法
`DispatcherServlet`的 `getHandlerAdapter`方法是Spring MVC处理请求的核心部分之一。它通过遍历预定义的 `HandlerAdapter`列表,找到适用于当前处理器的适配器,并调用适配器执行具体的处理逻辑。理解这个方法有助于深入了解Spring MVC的工作机制和扩展点。
22 0
|
1月前
|
前端开发 Java 应用服务中间件
【Spring】Spring MVC的项目准备和连接建立
【Spring】Spring MVC的项目准备和连接建立
57 2
|
2月前
|
缓存 前端开发 Java
【Java面试题汇总】Spring,SpringBoot,SpringMVC,Mybatis,JavaWeb篇(2023版)
Soring Boot的起步依赖、启动流程、自动装配、常用的注解、Spring MVC的执行流程、对MVC的理解、RestFull风格、为什么service层要写接口、MyBatis的缓存机制、$和#有什么区别、resultType和resultMap区别、cookie和session的区别是什么?session的工作原理
【Java面试题汇总】Spring,SpringBoot,SpringMVC,Mybatis,JavaWeb篇(2023版)
|
1月前
|
XML 前端开发 Java
Spring,SpringBoot和SpringMVC的关系以及区别 —— 超准确,可当面试题!!!也可供零基础学习
本文阐述了Spring、Spring Boot和Spring MVC的关系与区别,指出Spring是一个轻量级、一站式、模块化的应用程序开发框架,Spring MVC是Spring的一个子框架,专注于Web应用和网络接口开发,而Spring Boot则是对Spring的封装,用于简化Spring应用的开发。
134 0
Spring,SpringBoot和SpringMVC的关系以及区别 —— 超准确,可当面试题!!!也可供零基础学习
|
2月前
|
Java 应用服务中间件 Spring
IDEA 工具 启动 spring boot 的 main 方法报错。已解决
IDEA 工具 启动 spring boot 的 main 方法报错。已解决
|
2月前
|
XML 缓存 前端开发
springMVC02,restful风格,请求转发和重定向
文章介绍了RESTful风格的基本概念和特点,并展示了如何使用SpringMVC实现RESTful风格的请求处理。同时,文章还讨论了SpringMVC中的请求转发和重定向的实现方式,并通过具体代码示例进行了说明。
springMVC02,restful风格,请求转发和重定向
下一篇
无影云桌面