Spring MVC #Valid Validation with custom HandlerMethodArgumentResolver - java

I want to register a custom HandlerMethodArgumentResolver that could handle the following #Controller handler method definition
#RequestMapping(method = RequestMethod.POST)
public String createDomain(#Valid Domain domain, BindingResult errors, #RequestParam("countryId") Long countryId) {
I can register my resolver, which just creates a Domain object through request parameters, by overriding addArgumentResolver() from WebMvcConfigurerAdapter. When Spring tries to resolve the Domain parameter, it goes through its list of HandlerMethodArgumentResolver (there are a lot) and picks the first one that supports() it.
In the above example, although my resolver will get called and my Domain argument will get initialized, the #Valid annotation won't have been processed and the resolver for BindingResult, an ErrorsMethodArgumentResolver will fail because it requires a #ModelAttribute, #RequestBody or the #RequestPart argument in the handler method, which I don't have.
If I try to fix it by adding #ModelAttribute
#RequestMapping(method = RequestMethod.POST)
public String createDomain(#Valid #ModelAttribute Domain domain, BindingResult errors, #RequestParam("countryId") Long countryId) {
a HandlerMethodArgumentResolver implementation, ModelAttributeMethodProcessor, will get checked first with supports() and resolve the argument (with #ModelAttribute and #Valid) before my custom resolver. The BindingResult won't fail, but I won't have my custom creation behavior on the Domain instance.
I could just copy-paste the code for validation and adding to model that's in ModelAttributeMethodProcessor, but I was hoping there was an easier way to resolve my parameters and perform validation without adding an object to the model. Is there such a way?

Nice description of the issue that you are facing.
I checked out the code that you have outlined and have come to the same conclusion that you have - there is no built-in way to have both a custom HandlerMethodArgumentResolver as well as #Valid related validation applied at the same time, the only choice is to do what the ModelAttributeMethodProcessor does which is to check if the parameter has a #Valid annotation and call the validation logic related code.
You can probably derive your HandlerMethodResolverArgumentResolver from ModelAttributeMethodProcessor and call super.validateIfApplicable(..) atleast this way the existing code is leveraged.

It's may be too late, but your HandlerMethodArgumentResolver gets WebDataBinderFactory object as last argument, then, to hook up the validation, simply add this to your resolver implementation:
Object resolvedObject = // your logic
if(parameter.hasParameterAnnotation(Valid.class){
binderFactory.createBinder(webRequest,resolvedObject,"resolvedObjectLogicalName").validate ();
}

Related

How the Spring MVC #Controller's handler methods' parameters are automatically resolved during run-time?

How does the DispatcherServlet (or any other bean, supporting Spring MVC infrastructure), dynamically, resolve the signature of the handler method of the #Component instance, and how does it know, what parameters/types are expected, in which order, in that method?
If I have a "#RequestMapping-ed" handler method, in my #Controller instance, defining any version of its signature:
public String f() {...}
public String f(Model model) {...}
public String f(HttpServletRequest, Model model) {...}
public String f(Model model, HttpServletRequest) {...}
public String f(SomeEntity se, Model model, HttpServletRequest, AnotherModel am) {...}
//so on..
would work fine, disregarding of parameter number, types, and order, which, eventually, are supplied with corresponding arguments, again - disregarding of their number, order and types.
There should be quite a work going underneath, to correctly instantiate expected arguments, pass them in a proper order, maybe even do some type-casting, if needed.. an so on, but I don't grasp the fundamentals of this.
I looked up the corresponding sources from spring-webmvc module, and sources of the DispatcherServlet; however, haven't got a firm grasp of the underlying mechanism.
I can guess, that some BeanPostProcessors could be involved.. doing reflective accesses, and so on.. but even in this case, as I'm not certain on what's happening, I would very much appreciate any valuable input.
In Spring MVC there are three implementations of HandlerMapping: BeanNameUrlHandlerMapping, SimpleUrlHandlerMapping and ControllerClassNameHandlerMapping.
You can read more about this subject here:
https://www.baeldung.com/spring-handler-mappings
I've not used Spring in years, but this post gives a pretty good step-by-step outline of the spring mvc request lifecycle
When the DispatcherServlet receives a request, it iterates over this list until it finds a matching handler object for the request in question. For simplicity, let's consider only RequestMappingHandlerMapping.
A bean of this type stores a mapping of #RequestMapping annotated methods (the actual Method object retrieved with reflection) stored as a HandlerMethod instances and wrapped in RequestMappingInfo objects that hold mapping data for matching the request, ie. URL, headers, and request parameters.
The DispatcherServlet retrieves the best matching HandlerMethod from these and any corresponding HandlerInterceptor instances which you may have registered. It retrieves these as a HandlerExecutionChain object. It will first apply any pre-handling by HandlerInterceptors. It will then try to invoke your HandlerMethod. This will typically (but not always) be a #RequestMapping annotated method inside a #Controller annotated class. This produces what Spring calls a dispatch result. The DispatcherServlet then applies post-handling by the HandlerInterceptors.

Spring MVC #ModelAttribute as a method parameter annotation

I bumped into an answer about the usage of #ModelAttribute on spring MVC here on stackoverflow, and learned that it was not actually required to be added into the method's parameter.
I looked for some controllers from our old project, deleted the annotation, and surprisingly the application still runs flawlessly without the #ModelAttribute. Please see example below:
#RequestMapping(method = RequestMethod.POST, value = "/audit/filter")
public String getAuditLogsWithFilter(Model model, AuditLogFilter auditLogFilter, BindingResult bindingResult)
I have read some articles about it but I can not grasp onto why #ModelAttribute is used for some method parameters particularly for spring controllers.
Can anybody provide a simple explanation into why that is? Or can someone enumerate some cases into which I should add the #ModelAttribute annotation to my parameter object?
as described in the official document, it's optional:
Note that using #ModelAttribute is optional (for example, to set its attributes). By default, any argument that is not a simple value type (as determined by BeanUtils#isSimpleProperty) and is not resolved by any other argument resolver is treated as if it were annotated with #ModelAttribute.

AspectJ with Spring : intercepted methods lose their parameters annotations

I recently added AOP with aspectJ and spring-aop to my existent spring project. The goal was to actually intercept controller calls to modify the response they send back, in order to bind some values to this response I didn't want to add manually to each and everyone of my controllers, for example the expiration date of the actual token used by the end-user (which I wasn't even able to showcase within my controller in any case). I actually managed to get it working until I started my unit tests :
In my unit tests I call directly my controller methods using Reflection feature from java, then replicate usual process (calling the filter chain, pre handler and post handlers, and the controller method itself which is first manually validated using spring validator when annotation #Valid is present on one of my parameters. All this process works fine and gets executed properly). The problem is that now that the controller method is intercepted by spring-aop, it's mentionned as coming from the proxy controller created, and all of my parameters annotations disapear. Here is a controller example :
#Override
public ResponseEntity<Object> editPassword(#Valid #RequestBody PasswordEditForm passwordEditForm, HttpServletRequest request) {
return factorizedUserBaseController.editPassword(passwordEditForm, request, User.class);
}
the parameter PasswordEditForm has the annotation #Valid so in my test cases it was first validated before any other step, but now as I double checked it, the #Valid annotation is not present on the proxy method, and therefore the parameter doesn't get validated, any clue for how to fix this and make my parameters annotation still understandable from my test point of view?
Note : when running the spring through mvn spring-boot:run, parameters with #Valid annotation gets correctly validated and then goes to my error handler method properly.
Problem Solved : from several other stackoverflow posts I understand that CGLIB (aop proxy lib used by Spring) doesn't support annotations. ( see Retain annotations on CGLIB proxies?). But my problem wasn't here, I was literally sure I was finding the method using the controller class itself (the one I coded) but what I was wrong about is that I was giving the controller instance as a parameter to some other parts of my code which in turn would use this controller class to find the method which of course wasn't working because thanks to Spring proxies, it wasn't anymore my controller itself but a proxy class extending my own controller class. Instead, I just had to replace :
Class<?> controllerClass = controllerInstanciationContainer
.getController()
.getClass();
with
Class<?> controllerClass = controllerInstanciationContainer
.getController()
.getClass()
.getSuperclass();

What class or component of Spring MVC determines the object passed into a Controller's RequestMapping method parameter?

Raw beginner with Spring MVC -- that said,
What component of Spring MVC passes in objects to a method annotated with #RequestMapping within a controller object?
For example,
#RequestMapping
public String test(Model model) {
model.addAttribute("testMessage", "My Test");
return "test";
}
Where does Model come from?
Can my method take in any parameters I want?
Is there some bit of intuitive Spring framework dependency injection that I'm simply not understanding here?
This is all part of Spring dependency injection.
Spring injects the Model model
object to your controller method. Model model is simply a Map <String, Object>
that stores attributes. When you added the attribute to the model object using model.addAttribute("testMessage", "My Test") its adding one entry to the Map. This map is accessible from the view that you're interested in. So you can use this map in your view to access the attribute that you added from the controller (i.e. testMessage)
There are several other things that you can pass in these controller methods that Spring resolves automatically and injects appropriate objects/value. You can use things like:
ModelMap modelMap
#RequestParam
#PathVariable
#ModelAttribute
BindingResult bindingResult
and on and on. When you pass these things on your controller method, Spring knows how to resolve them and inject them to your method.
This might give you an better understanding of spring mvc: http://docs.spring.io/spring/docs/current/spring-framework-reference/html/mvc.html

Using #RequestBody and #ModelAttribute together?

I'm trying to get at the body of a POST, and I'd like the parameters of my method to bind to an object.
Is this possible?
My current declaration doesn't ever get hit:
#RequestMapping(method = RequestMethod.POST)
public void doStuff(#RequestBody byte[] bodyData, #ModelAttribute Form form, Model model ) {
Looks like I'm getting this exception:
- 2011-02-25 16:57:30,354 - ERROR - http-8080-3 - org.springframework.web.portle
t.DispatcherPortlet - Could not complete request
java.lang.UnsupportedOperationException: #RequestBody not supported
For this to work correctly, you have to be sure you're using AnnotationMethodHandlerAdapter. This overrides HandlerMethodInvoker's createHttpInputMessage (which is throwing the exception you're seeing). (It does this in a private class.)
I believe you can just include the following in your *-servlet.xml
<bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter"/>
WARNING: The below answer is for the case of needing #RequestBody and #RequestParam in the same handler method. It does not answer this question, but could be of use to someone.
I've tested this out using Spring 3.0.1. This is possible, but it's somewhat precarious. You MUST have your #RequestBody method argument before your #RequestParam argument. I'm guessing this is because HandlerMethodInvoker reads the request body (along with the GET parameters) when retrieving parameters (and the request body can only be read once).
Here's an example (WARNING: I code in Scala, so I've not compiled this Java code)
#RequestMapping(value = "/test", method = RequestMethod.POST)
public String test(
#RequestBody String body,
#RequestParam("param1") String parma1,
Map<Object, Object> model: Map[AnyRef, AnyRef])
{
model.put("test", test)
model.put("body", body)
return "Layout"
}
An alternative is to use #PathVariable. I've confirmed that this works.
Unfortunately that is kind of impossible. If you are using portlet version of Spring MVC (and it looks like from the logs) then you might be interested in this JIRA issue.
AnnotationMethodHandlerAdapter uses PortletHandlerMethodInvoker internally and the second is a inner subclass of HandlerMethodInvoker - the place where you can configure HttpMessageConverter-s. But they're set to null. And the property is final.
That even would be to workaround if you could substitute HandlerMethodInvoker, but you can not.. it's constructor-created ;)
One thing to notice is that Servlet version of Spring MVC fully supports HttpMessageConverter-s and does not suffer this issue.

Categories