Method Parameter Names and Spring
Continuing on the previous blog entry about Constructor and method parameters and Java not retaining the parameter names at runtime – the previous entry was about constructor not retaining the parameter names and the implication of this for Contructor injections in Spring, here I will cover a few more scenarios where parameter names not being retained has implications with Spring:
1. Consider Spring MVC Controller method with a parameter to bind to a request parameter which is passed in:
@RequestMapping(value='/members/find') public String getMembersByName(@RequestParam String name){ ... return 'list'; }
Here the parameter ‘name’ has a @RequestParam annotation associated with it, which is in an indication to Spring MVC to bind a request parameter ‘name’ to this method parameter.
Since the parameter name is not retained at runtime, it is likely that an exception will be thrown by Spring:
Request processing failed; nested exception is java.lang.IllegalArgumentException: Name for argument type not available, and parameter name i nformation not found in class file either.
The fix here is simple, to either compile with debug options on which will retain the parameter names at runtime OR a better one is to simply indicate what the expected request parameter name is, as an argument to the @RequestParam annotation:
@RequestMapping(value='/members/find') public String getMembersByName(@RequestParam('name') String name){ return 'list'; }
2. Along the same lines consider another Spring MVC controller method, this time supporting URI template patterns:
@RequestMapping(value='/members/{id}', method=RequestMethod.GET) public @ResponseBody Member get(@PathVariable Integer id){ return this.memberDB.get(id); }
Here the expectation is that if a request comes in with a uri of /members/20, then the id parameter will get bound with a value of 20, however since at runtime the parameter name of ‘id’ is not retained, the fix like in the previous case is either to compile with debug on, or to explicitly mention in the @PathVariable annotation what the expected pattern name is:
@RequestMapping(value='/members/{id}', method=RequestMethod.GET) public @ResponseBody Member get(@PathVariable('id') Integer id){
3. A third example is with caching support in Spring with @Cacheable annotation. Consider a sample method annotated with @Cacheable:
@Cacheable(value='default', key='#param1.concat('-').concat(#param2)') public String cachedMethod(String param1, String param2){ return '' + new Random().nextInt(); }
Here the key is a Spring-EL expression, which instructs the keygenerator to generate the key by combining the argument of the first parameter of name param1 with argument to the second parameter with name param2. However the problem like before is that these names are not available at runtime.
One of the fixes, as before is to compile with debug symbols turned on. A second fix is to use placeholders to stand in for parameter index – a0 OR p0 for first parameter, a1 OR p1 for second parameter and so on, this way the @Cacheable key will look like this:
@Cacheable(value='default', key='#p0.concat('-').concat(#p1)') public String cachedMethod(String param1, String param2){ return '' + new Random().nextInt(); }
So in conclusion, a safe way to use Spring features that depend on method parameter names is to compile with debug on(-g or -g:var option of javac) or by explicitly passing in meta information that indicates what the parameter names are at runtime.
Reference: Method Parameter Names and Spring from our JCG partner Biju Kunjummen at the all and sundry blog.
It may be worth noting that maven compiles with all (lines, vars, source) debug info by default.