使用MultiActionController进行Spring-MVC表单验证

时间:2011-08-31 18:15:08

标签: java spring spring-mvc bean-validation

我正在尝试在MultiActionController上应用表单验证(我知道现在已经弃用了控制器类)。

我找到了这个答案(让我更接近目标,但并不完全): How to perform Spring validation in MultiActionController?

好的,所以根据javadoc,异常处理程序方法是控制器的方法,参数为(HttpServletRequest request, HttpServletResponse response, ExceptionClass exception)

据我所知(如果我错了,请纠正我),看起来工作流程如下:spring dispatcher-servlet转到控制器的请求方法,如果在其中发生异常执行(例如,由于验证失败导致的绑定异常),它将转到异常处理程序方法,它的异常参数与发生的异常匹配(如果存在这样的异常处理程序方法)。

但与常规控制器的方法不同,此异常处理程序方法没有命令对象参数。 所以我的问题是如何在该方法中访问与请求一起发送的命令对象,其中发生了绑定异常(由于验证错误)?

例如,当我使用注释验证时,我在请求处理程序方法中访问BindingResult和命令对象的(作为方法参数),因此如果出现验证错误,我可以使用命令对象数据加载返回的ModelAndView。

然而,我的异常处理程序方法(在我的MultiActionController中),以

结尾
BindException bindException = (BindException) bindingException.getRootCause();
return new ModelAndView("myFormView").addAllObjects(bindException.getModel());

- 提交无效数据后,由于无法找到命令对象,因此无法呈现我的JSP视图(“myFormView”),我得到了异常。

谢谢!

更多信息:

我的控制器(SearchBookController)中的实际请求处理程序方法如下所示:

public ModelAndView list(HttpServletRequest request, HttpServletResponse response, Book book) throws Exception {

    ModelMap modelMap = new ModelMap();

    //getting a list of books according to the propertiest of the command object book...
    modelMap.addAttribute("bookList", bookDAO.listBooks(book));

    return new ModelAndView("bookForm", modelMap);
}

我还向控制器添加了以下异常处理程序方法:

public ModelAndView hanldeBindException(HttpServletRequest request, HttpServletResponse response, ServletRequestBindingException bindingException) {
    // do what you want right here

    //I WOULD LIKE TO ADD HERE THE SUBMITTED BOOK AND THE FETCHED BOOKLIST TO THE ModelAndView, BUT I DO NOT KNOW HOW TO DO IT

    BindException bindException = (BindException) bindingException.getRootCause();
    return new ModelAndView("bookForm").addAllObjects(bindException.getModel());
}

这是我在servlet-dispatcher.xml中将我的验证器添加到SearchBookController的方法:

<bean name="/book/search.htm" class="com.books.web.SearchBookController" p:validators-ref="searchBookValidator" >
    <property name="bookDAO" ref="myBookDAO" />
</bean>

<bean id="searchBookValidator" class="com.books.validator.SearchBookValidator" />

验证器现在只是确保ValidationUtils.rejectIfEmptyOrWhitespace验证书籍属性。

我的视图(bookForm.jsp)显示了提交字段和搜索结果(它在呈现结果视图时重新显示提交的字段)。因此,在提交之后,视图应该同时获得book命令对象和bookList对象。

bookForm.jsp看起来像这样:

    <tr>
        <td>Details :</td>
        <td><form:input path="details" /></td>
        <td><form:errors path="details" cssClass="error"/></td>
    </tr>

(详情是Book的其中一个字段。)

以下是我尝试加载bookForm.jsp时的异常消息(甚至在提交之前,只是当我尝试加载页面以便填写表单时):

(***当我从servlet-dispatcher.xml中的控制器定义中删除p:validators-ref="searchBookValidator"时,jsp页面在提交之前和之后正确加载。

HTTP Status 500 - 

--------------------------------------------------------------------------------

type Exception report

message 

description The server encountered an internal error () that prevented it from fulfilling this request.

exception 

org.apache.jasper.JasperException: An exception occurred processing JSP page /WEB-INF/jsp/bookForm.jsp at line 209

206:        --%>
207:        <tr>
208:            <td>Details :</td>
209:            <td><form:input path="details" /></td>
210:            <td><form:errors path="details" cssClass="error"/></td>
211:        </tr>
212:        <tr>


Stacktrace:
    org.apache.jasper.servlet.JspServletWrapper.handleJspException(JspServletWrapper.java:510)
    org.apache.jasper.servlet.JspServletWrapper.service(JspServletWrapper.java:413)
    org.apache.jasper.servlet.JspServlet.serviceJspFile(JspServlet.java:313)
    org.apache.jasper.servlet.JspServlet.service(JspServlet.java:260)
    javax.servlet.http.HttpServlet.service(HttpServlet.java:717)
    org.springframework.web.servlet.view.InternalResourceView.renderMergedOutputModel(InternalResourceView.java:238)
    org.springframework.web.servlet.view.AbstractView.render(AbstractView.java:250)
    org.springframework.web.servlet.DispatcherServlet.render(DispatcherServlet.java:1047)
    org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:817)
    org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:719)
    org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:644)
    org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:549)
    javax.servlet.http.HttpServlet.service(HttpServlet.java:617)
    javax.servlet.http.HttpServlet.service(HttpServlet.java:717)


