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-parametersParameter, 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-gThe 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-gThen 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-gThe 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-parametersA 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 oneHandlerMethodClass, method parameters are usedMethodParameterSaid: MethodParameterThere is a method to get the parameter namegetParameterName():The task of obtaining parameter names is actually given toParameterNameDiscovererThis is an interface whose main function is to resolve method parameter names.MethodParametertheParameterNameDiscovererThe implementation class isPrioritizedParameterNameDiscoverer.We’re one step away from the truth. Go check it outLocalVariableTableParameterNameDiscovererTo achieve it.As long as theinspectClass()The method will know the truth.As you can see,LocalVariableTableParameterNameDiscovererThe 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.asmUnder 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-gParameters can be Parsing is cumbersome and relies on ASM
  1. If added@RequestParamAnnotation parsing is preferred.
  2. If there are no annotations, useStandardReflectionParameterNameDiscovererParse, passParameter.getName()Reflection, provided JDK version 8 or later and enabled-parametersCompile parameters.
  3. If neither of the preceding two options is available, useLocalVariableTableParameterNameDiscovererParse through ASM technology.

Note: If not compiled-gParameters, even with ASM can not be resolved, clever housewife can not make bricks without rice!!