Spring mvc的参数究竟是如何绑定的

Stella981
• 阅读 774

在使用Spring mvc的时候我们会被他优雅的参数绑定所吸引,再也不用写那么多getParameter也不用像struts2中写一堆get、set给开发者带来了很大的便利。

/**
 *注册
 */
@RequestMapping(value = "/register", method = RequestMethod.POST)
public Object register(String userName, String pwd, String rePwd) {}

其中userNamepwdrePwd均来自页面input表单。但是这些参数究竟是怎么注入的呢?小伙伴肯定都想到了方法参数名。下面我们来看一个字节码实验! ##我们来看看2份javap的到的字节码。 ###首先看java源码:

public class Test {
    public void action(String userName, int age) {
        System.out.println(userName);
        System.out.println(age);
    }
}

注:javap -v Test ###eclipse下编译得到的字节码:

public class Test
  minor version: 0
  major version: 51
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Class              #2             // Test
   #2 = Utf8               Test
   #3 = Class              #4             // java/lang/Object
   #4 = Utf8               java/lang/Object
   #5 = Utf8               <init>
   #6 = Utf8               ()V
   #7 = Utf8               Code
   #8 = Methodref          #3.#9          // java/lang/Object."<init>":()V
   #9 = NameAndType        #5:#6          // "<init>":()V
  #10 = Utf8               LineNumberTable
  #11 = Utf8               LocalVariableTable
  #12 = Utf8               this
  #13 = Utf8               LTest;
  #14 = Utf8               action
  #15 = Utf8               (Ljava/lang/String;I)V
  #16 = Fieldref           #17.#19        // java/lang/System.out:Ljava/io/PrintStream;
  #17 = Class              #18            // java/lang/System
  #18 = Utf8               java/lang/System
  #19 = NameAndType        #20:#21        // out:Ljava/io/PrintStream;
  #20 = Utf8               out
  #21 = Utf8               Ljava/io/PrintStream;
  #22 = Methodref          #23.#25        // java/io/PrintStream.println:(Ljava/lang/String;)V
  #23 = Class              #24            // java/io/PrintStream
  #24 = Utf8               java/io/PrintStream
  #25 = NameAndType        #26:#27        // println:(Ljava/lang/String;)V
  #26 = Utf8               println
  #27 = Utf8               (Ljava/lang/String;)V
  #28 = Methodref          #23.#29        // java/io/PrintStream.println:(I)V
  #29 = NameAndType        #26:#30        // println:(I)V
  #30 = Utf8               (I)V
  #31 = Utf8               userName
  #32 = Utf8               Ljava/lang/String;
  #33 = Utf8               age
  #34 = Utf8               I
  #35 = Utf8               SourceFile
  #36 = Utf8               Test.java
{
  public Test();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #8                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 2: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   LTest;

  public void action(java.lang.String, int);
    descriptor: (Ljava/lang/String;I)V
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=3, args_size=3
         0: getstatic     #16                 // Field java/lang/System.out:Ljava/io/PrintStream;
         3: aload_1
         4: invokevirtual #22                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
         7: getstatic     #16                 // Field java/lang/System.out:Ljava/io/PrintStream;
        10: iload_2
        11: invokevirtual #28                 // Method java/io/PrintStream.println:(I)V
        14: return
      LineNumberTable:
        line 5: 0
        line 6: 7
        line 7: 14
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      15     0  this   LTest;
            0      15     1 userName   Ljava/lang/String;
            0      15     2   age   I
}

用javac编译

