【方向盘】为何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




相关文章
|
7天前
|
Java 数据库连接 数据库
spring复习05,spring整合mybatis,声明式事务
这篇文章详细介绍了如何在Spring框架中整合MyBatis以及如何配置声明式事务。主要内容包括:在Maven项目中添加依赖、创建实体类和Mapper接口、配置MyBatis核心配置文件和映射文件、配置数据源、创建sqlSessionFactory和sqlSessionTemplate、实现Mapper接口、配置声明式事务以及测试使用。此外,还解释了声明式事务的传播行为、隔离级别、只读提示和事务超时期间等概念。
spring复习05,spring整合mybatis,声明式事务
|
10天前
|
Java 数据库连接 数据库
SpringBoot 整合jdbc和mybatis
本文详细介绍了如何在SpringBoot项目中整合JDBC与MyBatis,并提供了具体的配置步骤和示例代码。首先,通过创建用户实体类和数据库表来准备基础环境;接着,配置Maven依赖、数据库连接及属性;最后,分别展示了JDBC与MyBatis的集成方法及其基本操作,包括增删查改等功能的实现。适合初学者快速入门。
SpringBoot 整合jdbc和mybatis
|
19天前
|
缓存 前端开发 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版)
|
8天前
|
Java 应用服务中间件 Spring
IDEA 工具 启动 spring boot 的 main 方法报错。已解决
IDEA 工具 启动 spring boot 的 main 方法报错。已解决
|
6天前
|
XML Java 关系型数据库
springboot 集成 mybatis-plus 代码生成器
本文介绍了如何在Spring Boot项目中集成MyBatis-Plus代码生成器,包括导入相关依赖坐标、配置快速代码生成器以及自定义代码生成器模板的步骤和代码示例,旨在提高开发效率,快速生成Entity、Mapper、Mapper XML、Service、Controller等代码。
springboot 集成 mybatis-plus 代码生成器
|
7天前
|
SQL XML Java
springboot整合mybatis-plus及mybatis-plus分页插件的使用
这篇文章介绍了如何在Spring Boot项目中整合MyBatis-Plus及其分页插件,包括依赖引入、配置文件编写、SQL表创建、Mapper层、Service层、Controller层的创建,以及分页插件的使用和数据展示HTML页面的编写。
springboot整合mybatis-plus及mybatis-plus分页插件的使用
|
7天前
|
XML Java 数据库连接
springboot中整合mybatis及简单使用
这篇文章介绍了如何在Spring Boot项目中整合MyBatis,包括依赖引入、配置数据源、创建测试表、编写Mapper接口和XML文件、以及创建Service和Controller层的步骤。
springboot中整合mybatis及简单使用
|
7天前
|
XML 缓存 前端开发
springMVC02,restful风格,请求转发和重定向
文章介绍了RESTful风格的基本概念和特点,并展示了如何使用SpringMVC实现RESTful风格的请求处理。同时,文章还讨论了SpringMVC中的请求转发和重定向的实现方式,并通过具体代码示例进行了说明。
springMVC02,restful风格,请求转发和重定向
|
20天前
|
Java 数据库连接 数据格式
【Java笔记+踩坑】Spring基础2——IOC,DI注解开发、整合Mybatis,Junit
IOC/DI配置管理DruidDataSource和properties、核心容器的创建、获取bean的方式、spring注解开发、注解开发管理第三方bean、Spring整合Mybatis和Junit
【Java笔记+踩坑】Spring基础2——IOC,DI注解开发、整合Mybatis,Junit
|
25天前
|
前端开发 JavaScript Java
技术分享:使用Spring Boot3.3与MyBatis-Plus联合实现多层次树结构的异步加载策略
在现代Web开发中,处理多层次树形结构数据是一项常见且重要的任务。这些结构广泛应用于分类管理、组织结构、权限管理等场景。为了提升用户体验和系统性能,采用异步加载策略来动态加载树形结构的各个层级变得尤为重要。本文将详细介绍如何使用Spring Boot3.3与MyBatis-Plus联合实现这一功能。
57 2
下一篇
无影云桌面