If we want Spring to match query parameter names with @RequestParam (or similarly with @PathVariable or @RequestHeader) automatically without explicitly specifying the name i.e. without specifying value in @RequestParam("myRequestParamName") then our code should be compiled with debugging information or with the -parameters compiler flag.
Let's try that without explicitly compiling with debugging info or with -parameter flag.
A Controller
@RestController
public class MyController {
@RequestMapping("/user")
public String test(String name) {
return "test response for name: " + name;
}
}
Let's make a request to our controller
To try examples, run embedded tomcat (configured in pom.xml of example project below):
mvn tomcat7:run-war
Entering /user?name=joe in the browser:
We didn't explicitly compile our code with -g option or with -parameter flag, but it still works.
After some debugging I found Spring uses implementations of interface ParameterNameDiscoverer to find parameter names. By default spring uses DefaultParameterNameDiscoverer which first delegates to StandardReflectionParameterNameDiscoverer (which relies on -parameter flag), if it returns null name then ASM-based LocalVariableTableParameterNameDiscoverer is attempted (which relies on -g:vars option). In our case LocalVariableTableParameterNameDiscoverer returned the real parameter name.
These resolvers can also be used outside of Spring container. Let's try LocalVariableTableParameterNameDiscoverer:
package com.logicbig.example;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.Arrays;
public class Test {
public void testMethod(String myParamName) {
}
public static void main(String[] args) throws NoSuchMethodException {
Method m = Test.class.getDeclaredMethod("testMethod", String.class);
for (Parameter parameter : m.getParameters()) {
System.out.println(parameter.getName());
}
LocalVariableTableParameterNameDiscoverer d =
new LocalVariableTableParameterNameDiscoverer();
String[] pNames = d.getParameterNames(m);
System.out.println(Arrays.toString(pNames));
}
}
arg0 [myParamName]
It's very surprising as I'm not specifying any -g option when compiling. So how LocalVariableTableParameterNameDiscoverer still works?. Let's see what is in our Test class via javap command.
D:\auto-handler-method-param-name-matching\target\classes>javap -l com.logicbig.example.Test Compiled from "Test.java" public class com.logicbig.example.Test { public com.logicbig.example.Test(); LineNumberTable: line 8: 0 LocalVariableTable: Start Length Slot Name Signature 0 5 0 this Lcom/logicbig/example/Test;
public void testMethod(java.lang.String); LineNumberTable: line 10: 0 LocalVariableTable: Start Length Slot Name Signature 0 1 0 this Lcom/logicbig/example/Test; 0 1 1 myParamName Ljava/lang/String;
public static void main(java.lang.String[]) throws java.lang.NoSuchMethodException; LineNumberTable: line 14: 0 line 15: 17 line 16: 40 line 15: 51 line 19: 57 line 21: 65 line 22: 71 line 24: 81 LocalVariableTable: Start Length Slot Name Signature 40 11 5 parameter Ljava/lang/reflect/Parameter; 0 82 0 args [Ljava/lang/String; 17 65 1 m Ljava/lang/reflect/Method; 65 17 2 d Lorg/springframework/core/LocalVariableTableParameterNameDiscoverer; 71 11 3 pNames [Ljava/lang/String; }
That confirms that code is complied with -g vars option as local variable names are included in the class file. By default javac includes only line number and source file information (see here). After some investigation, it turned out that the maven's Java compiler plugin, by default, adds all debug information via 'debug' option. I turned that option off and then it stopped working with following exception while hitting our example Spring controller:
java.lang.IllegalArgumentException: Name for argument type [java.lang.String] not available, and parameter name information not found in class file either.
org.springframework.web.method.annotation.AbstractNamedValueMethodArgumentResolver.updateNamedValueInfo(AbstractNamedValueMethodArgumentResolver.java:171)
org.springframework.web.method.annotation.AbstractNamedValueMethodArgumentResolver.getNamedValueInfo(AbstractNamedValueMethodArgumentResolver.java:148)
org.springframework.web.method.annotation.AbstractNamedValueMethodArgumentResolver.resolveArgument(AbstractNamedValueMethodArgumentResolver.java:97)
org.springframework.web.method.support.HandlerMethodArgumentResolverComposite.resolveArgument(HandlerMethodArgumentResolverComposite.java:124)
org.springframework.web.method.support.InvocableHandlerMethod.getMethodArgumentValues(InvocableHandlerMethod.java:161)
org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:131)
Conclusion
Spring MVC can only match the http request parameters to our handler method parameters if the source code is either compiled with -g:vars or -parameter option of javac. If you are compiling your code via maven then maven by default includes all debug info via -g option. If your application is relying on automatic parameter name discovery and you want to be 100% sure that your code won't break at some point, you may like to add the -parameter or -g option yourself in maven compiler plugin (check out this and this).
Example ProjectDependencies and Technologies Used: - spring-webmvc 5.0.5.RELEASE: Spring Web MVC.
- javax.servlet-api 3.0.1 Java Servlet API
- JDK 1.8
- Maven 3.3.9
|