For those of you who have used SpringMVC, when we need to receive request parameters from the Controller layer, we just need to add @requestParam annotation to the parameter and SpringMVC will automatically bind the parameters for us, as shown in this example:
@GetMapping("test1")
public void test1(@RequestParam("name") String name, @RequestParam("age") Integer age) {}Copy the code
Example client request:
The curl http://127.0.0.1:8080/test1? name=root&age=18Copy the code
Annotating each parameter is cumbersome to write, so SpringMVC can also automatically match the parameter name, as long as the method parameter name and the client request parameter name can be bound, the code can be simplified as:
@GetMapping("test2")
public void test2(String name, Integer age) throws Exception {}Copy the code
How does SpringMVC do this??
Reflection gets the parameter name
Those familiar with SpringMVC know that SpringMVC distributes client requests through a DispatcherServlet, and sends the requests to the corresponding Handler according to the URI mapping of the request. Basically, the Controller method is called by reflection, and the requested parameters are parsed, matched with the method parameters, and passed.
In order to bind parameters, the first thing to do is to know what parameter name the Controller method needs.
For the first version, it is easy to understand that the method wants the parameter name to be the value of the @requestParam annotation, which can be retrieved by reflection as follows:
public static void main(String[] args) throws Exception {
Method test1 = UserController.class.getMethod("test1", String.class, Integer.class);
for (Parameter parameter : test1.getParameters()) {
RequestParam requestParam = parameter.getAnnotation(RequestParam.class);
System.err.println("Test1 - Parameter name :"+ requestParam.value()); Console output: test1- Parameter name :name test1- Parameter name :ageCopy the code
For the second simplified version, however, there is no reflection to get the parameter name, as follows:
public static void main(String[] args) throws Exception {
Method test2 = UserController.class.getMethod("test2", String.class, Integer.class);
for (Parameter parameter : test2.getParameters()) {
System.err.println("Test2 - Parameter name :"+parameter.getName()); }}Copy the code
Guess what the parameter name is??Arg0, arg1!! Why??
If you’re familiar with the JVM, Java code needs to be compiled into a bytecode Class file using the Javac command. This process will discard the method argument names as arg0, arg1… Therefore, the parameter name cannot be obtained by reflection.
– the parameters parameter
Since reflection doesn’t get the parameter names because they are discarded at compile time, is there any way to make Javac compile with the parameter names preserved? The answer is yes, the -parameters parameter.
Parameter.getname () adds a new feature to JDK8 that allows you to keep the parameter name by adding the -parameters parameter at compile time, and to get the normal parameter name by calling parameter.getName().
Examples include the following test classes:
public class Demo {
public void test(String name, Integer age) {}}Copy the code
Javac Demo. Java # The default compilation method javap -verbose DemoCopy the code
Javac-parameters Demo. Java # add -parameters parameter to compile javap-verbose DemoCopy the code
As you can see, it’s added-parameters
Parameter, the bytecode file uses an additional MethodParameters field to hold the method’s parameter name. So when the reflection goes throughparameter.getName()
You can get the parameter name.
Note: only SUPPORT JDK8 and above!!
The -g parameter
Since -parameters requires the JDK to be at least version 8, and SpringMVC is definitely meant to support older JDKS, is there any other way to keep the parameter name?? The answer, again, is the -g argument.
At compile time, plus-g
The parameter tells the compiler that we need to debug the class, and the compiler keeps the local variable table information when compiling, and the parameter is part of the local variable table.You can see, plus-g
Then you can get the name of the parameter from the local variable table.
Using Maven to manage projects, the -g parameter is added by default, and no developer intervention is required.
Note: Although-g
The information of the local variable scale will be preserved, but still cannot be reflectedparameter.getName()
To get the parameter name, the developer needs to parse the Class bytecode file to get it, which is and-parameters
A big difference!!
ASM
ASM is a general-purpose Java bytecode manipulation and analysis framework. It can be used to modify existing classes or to generate classes dynamically directly in binary form. ASM provides some common bytecode conversion and analysis algorithms from which you can build custom complex conversion and code analysis tools. ASM provides similar functionality to other Java bytecode frameworks, but with a focus on performance. Because it is designed and implemented as small and fast as possible, it is ideal for use in dynamic systems (but of course it can also be used statically, for example in compilers).
Adding the -g argument at compile time preserves the parameter name, but it still cannot be retrieved by reflection. You need to parse the bytecode file to get it yourself. Is there a good tool kit for parsing bytecode files? Again, the answer is: yes.
Java can easily manipulate bytecode files using ASM, which is used by many open source frameworks, such as CGLIB.
Here is an example of using ASM to get method parameter names. 1. Introduce dependencies
<dependency>
<groupId>asm</groupId>
<artifactId>asm-util</artifactId>
<version>3.3.1</version>
</dependency>
Copy the code
2. Code examples
public class Demo {
public void test(String name, Integer age) {}/** * use ASM to access the parameter name *@param args
* @throws Exception
*/
public static void main(String[] args) throws Exception {
Class<Demo> clazz = Demo.class;
Method method = clazz.getMethod("test", String.class, Integer.class);
InputStream in = clazz.getResourceAsStream("/" + clazz.getName().replace('. '.'/') + ".class");
ClassReader cr = new ClassReader(in);
ClassNode cn = new ClassNode();
cr.accept(cn, ClassReader.EXPAND_FRAMES);
List<MethodNode> methodNodes = cn.methods;
for (MethodNode methodNode : methodNodes) {
if (method.getName().equals(methodNode.name)) {
System.err.println("Test method parameter :");
List<LocalVariableNode> localVariables = methodNode.localVariables;
for (LocalVariableNode localVariable : localVariables) {
System.err.println(localVariable.name);
}
}
}
}
}
Copy the code
Console output:
Test method parameter: this name ageCopy the code
Note: This approach does not work with interfaces and abstract methods, which have no method body and therefore no local variable table. This is why MyBatis cannot bind parameters to interface method parameter names in XML!!
So far, we have known that Java has two ways to get the parameter name of the method, namely, adding -parameters parameter reflection to get, and -g parameter through ASM parsing bytecode file to get. Which one does SpringMVC use??
The SpringMVC approach
How does SpringMVC solve the problem of parameter names? Is it through the -parameters parameter? Of course not. First of all, the -parameters parameter is only available in JDK8, older JDK8 JDK doesn’t have this function at all, SpringMVC is supposed to support pre-JDK8 versions, and this solution forces developers to manually add parameters at compile time, which is also unfriendly.
To know the solution of SpringMVC, you must see the source code!! Debug trace source process I will not describe, interested students can go to track their own.
SpringMVC encapsulates a method handler into oneHandlerMethod
Class, method parameters are usedMethodParameter
Said: MethodParameter
There is a method to get the parameter namegetParameterName()
:The task of obtaining parameter names is actually given toParameterNameDiscoverer
This is an interface whose main function is to resolve method parameter names.MethodParameter
theParameterNameDiscoverer
The implementation class isPrioritizedParameterNameDiscoverer
.We’re one step away from the truth. Go check it outLocalVariableTableParameterNameDiscoverer
To achieve it.As long as theinspectClass()
The method will know the truth.As you can see,LocalVariableTableParameterNameDiscoverer
The bottom layer is to use ASM technology to obtain the method parameter name. Spring does not rely on ASM directly, but encapsulates them into its ownorg.springframework.asm
Under the bag.
conclusion
SpringMVC gets the parameter name of the Controller method in three ways:
plan | limit | The advantages and disadvantages |
---|---|---|
Parameter annotation | unlimited | Write the trouble |
-parameters | Only JDK8 or later is supported | Directly throughparameter.getName() Access, convenience |
-g | Unrestricted, compile plus-g Parameters can be |
Parsing is cumbersome and relies on ASM |
- If added
@RequestParam
Annotation parsing is preferred. - If there are no annotations, use
StandardReflectionParameterNameDiscoverer
Parse, passParameter.getName()
Reflection, provided JDK version 8 or later and enabled-parameters
Compile parameters. - If neither of the preceding two options is available, use
LocalVariableTableParameterNameDiscoverer
Parse through ASM technology.
Note: If not compiled-g
Parameters, even with ASM can not be resolved, clever housewife can not make bricks without rice!!