Spring-MVC多操作控制器表单验证

3
我正在尝试在MultiActionController上应用表单验证(我知道控制器类现在已过时)。
我找到了这个答案(让我更接近我的目标,但不完全):如何在MultiActionController中执行Spring验证? 根据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)同时显示提交字段和搜索结果(当结果视图被呈现时,它重新显示提交的字段)。因此,在提交后,视图应该获取书籍命令对象和bookList对象。

bookForm.jsp如下所示:

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

(details是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

为了帮助我查看发生了什么,我在控制器的handleBindException中包含了以下代码:
    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"/> 没有收到任何错误信息吗?


你能发布渲染页面的输出吗(“无法呈现,因为找不到命令对象”)? - Arthur Ronald
谢谢。我刚刚在我的主帖标题为 MORE INFO 的地方添加了输出和更多细节。 - rapt
3个回答

1

您的堆栈跟踪消息

java.lang.IllegalStateException: 请求属性中不存在绑定结果或名为“book”的纯目标对象

好的,我想您可能有一个如下所示的Spring表单(请注意commandName属性)

<form:form commandName="book">

然而,当你的页面被渲染时,表单标签会寻找名为book的任何请求属性。如果Spring找到了名为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 文件。根据你自己的属性文件进行更新。

谢谢。您说得对,需要将命令对象添加到我从请求处理程序返回的ModelAndView中。实际上,我确实将命令对象添加到了ModelAndView中,但是在这里的代码中不小心省略了它。顺便说一下,我还需要将其添加到从异常处理程序返回的ModelAndView中,因为我仍然需要在视图中使用它。BindingResult bindingResult = (BindingResult) bindException.getModel().get(BindingResult.MODEL_KEY_PREFIX + "command"); 确实让我可以访问命令对象 - 谢谢! - rapt
我想提一下,后缀应该是“command”,而不是表单的commandName(在我的情况下是“book”)(我刚刚得到了bindException.getModel().get(BindingResult.MODEL_KEY_PREFIX +“book”)的null值,所以我沿着Map bindException.getModel() 迭代,并看到命令保存在名称“command”下)。然而,我仍然有一个问题: JSP中的错误标签没有输出所需的错误消息。请在我的主帖子标题UPDATE下查看更多详细信息。 - rapt
我有<bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource" p:basename="messages"/>。为了测试目的,我在视图(bookForm.jsp)中添加了<spring:message code="details.required"/>并显示了参数“需要详细信息”。但是当我提交空的详细信息字段时,没有显示错误消息。 - rapt
请注意我之前提到的bindException.getModel()的内容 - 它列出了它正在寻找的代码codes [details.required.command.details,details.required.details,details.required.java.lang.String,details.required];,然后它说它没有找到这些代码的参数arguments [];(但为什么呢?),然后它还在寻找默认消息default message [null](不确定它是什么意思)。无论如何,看起来它无法看到属性文件。有任何想法为什么吗? - rapt
@rapt 请参考https://dev59.com/2k3Sa4cB1Zd3GeqPyMpn和https://dev59.com/-HE85IYBdhLWcg3wgDhI,希望两者都能对您有所帮助。祝好! - Arthur Ronald

1
这已经晚了4年,但希望能够帮助其他人。关于rapt最后一个问题的原因,为什么他仍然没有打印出<form:errors path="details"/>。那是因为MultiActionController的默认commandName是"command"(从他的日志输出中可以看到)。
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,在rapt的情况下,可以重写getCommandName方法以返回所需的commandName("book"),或按照以下命名约定派生它。
protected String getCommandName(Object command) {
    return Conventions.getVariableName(command);
}

字段“details”的错误现在应该会出现了。

0

我正在尝试在Spring MultiActionController中进行验证,就像您所做的那样。获取ResourceBundles中的消息仍然存在问题(仍在寻找解决方法),但我只是尝试在我的验证器类(validate函数)中设置默认消息,如下所示:

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

但我仍然无法在我的JSP中看到错误消息,然后我尝试像这样更改JSP中的表单命令名称:

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

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

如果这没有帮助,对不起。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接