public class Test
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #6.#15         // java/lang/Object."<init>":()V
   #2 = Fieldref           #16.#17        // java/lang/System.out:Ljava/io/PrintStream;
   #3 = Methodref          #18.#19        // java/io/PrintStream.println:(Ljava/lang/String;)V
   #4 = Methodref          #18.#20        // java/io/PrintStream.println:(I)V
   #5 = Class              #21            // Test
   #6 = Class              #22            // java/lang/Object
   #7 = Utf8               <init>
   #8 = Utf8               ()V
   #9 = Utf8               Code
  #10 = Utf8               LineNumberTable
  #11 = Utf8               action
  #12 = Utf8               (Ljava/lang/String;I)V
  #13 = Utf8               SourceFile
  #14 = Utf8               Test.java
  #15 = NameAndType        #7:#8          // "<init>":()V
  #16 = Class              #23            // java/lang/System
  #17 = NameAndType        #24:#25        // out:Ljava/io/PrintStream;
  #18 = Class              #26            // java/io/PrintStream
  #19 = NameAndType        #27:#28        // println:(Ljava/lang/String;)V
  #20 = NameAndType        #27:#29        // println:(I)V
  #21 = Utf8               Test
  #22 = Utf8               java/lang/Object
  #23 = Utf8               java/lang/System
  #24 = Utf8               out
  #25 = Utf8               Ljava/io/PrintStream;
  #26 = Utf8               java/io/PrintStream
  #27 = Utf8               println
  #28 = Utf8               (Ljava/lang/String;)V
  #29 = Utf8               (I)V
{
  public Test();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 2: 0

  public void action(java.lang.String, int);
    descriptor: (Ljava/lang/String;I)V
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=3, args_size=3
         0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
         3: aload_1
         4: invokevirtual #3                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
         7: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
        10: iload_2
        11: invokevirtual #4                  // Method java/io/PrintStream.println:(I)V
        14: return
      LineNumberTable:
        line 5: 0
        line 6: 7
        line 7: 14
}

可以看出eclipse下多了LocalVariableTable这样一段,这一段就是记录的我们方法的参数名。

LocalVariableTable:
Start  Length  Slot  Name   Signature
    0      15     0  this   LTest;
    0      15     1 userName   Ljava/lang/String;
    0      15     2   age   I

LocalVariableTable属性建立了方法中的局部变量与源代码中的局部变量之间的对应关系。这个属性存在于Code属性中。这个属性是可选的,编译器可以选择不生成这个属性。 这个参数是为了让编译器在代码提示时,可以提示出友好的参数名。 ##我们再来翻翻Spring的源代码 参数绑定的代码位于Spring-web org.springframework.web.method.support.InvocableHandlerMethod.java中, 有兴趣的同学请查看invokeForRequestgetMethodArgumentValuesdoInvoke三个方法!

我们继续追踪,追踪到了DefaultParameterNameDiscoverer这个类!

/**
 * Default implementation of the {link ParameterNameDiscoverer} strategy interface,
 * using the Java 8 standard reflection mechanism (if available), and falling back
 * to the ASM-based {link LocalVariableTableParameterNameDiscoverer} for checking
 * debug information in the class file.
 *
 * Further discoverers may be added through {link #addDiscoverer(ParameterNameDiscoverer)}.
 *
 * author Juergen Hoeller
 * since 4.0
 * see StandardReflectionParameterNameDiscoverer
 * see LocalVariableTableParameterNameDiscoverer
 */
public class DefaultParameterNameDiscoverer extends PrioritizedParameterNameDiscoverer {

    private static final boolean standardReflectionAvailable = ClassUtils.isPresent(
            "java.lang.reflect.Executable", DefaultParameterNameDiscoverer.class.getClassLoader());


    public DefaultParameterNameDiscoverer() {
        if (standardReflectionAvailable) {
            addDiscoverer(new StandardReflectionParameterNameDiscoverer());
        }
        addDiscoverer(new LocalVariableTableParameterNameDiscoverer());
    }

}

英语好的同学可以翻译一下类上面的注释,大致的意思是默认优先尝试判断是否是java8,java8则使用java8提供的方法获取否则使用Asm从字节码LocalVariableTable中读取。

有兴趣的朋友可以读读LocalVariableTableParameterNameDiscoverer的代码,它里面就是采用Asm读取的字节码。

spring-core中是包含了Asm和CGlib的代码的,Spring中并不需要手动导入Asm和CGlib的包。

最后我们来思考一下: 如果我们的Spring mvc项目,非java8也不编译LocalVariableTable信息会出现什么情况? 如下图,在eclipse、idea中取消LocalVariableTable信息的生成。 Spring mvc的参数究竟是如何绑定的 Spring mvc的参数究竟是如何绑定的

重新编译运行我们的示例项目spring-shiro-traininghttp://git.oschina.net/wangzhixuan/spring-shiro-training

启动完之后报异常了:

java.lang.IllegalArgumentException: Name for argument type [java.lang.String] not available, and parameter name information not found in class file either.
    at org.springframework.web.method.annotation.AbstractNamedValueMethodArgumentResolver.updateNamedValueInfo(AbstractNamedValueMethodArgumentResolver.java:154) ~[spring-web-4.2.6.RELEASE.jar:4.2.6.RELEASE]
    at org.springframework.web.method.annotation.AbstractNamedValueMethodArgumentResolver.getNamedValueInfo(AbstractNamedValueMethodArgumentResolver.java:132) ~[spring-web-4.2.6.RELEASE.jar:4.2.6.RELEASE]
    at org.springframework.web.method.annotation.AbstractNamedValueMethodArgumentResolver.resolveArgument(AbstractNamedValueMethodArgumentResolver.java:88) ~[spring-web-4.2.6.RELEASE.jar:4.2.6.RELEASE]
    at org.springframework.web.method.support.HandlerMethodArgumentResolverComposite.resolveArgument(HandlerMethodArgumentResolverComposite.java:99) ~[spring-web-4.2.6.RELEASE.jar:4.2.6.RELEASE]
    at org.springframework.web.method.support.InvocableHandlerMethod.getMethodArgumentValues(InvocableHandlerMethod.java:161) ~[spring-web-4.2.6.RELEASE.jar:4.2.6.RELEASE]
    at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:128) ~[spring-web-4.2.6.RELEASE.jar:4.2.6.RELEASE]
    at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:110) ~[spring-webmvc-4.2.6.RELEASE.jar:4.2.6.RELEASE]
    at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:832) ~[spring-webmvc-4.2.6.RELEASE.jar:4.2.6.RELEASE]
    at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:743) ~[spring-webmvc-4.2.6.RELEASE.jar:4.2.6.RELEASE]
    at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:85) ~[spring-webmvc-4.2.6.RELEASE.jar:4.2.6.RELEASE]
    at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:961) [spring-webmvc-4.2.6.RELEASE.jar:4.2.6.RELEASE]
    at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:895) [spring-webmvc-4.2.6.RELEASE.jar:4.2.6.RELEASE]
    at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:967) [spring-webmvc-4.2.6.RELEASE.jar:4.2.6.RELEASE]
    at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:869) [spring-webmvc-4.2.6.RELEASE.jar:4.2.6.RELEASE]
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:647) [tomcat-embed-core-7.0.47.jar:7.0.47]
    at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:843) [spring-webmvc-4.2.6.RELEASE.jar:4.2.6.RELEASE]
    ......

注:省略了部分异常。 ##总结 Java8之前由于没有提供获取方法入参的参数名的Api,故Spring采用从字节码中获取。Spring mvc中的控制器的参数绑定也并非完美,可以说是投机取巧,因为编辑器都会默认生成LocalVariableTable信息。

点赞
收藏
评论区
推荐文章
blmius blmius
3年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
Wesley13 Wesley13
3年前
java将前端的json数组字符串转换为列表
记录下在前端通过ajax提交了一个json数组的字符串,在后端如何转换为列表。前端数据转化与请求varcontracts{id:'1',name:'yanggb合同1'},{id:'2',name:'yanggb合同2'},{id:'3',name:'yang
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
待兔 待兔
4个月前
手写Java HashMap源码
HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22
Jacquelyn38 Jacquelyn38
3年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
Stella981 Stella981
3年前
Django中Admin中的一些参数配置
设置在列表中显示的字段,id为django模型默认的主键list_display('id','name','sex','profession','email','qq','phone','status','create_time')设置在列表可编辑字段list_editable
Wesley13 Wesley13
3年前
35岁是技术人的天花板吗?
35岁是技术人的天花板吗?我非常不认同“35岁现象”,人类没有那么脆弱,人类的智力不会说是35岁之后就停止发展,更不是说35岁之后就没有机会了。马云35岁还在教书,任正非35岁还在工厂上班。为什么技术人员到35岁就应该退役了呢?所以35岁根本就不是一个问题,我今年已经37岁了,我发现我才刚刚找到自己的节奏,刚刚上路。
Wesley13 Wesley13
3年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
为什么mysql不推荐使用雪花ID作为主键
作者:毛辰飞背景在mysql中设计表的时候,mysql官方推荐不要使用uuid或者不连续不重复的雪花id(long形且唯一),而是推荐连续自增的主键id,官方的推荐是auto_increment,那么为什么不建议采用uuid,使用uuid究
Python进阶者 Python进阶者
10个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这