Spring mvc validation exceptionhandler invoked bef

2020-07-06 05:33发布

问题:

I have following code :

public class StudentController extends BaseController {

@RequestMapping(value = "/student/edit", method = RequestMethod.POST)
    public String submitForm(@Valid @ModelAttribute(MODEL_ATTRIBUTE_STUDENT) StudentDTO edited,
                                         BindingResult bindingResult, RedirectAttributes attributes) {
        if (bindingResult.hasErrors()) {
            return STUDENT_EDIT_FORM_VIEW;
        }

        //some logic...
    }

@InitBinder
public void initBinder(WebDataBinder binder) {
    SimpleDateFormat dateFormat = new SimpleDateFormat("MM/dd/yyyy");
    dateFormat.setLenient(false);

    // true passed to CustomDateEditor constructor means convert empty String to null
    binder.registerCustomEditor(Date.class, new CustomDateEditor(dateFormat, true));
}
}

public class BaseController {
    @ExceptionHandler(Exception.class)
    protected ModelAndView handleAllException(Exception ex) {
        Long errorId = System.currentTimeMillis() + RandomInteger.generate(1, Integer.MAX_VALUE);

        //do something...

        Map<String, String> parameters = new HashMap<>();
        parameters.put("flash_message", "Things blew up...!");

        return new ModelAndView("/errorpage", parameters);
    }
}

Now, when there is a binding error (example in a date field user entered Feb 40th, 2013), then I expected the following block to be executed:

if (bindingResult.hasErrors()) {
    return STUDENT_EDIT_FORM_VIEW;
}

but instead the ExceptionHandler is executed and errorpage.html is returned..!

I am not using spring tags (form:form) in the view. I am using thymeleaf + boostrap + jquery EDIT: Here is the exception as requested -

org.springframework.validation.BindException: org.springframework.validation.BeanPropertyBindingResult: 1 errors
Field error in object 'student' on field 'responseDate': rejected value [02/30/2014]; codes [typeMismatch.student.responseDate,typeMismatch.responseDate,typeMismatch.java.util.Date,typeMismatch]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [student.responseDate,responseDate]; arguments []; default message [responseDate]]; default message [Failed to convert property value of type 'java.lang.String' to required type 'java.util.Date' for property 'responseDate'; nested exception is java.lang.IllegalArgumentException: Could not parse date: Unparseable date: "02/30/2014"]
    at org.springframework.web.method.annotation.ModelAttributeMethodProcessor.resolveArgument(ModelAttributeMethodProcessor.java:110)
    at org.springframework.web.method.support.HandlerMethodArgumentResolverComposite.resolveArgument(HandlerMethodArgumentResolverComposite.java:77)
    at org.springframework.web.method.support.InvocableHandlerMethod.getMethodArgumentValues(InvocableHandlerMethod.java:162)
    at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:123)
    at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:104)
    at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandleMethod(RequestMappingHandlerAdapter.java:745)
    at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:686)
    at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:80)
    at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:925)
    at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:856)
    at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:936)
    at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:838)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:647)
    at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:812)

StudentDTO class has bunch of dates that student needs to input. Looks like the following

public class StudentDTO {
private Long id;
private String state;
private String region;
private Date responseDate;
private Date stayLiftedDate;
...
...
//Then all getters and setters.
}

EDIT2:

Here is what happens if I throw an error and log stacktrace from handleAllException() method of basecontroller

