在使用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信息。