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.
Note: Before Spring MVC 6.1, it was possible to use features like @RequestMapping without explicitly adding -parameters to your Maven compiler configuration, because LocalVariableTableParameterNameDiscoverer could infer parameter names from debug symbols included in bytecode by default. This meant that many projects worked out of the box without any special compiler flags, as the default Maven build included enough debug information for Spring to resolve parameter names automatically. From 6.1 onwards, this fallback was removed, making the -parameters flag mandatory for parameter name resolution to work correctly.
Example
Let's create a Spring MVC project without explicitly compiling with debugging info or with -parameter flag. We are using Spring MVC version older than 6.
A Controller
@Controller
public class MyController {
@ResponseBody
@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 Jetty (configured in pom.xml of example project below):
mvn jetty:run
$ curl -s http://localhost:8080/user?name=joe test response for name: joe
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);
System.out.println("-- using Parameter#getName() --");
for (Parameter parameter : m.getParameters()) {
System.out.println(parameter.getName());
}
System.out.println(
"-- using LocalVariableTableParameterNameDiscoverer#getParameterNames --");
LocalVariableTableParameterNameDiscoverer d =
new LocalVariableTableParameterNameDiscoverer();
String[] pNames = d.getParameterNames(m);
System.out.println(Arrays.toString(pNames));
}
}
Output-- using Parameter#getName() -- arg0 -- using LocalVariableTableParameterNameDiscoverer#getParameterNames -- [myParamName]
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 (which was only possible Spring MVC version prior to 6) 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).
With -parameters enabled, StandardReflectionParameterNameDiscoverer handles everything that LocalVariableTableParameterNameDiscoverer used to, more reliably and without bytecode parsing hacks. If you're seeing errors after upgrading to Spring 6.1, the fix is almost always just enabling the -parameters compiler flag in your build configuration.
Example ProjectDependencies and Technologies Used: - spring-webmvc 5.0.5.RELEASE (Spring Web MVC)
Version Compatibility: 3.2.9.RELEASE - 5.3.39
- javax.servlet-api 3.0.1 (Java Servlet API)
- JDK 1.8
- Maven 3.9.11
|