root cause 

java.lang.IllegalStateException: Neither BindingResult nor plain target object for bean name 'book' available as request attribute
    org.springframework.web.servlet.support.BindStatus.<init>(BindStatus.java:141)
    org.springframework.web.servlet.tags.form.AbstractDataBoundFormElementTag.getBindStatus(AbstractDataBoundFormElementTag.java:174)
    org.springframework.web.servlet.tags.form.AbstractDataBoundFormElementTag.getPropertyPath(AbstractDataBoundFormElementTag.java:194)
    org.springframework.web.servlet.tags.form.AbstractDataBoundFormElementTag.getName(AbstractDataBoundFormElementTag.java:160)
    org.springframework.web.servlet.tags.form.AbstractDataBoundFormElementTag.autogenerateId(AbstractDataBoundFormElementTag.java:147)
    org.springframework.web.servlet.tags.form.AbstractDataBoundFormElementTag.resolveId(AbstractDataBoundFormElementTag.java:138)
    org.springframework.web.servlet.tags.form.AbstractDataBoundFormElementTag.writeDefaultAttributes(AbstractDataBoundFormElementTag.java:122)
    org.springframework.web.servlet.tags.form.AbstractHtmlElementTag.writeDefaultAttributes(AbstractHtmlElementTag.java:408)
    org.springframework.web.servlet.tags.form.InputTag.writeTagContent(InputTag.java:140)
    org.springframework.web.servlet.tags.form.AbstractFormTag.doStartTagInternal(AbstractFormTag.java:102)
    org.springframework.web.servlet.tags.RequestContextAwareTag.doStartTag(RequestContextAwareTag.java:79)
    org.apache.jsp.WEB_002dINF.jsp.bookForm_jsp._jspx_meth_form_005finput_005f0(bookForm_jsp.java:593)
    org.apache.jsp.WEB_002dINF.jsp.bookForm_jsp._jspService(bookForm_jsp.java:326)
    org.apache.jasper.runtime.HttpJspBase.service(HttpJspBase.java:70)
    javax.servlet.http.HttpServlet.service(HttpServlet.java:717)
    org.apache.jasper.servlet.JspServletWrapper.service(JspServletWrapper.java:377)
    org.apache.jasper.servlet.JspServlet.serviceJspFile(JspServlet.java:313)
    org.apache.jasper.servlet.JspServlet.service(JspServlet.java:260)
    javax.servlet.http.HttpServlet.service(HttpServlet.java:717)
    org.springframework.web.servlet.view.InternalResourceView.renderMergedOutputModel(InternalResourceView.java:238)
    org.springframework.web.servlet.view.AbstractView.render(AbstractView.java:250)
    org.springframework.web.servlet.DispatcherServlet.render(DispatcherServlet.java:1047)
    org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:817)
    org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:719)
    org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:644)
    org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:549)
    javax.servlet.http.HttpServlet.service(HttpServlet.java:617)
    javax.servlet.http.HttpServlet.service(HttpServlet.java:717)

更新

按照下面的答案,我现在可以从异常处理程序方法访问命令对象。我仍然有一个问题:如果我提交应该触发错误消息的数据,例如通过

<tr>
    <td>Details :</td>
    <td><form:input path="details" /></td>
    <td><form:errors path="details" cssClass="error"/></td>
</tr>

在结果视图JSP中 - 我没有看到该错误消息。

我的验证器如下所示:

public class SearchBookValidator implements Validator {

    //......

    @Override
    public void validate(Object target, Errors errors) {
        ValidationUtils.rejectIfEmptyOrWhitespace(errors, "details", "details.required");
    }
}

和我的messages.properties包含以下行:

details.required=details are required

为了帮助我看看会发生什么,我在控制器的hanldeBindException中包含了以下代码:

    Map mp = bindException.getModel();
    for (Object o : mp.entrySet()) {
        Map.Entry pairs = (Map.Entry)o;
        System.out.println(pairs.getKey() + " = " + pairs.getValue());
    }

