Spring mvc的参数究竟是如何绑定的
2016-07-19 09:55
495 查看
在使用Spring mvc的时候我们会被他优雅的参数绑定所吸引,再也不用写那么多getParameter也不用像struts2中写一堆get、set给开发者带来了很大的便利。
其中
##我们来看看2份javap的到的字节码。
###首先看java源码:
注:
###eclipse下编译得到的字节码:
用javac编译
可以看出eclipse下多了LocalVariableTable这样一段,这一段就是记录的我们方法的参数名。
LocalVariableTable属性建立了方法中的局部变量与源代码中的局部变量之间的对应关系。这个属性存在于Code属性中。这个属性是可选的,编译器可以选择不生成这个属性。
这个参数是为了让编译器在代码提示时,可以提示出友好的参数名。
##我们再来翻翻Spring的源代码
参数绑定的代码位于Spring-web
有兴趣的同学请查看
我们继续追踪,追踪到了
英语好的同学可以翻译一下类上面的注释,大致的意思是默认优先尝试判断是否是
有兴趣的朋友可以读读
spring-core中是包含了Asm和CGlib的代码的,Spring中并不需要手动导入Asm和CGlib的包。
最后我们来思考一下:
如果我们的
如下图,在eclipse、idea中取消LocalVariableTable信息的生成。
![](https://static.oschina.net/uploads/img/201607/18223624_Di1d.jpg)
![](https://static.oschina.net/uploads/img/201607/18223729_g6UT.png)
重新编译运行我们的示例项目
启动完之后报异常了:
注:省略了部分异常。
##总结
Java8之前由于没有提供获取方法入参的参数名的Api,故Spring采用从字节码中获取。Spring mvc中的控制器的参数绑定也并非完美,可以说是投机取巧,因为编辑器都会默认生成LocalVariableTable信息。
/** *注册 */ @RequestMapping(value = "/register", method = RequestMethod.POST) public Object register(String userName, String pwd, String rePwd) {}
其中
userName,
pwd,
rePwd均来自页面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中,
有兴趣的同学请查看
invokeForRequest、
getMethodArgumentValues和
doInvoke三个方法!
我们继续追踪,追踪到了
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信息的生成。
![](https://static.oschina.net/uploads/img/201607/18223624_Di1d.jpg)
![](https://static.oschina.net/uploads/img/201607/18223729_g6UT.png)
重新编译运行我们的示例项目
spring-shiro-training:http://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信息。
相关文章推荐
- 一个jar包里的网站
- 一个jar包里的网站之文件上传
- 一个jar包里的网站之返回对媒体类型
- Spring和ThreadLocal
- Spring Boot 开发微服务
- Spring AOP动态代理-切面
- Angularjs 跨域请求
- Spring整合Quartz(JobDetailBean方式)
- Spring整合Quartz(JobDetailBean方式)
- jfinal与bootstrap的登录跳转实战演习
- Bootstrap+jfinal退出系统弹出确认框的实现方法
- 模拟Spring的简单实现
- Spring整合WebSocket应用示例(上)
- spring+html5实现安全传输随机数字密码键盘
- Spring中属性注入详解
- 监听器获取Spring配置文件的方法
- Java利用Sping框架编写RPC远程过程调用服务的教程
- springmvc 发送ajax出现中文乱码的解决方法汇总
- Spring MVC中Ajax实现二级联动的简单实例