java.lang.Exception: Sample Error
    at com.mycompany.controller.BaseController.handleAllException(BaseController.java:103)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:606)
    at org.springframework.web.method.support.InvocableHandlerMethod.invoke(InvocableHandlerMethod.java:219)
    at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:132)
    at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:104)
    at org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver.doResolveHandlerMethodException(ExceptionHandlerExceptionResolver.java:321)
    at org.springframework.web.servlet.handler.AbstractHandlerMethodExceptionResolver.doResolveException(AbstractHandlerMethodExceptionResolver.java:60)
    at org.springframework.web.servlet.handler.AbstractHandlerExceptionResolver.resolveException(AbstractHandlerExceptionResolver.java:136)
    at org.springframework.web.servlet.handler.HandlerExceptionResolverComposite.resolveException(HandlerExceptionResolverComposite.java:73)
    at org.springframework.web.servlet.DispatcherServlet.processHandlerException(DispatcherServlet.java:1148)
    at org.springframework.web.servlet.DispatcherServlet.processDispatchResult(DispatcherServlet.java:985)
    at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:939)
    at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:856)
    at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:936)
    at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:838)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:647)
    at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:812)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:728)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:305)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:210)
    at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:243)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:210)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
    at org.springframework.security.web.access.intercept.FilterSecurityInterceptor.invoke(FilterSecurityInterceptor.java:118)
    at org.springframework.security.web.access.intercept.FilterSecurityInterceptor.doFilter(FilterSecurityInterceptor.java:84)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
    at org.springframework.security.web.access.ExceptionTranslationFilter.doFilter(ExceptionTranslationFilter.java:113)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
    at org.springframework.security.web.authentication.AnonymousAuthenticationFilter.doFilter(AnonymousAuthenticationFilter.java:113)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
    at org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter.doFilter(SecurityContextHolderAwareRequestFilter.java:154)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
    at org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.doFilter(AbstractAuthenticationProcessingFilter.java:199)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
    at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:110)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
    at com.mycompany.security.CookieAuth.doFilter(CookieAuth.java:50)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
    at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:192)
    at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:160)
    at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:346)
    at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:259)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:243)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:210)
    at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:222)
    at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:123)
    at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:171)
    at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:100)
    at org.apache.catalina.valves.AccessLogValve.invoke(AccessLogValve.java:953)
    at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:118)
    at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:409)
    at org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1044)
    at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:607)
    at org.apache.tomcat.util.net.JIoEndpoint$SocketProcessor.run(JIoEndpoint.java:313)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
    at java.lang.Thread.run(Thread.java:744)

回答1:

You will notice the exception is thrown at ModelAttributeMethodProcessor#resolveArgument(..). It happens here

if (binder.getBindingResult().hasErrors()) {
    if (isBindExceptionRequired(binder, parameter)) {
        throw new BindException(binder.getBindingResult());
    }
}

So if there was an error parsing the date parameter (or any other error) and isBindExceptionRequest(..) returns true, the BindingResult is wrapped in a BindException and thrown. So what is isBindExceptionRequires(..)?

It's implemented as such

protected boolean isBindExceptionRequired(WebDataBinder binder, MethodParameter parameter) {
    int i = parameter.getParameterIndex();
    Class<?>[] paramTypes = parameter.getMethod().getParameterTypes();
    boolean hasBindingResult = (paramTypes.length > (i + 1) && Errors.class.isAssignableFrom(paramTypes[i + 1]));

    return !hasBindingResult;
}

and as the Javadoc says

Returns: true if the next method argument is not of type Errors.

In other words, if your handler method is declared like so

public String submitForm(@Valid @ModelAttribute(MODEL_ATTRIBUTE_STUDENT) StudentDTO edited,
                                     BindingResult bindingResult, RedirectAttributes attributes) {

a BindException will not be thrown since there is a parameter of type Errors (BindingResult is a sub type) next to the @ModelAttribute parameter.

This means that you are getting the exception from some other handler method where you don't have the BindingResult following a command object parameter. Or there is something else in your configuration which you aren't showing us.



回答2:

working sample:

@RequestMapping(value = "/student/edit", method = RequestMethod.POST)
public String submitForm(@Valid @ModelAttribute(MODEL_ATTRIBUTE_STUDENT)  
StudentDTO edited, BindingResult bindingResult) {
    if (bindingResult.hasErrors()) {
        return STUDENT_EDIT_FORM_VIEW;
    }

    //some logic...
}