当我提交故意错误数据的表单时(即详细信息文本字段为空),我在控制台上收到以下输入:

command = com.books.domain.Book@1173447
org.springframework.validation.BindingResult.command = org.springframework.validation.BeanPropertyBindingResult: 1 errors
Field error in object 'command' on field 'details': rejected value []; codes [details.required.command.details,details.required.details,details.required.java.lang.String,details.required]; arguments []; default message [null]

你能看出为什么我没有通过<form:errors path="details" cssClass="error"/>收到任何错误消息吗?

3 个答案:

答案 0 :(得分:1)

您的Stacktrace消息

  

java.lang.IllegalStateException:BindingResult和bean名称'book'的普通目标对象都不可用作请求属性

好吧,我想你有一个Spring表单,如下所示(注意 commandName 属性)

<form:form commandName="book">

但是,在呈现页面时,表单标签会查找名为 book 的任何请求属性。如果Spring确实找到任何名为book的请求属性,您将看到这条好消息

  

BindingResult和bean名称'book'的普通目标对象都不能作为请求属性

list 方法中,我们可以看到您的命令对象未包含在模型中

public ModelAndView list(HttpServletRequest request, HttpServletResponse response, Book book) throws Exception {
    /**
      * Book object has not been added to the model
      */ 
    ModelMap modelMap = new ModelMap();
    modelMap.addAttribute("bookList", bookDAO.listBooks(book);

    return new ModelAndView("bookForm", modelMap);
}

改为使用

public ModelAndView list(HttpServletRequest request, HttpServletResponse response, Book book) throws Exception {
    return new ModelAndView("bookForm")
           .addAttribute("bookList", bookDAO.listBooks(book))
           .addAttribute(book);
}

一些注意事项:如果抛出的异常与作为参数的异常匹配,则只会调用您的异常处理程序

// It will be just called when some validation or binding Exception occurs
// Otherwise, Spring will bypass it
public ModelAndView hanldeBindException(HttpServletRequest request, HttpServletResponse response, ServletRequestBindingException bindingException) {
    BindException bindException = (BindException) bindingException.getRootCause();

    BindingResult bindingResult = (BindingResult) bindException.getModel().get(BindingResult.MODEL_KEY_PREFIX + "book");

    /**
      * bindingResult.getTarget() returns submitted Book object
      */

    return new ModelAndView("bookForm")
           .addAllObjects(bindException.getModel())
           .addAttribute("bookList", bookDAO.listBooks(bindingResult.getTarget()));
}

<强>更新

您是否注册了消息来源?

<!--IT MUST BE CALLED messageSource-->
<bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource">
    <property name="basenames" value="ValidationMessages"/>
</bean>

以前的设置使用类路径根目录中的ValidationMessages.properties。根据您的属性文件更新

答案 1 :(得分:1)

这已经晚了4年,但希望这会有助于其他人。与rapt的最后一个问题有关,为什么他仍然没有打印<form:errors path="details"/>。这是因为MultiActionController的默认commandName是&#34;命令&#34; (从他的日志输出中可以看出)

command = com.books.domain.Book@1173447
org.springframework.validation.BindingResult.command = org.springframework.validation.BeanPropertyBindingResult: 1 errors
Field error in object 'command' on field 'details': rejected value []; codes [details.required.command.details,details.required.details,details.required.java.lang.String,details.required]; arguments []; default message [null]

要获取正确的commandName,请覆盖getCommandName方法以返回所需的commandName(&#34;书&#34;在rapt的情况下),或通过命名约定派生它,如下所示

protected String getCommandName(Object command) {
    return Conventions.getVariableName(command);
}

字段的错误&#34;详细信息&#34;现在应该出现了。

答案 2 :(得分:0)

我只是像你一样在Spring MultiActionController中尝试验证。为了从ResourceBundles获取消息我仍然有问题(仍然找不到)但我只是尝试在我的验证器类(验证函数)中设置这样的默认消息:

ValidationUtils.rejectIfEmpty(errors, "fieldName","required.field","this field requred!");

但我仍然无法在我的jsp中看到错误消息然后我尝试在jsp中更改表单commmandName,如下所示:

<form:form action="update.htm" commandName="command">
   <!-- Form Field and so on -->
</form:form>

它有效! (还记得在你的控制器中对你的对象名称进行调整)我仍在寻找一种方法来将commandName更改为我们的自定义名称,例如“book”,而不是默认的“command”对象名。

对不起,如果没有帮助。