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

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

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

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

1public class Test { 2 public void action(String userName, int age) { 3 System.out.println(userName); 4 System.out.println(age); 5 } 6}

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

1public class Test 2 minor version: 0 3 major version: 51 4 flags: ACC_PUBLIC, ACC_SUPER 5Constant pool: 6 #1 = Class #2 // Test 7 #2 = Utf8 Test 8 #3 = Class #4 // java/lang/Object 9 #4 = Utf8 java/lang/Object 10 #5 = Utf8 <init> 11 #6 = Utf8 ()V 12 #7 = Utf8 Code 13 #8 = Methodref #3.#9 // java/lang/Object."<init>":()V 14 #9 = NameAndType #5:#6 // "<init>":()V 15 #10 = Utf8 LineNumberTable 16 #11 = Utf8 LocalVariableTable 17 #12 = Utf8 this 18 #13 = Utf8 LTest; 19 #14 = Utf8 action 20 #15 = Utf8 (Ljava/lang/String;I)V 21 #16 = Fieldref #17.#19 // java/lang/System.out:Ljava/io/PrintStream; 22 #17 = Class #18 // java/lang/System 23 #18 = Utf8 java/lang/System 24 #19 = NameAndType #20:#21 // out:Ljava/io/PrintStream; 25 #20 = Utf8 out 26 #21 = Utf8 Ljava/io/PrintStream; 27 #22 = Methodref #23.#25 // java/io/PrintStream.println:(Ljava/lang/String;)V 28 #23 = Class #24 // java/io/PrintStream 29 #24 = Utf8 java/io/PrintStream 30 #25 = NameAndType #26:#27 // println:(Ljava/lang/String;)V 31 #26 = Utf8 println 32 #27 = Utf8 (Ljava/lang/String;)V 33 #28 = Methodref #23.#29 // java/io/PrintStream.println:(I)V 34 #29 = NameAndType #26:#30 // println:(I)V 35 #30 = Utf8 (I)V 36 #31 = Utf8 userName 37 #32 = Utf8 Ljava/lang/String; 38 #33 = Utf8 age 39 #34 = Utf8 I 40 #35 = Utf8 SourceFile 41 #36 = Utf8 Test.java 42{ 43 public Test(); 44 descriptor: ()V 45 flags: ACC_PUBLIC 46 Code: 47 stack=1, locals=1, args_size=1 48 0: aload_0 49 1: invokespecial #8 // Method java/lang/Object."<init>":()V 50 4: return 51 LineNumberTable: 52 line 2: 0 53 LocalVariableTable: 54 Start Length Slot Name Signature 55 0 5 0 this LTest; 56 57 public void action(java.lang.String, int); 58 descriptor: (Ljava/lang/String;I)V 59 flags: ACC_PUBLIC 60 Code: 61 stack=2, locals=3, args_size=3 62 0: getstatic #16 // Field java/lang/System.out:Ljava/io/PrintStream; 63 3: aload_1 64 4: invokevirtual #22 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 65 7: getstatic #16 // Field java/lang/System.out:Ljava/io/PrintStream; 66 10: iload_2 67 11: invokevirtual #28 // Method java/io/PrintStream.println:(I)V 68 14: return 69 LineNumberTable: 70 line 5: 0 71 line 6: 7 72 line 7: 14 73 LocalVariableTable: 74 Start Length Slot Name Signature 75 0 15 0 this LTest; 76 0 15 1 userName Ljava/lang/String; 77 0 15 2 age I 78}

用javac编译

1public class Test 2 minor version: 0 3 major version: 52 4 flags: ACC_PUBLIC, ACC_SUPER 5Constant pool: 6 #1 = Methodref #6.#15 // java/lang/Object."<init>":()V 7 #2 = Fieldref #16.#17 // java/lang/System.out:Ljava/io/PrintStream; 8 #3 = Methodref #18.#19 // java/io/PrintStream.println:(Ljava/lang/String;)V 9 #4 = Methodref #18.#20 // java/io/PrintStream.println:(I)V 10 #5 = Class #21 // Test 11 #6 = Class #22 // java/lang/Object 12 #7 = Utf8 <init> 13 #8 = Utf8 ()V 14 #9 = Utf8 Code 15 #10 = Utf8 LineNumberTable 16 #11 = Utf8 action 17 #12 = Utf8 (Ljava/lang/String;I)V 18 #13 = Utf8 SourceFile 19 #14 = Utf8 Test.java 20 #15 = NameAndType #7:#8 // "<init>":()V 21 #16 = Class #23 // java/lang/System 22 #17 = NameAndType #24:#25 // out:Ljava/io/PrintStream; 23 #18 = Class #26 // java/io/PrintStream 24 #19 = NameAndType #27:#28 // println:(Ljava/lang/String;)V 25 #20 = NameAndType #27:#29 // println:(I)V 26 #21 = Utf8 Test 27 #22 = Utf8 java/lang/Object 28 #23 = Utf8 java/lang/System 29 #24 = Utf8 out 30 #25 = Utf8 Ljava/io/PrintStream; 31 #26 = Utf8 java/io/PrintStream 32 #27 = Utf8 println 33 #28 = Utf8 (Ljava/lang/String;)V 34 #29 = Utf8 (I)V 35{ 36 public Test(); 37 descriptor: ()V 38 flags: ACC_PUBLIC 39 Code: 40 stack=1, locals=1, args_size=1 41 0: aload_0 42 1: invokespecial #1 // Method java/lang/Object."<init>":()V 43 4: return 44 LineNumberTable: 45 line 2: 0 46 47 public void action(java.lang.String, int); 48 descriptor: (Ljava/lang/String;I)V 49 flags: ACC_PUBLIC 50 Code: 51 stack=2, locals=3, args_size=3 52 0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream; 53 3: aload_1 54 4: invokevirtual #3 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 55 7: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream; 56 10: iload_2 57 11: invokevirtual #4 // Method java/io/PrintStream.println:(I)V 58 14: return 59 LineNumberTable: 60 line 5: 0 61 line 6: 7 62 line 7: 14 63}

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

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

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

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

1/** 2 * Default implementation of the {link ParameterNameDiscoverer} strategy interface, 3 * using the Java 8 standard reflection mechanism (if available), and falling back 4 * to the ASM-based {link LocalVariableTableParameterNameDiscoverer} for checking 5 * debug information in the class file. 6 * 7 * Further discoverers may be added through {link #addDiscoverer(ParameterNameDiscoverer)}. 8 * 9 * author Juergen Hoeller 10 * since 4.0 11 * see StandardReflectionParameterNameDiscoverer 12 * see LocalVariableTableParameterNameDiscoverer 13 */ 14public class DefaultParameterNameDiscoverer extends PrioritizedParameterNameDiscoverer { 15 16 private static final boolean standardReflectionAvailable = ClassUtils.isPresent( 17 "java.lang.reflect.Executable", DefaultParameterNameDiscoverer.class.getClassLoader()); 18 19 20 public DefaultParameterNameDiscoverer() { 21 if (standardReflectionAvailable) { 22 addDiscoverer(new StandardReflectionParameterNameDiscoverer()); 23 } 24 addDiscoverer(new LocalVariableTableParameterNameDiscoverer()); 25 } 26 27}

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

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

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

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

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

启动完之后报异常了:

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

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

点赞
收藏

评论区

加载中...

相关推荐

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

Oracle 分组与拼接字符串同时使用

SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(

MySQL部分从库上面因为大量的临时表tmp_table造成慢查询

背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_

皕杰报表之UUID

​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为

手写Java HashMap源码

HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22

2020年前端实用代码段,为你的工作保驾护航

有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )

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