Search code examples
javaspringspring-mvchibernate-validatorspring-validator

How does Spring MVC resolve and validate handler method parameters?


I am pretty new in Spring MVC and I have imported a tutorial project related to server side validation and I have some doubt about how exactly it works.

So I have this login page named login.jsp that contain this login form:

<form:form action="${pageContext.request.contextPath}/login" commandName="user" method="post">

    <table>

        <tr>
            <td><label>Enter Username : </label></td>
            <td><form:input type="text" path="username" name="username" />
                <br> <form:errors path="username" style="color:red;"></form:errors>
            </td>
        </tr>

        <tr>
            <td><label>Enter Password : </label></td>
            <td><form:input type="password" path="password" name="password" />
                <br> <form:errors path="password" style="color:red;"></form:errors>
            </td>
        </tr>

        <tr>
            <td>&nbsp</td>
            <td align="center"><input type="submit" value="Login" /></td>
        </tr>

    </table>

</form:form>

that I think use the object specified by the commandName="user" attribute retrieved from the model (correct me if I am wrong) to store the username and password inserted by the user.

This commandName="user" is an instance of this User class:

import javax.validation.constraints.Size;
import org.hibernate.validator.constraints.NotBlank;

public class User {

    @NotBlank(message="Username can not be blank")
    private String username;
    
    @Size(min=6,message="Password must be atleast 6 characters long")
    private String password;
    
    private String gender;
    private String vehicle;
    private String country;
    private String image;

    ...............................................
    ...............................................
    GETTER AND SETTER METHODS
    ...............................................
    ...............................................
}

So as you can see there are the @NotBlank and @Size validation annotation declared on the username and password fields.

And here the first doubt: what exactly is the difference from the 2 used library javax.validation and org.hibernate.validator ?

Why in the tutorial use both? Can I do the same thing using only the hibernate validator library? (I think that I can specify the valid length of a string using the Hibernate validator, or not)?

So then when the login form is submitted it generate and HttpRequest toward the /login resource that is handled by this method declared into a controller class:

@RequestMapping(value="/login" , method=RequestMethod.POST)
public String do_login(HttpServletRequest req , Model md , HttpSession session , @Valid User user, BindingResult br)
{
    try
    {
        //System.out.println(br.getAllErrors().size());
        
        String username = req.getParameter("username");
        String password = req.getParameter("password");
        
        System.out.println("Username and pasword are : "+username +"  "+ password);
        if(br.getAllErrors().size() > 0){
            System.out.println("Server side validation takes place....");
        }
        else{
        Login_Model lm = new Login_Model();
        String message = lm.do_login_process(username, password);
        
        if(message.equals("login success"))
        {
            session.setAttribute("username", username);
            return "redirect:/myprofile";
        }
        else
        {
            md.addAttribute("error_msg", message);
        }
        }
        return "login";
    }
    catch(Exception e)
    {
        return "login";
    }
}

OK, and now I have the following doubts about this method:

  1. It take this object as input parameter: @Valid User user. Who pass to it? I think that it could depend by the fact that in the form I specified commandName="user" so Spring automatically do it. Is it correct?

  2. From what I have understand the @Valid annotation automatically call the validation process. How it happen? is something related to the AOP feature provided by Spring? or what? Why this @Valid annotation is related only to the javax.validation library and not also to the Hibernate validator (so this @Valid annotation validate also the field annotated with Hibernate validator annotation? why?)

  3. From what I have understand if the user insert wrong values into the login form I can obtain this error by the BindingResult br input parameter. Using the debugger I can see that this object contain the error message defined by the annotation defined into the User model object. How exactly does this work?


Solution

  • And here the first doubt: what exactly is the difference between the 2 used libraries javax.validation and org.hibernate.validator?

    javax.validation comes from the JSR-303 API. You can look at the API classes for instance in this Maven dependency.

    Hibernate validator is one of the implementations of JSR 303 (reference implementation actually), so it implements all of the API, but adds its own extensions, for instance the @NotBlank annotation you mention. Other implementation of JSR 303 is for instance Apache BVal.


    It takes this object as input parameter: @Valid User user. Who passes it to the handler method?

    Values to handler methods in Spring MVC are provided by implementations of interface HandlerMethodArgumentResolver. In your case the implementation that will be called to resolve User parameter will probably be ServletModelAttributeMethodProcessor. You can view source code and JavaDocs of these classes to see how they work internally.


    The @Valid annotation will automatically call the validation process. How does this happend? Does it use AOP? Why this @Valid annotation is related only to the javax.validation library and not also to the Hibernate validator (so this @Valid annotation validate also the field annotated with Hibernate validator annotation?)

    The validation process is invoked by ModelAttributeMethodProcessor, which is what previously mentioned class ServletModelAttributeMethodProcessor inherits from. It contains following method:

    protected void validateIfApplicable(WebDataBinder binder, MethodParameter parameter) {
        Annotation[] annotations = parameter.getParameterAnnotations();
        for (Annotation ann : annotations) {
            if (ann.annotationType().getSimpleName().startsWith("Valid")) {
                Object hints = AnnotationUtils.getValue(ann);
                binder.validate(hints instanceof Object[] ? (Object[]) hints : new Object[] {hints});
                break;
            }
        }
    }
    

    If you look closely, you will see condition for the following expression:

    ann.annotationType().getSimpleName().startsWith("Valid")
    

    This means that Spring will invoke the validation if the parameter has any annotation which starts with Valid. It might be JSR 303's @Valid or Spring's @Validated which supports validation groups. It could even be your custom annotation, as long as its name starts with Valid.


    From what I have understand if the user inserts wrong values into the login form I can obtain this error from the BindingResult handler parameter. Using the debugger I can see that this object contain the error message defined by the annotation defined into the User model object. How exactly works?

    Let's go back to the ModelAttributeMethodProcessor class. There is following code in its resolveArgument method:

    WebDataBinder binder = binderFactory.createBinder(webRequest, attribute, name);
    

    This creates instance of WebDataBinder on which the validation is invoked in the previously mentioned validateIfApplicable method. The validation itself populates the BindingResult which is then provided to the controller handler method via ErrorsMethodArgumentResolver class, which once again implements HandlerMethodArgumentResolver.


    TLDR: A lot of the thing you ask about in this question can be traced to various implementation of HandlerMethodArgumentResolver. I suggest to go through these classes and step through them using debugger to gain better understanding of them.