您的位置:首页 > 编程语言 > Java开发

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

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

/**
*注册
*/
@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信息的生成。





重新编译运行我们的示例项目
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信息。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  Spring jfinal