【问题标题】:Spring MVC Controller, how to keep BindingResult errors, while emptying the form valuesSpring MVC Controller,如何保持 BindingResult 错误,同时清空表单值
【发布时间】:2016-05-10 07:56:35
【问题描述】:

我有一个使用 Spring MVC 控制器的 Web 表单。该表单由 Spring 验证。 当出现验证错误时,Spring 会显示相同的表单,预先填写用户输入的值和验证错误。

出于安全原因,我不希望表单预先填写用户输入的值,但我确实需要显示验证错误。

我该怎么做?

我通过查看 Spring MVC 源代码并了解 BINDING_RESULT_KEY 是如何构建的,从而实现了这种行为。这是源代码。

但是,这是一个 hack,它可能会停止在新版本的 Spring MVC 上工作。

我该如何正确实现这一目标?

package com.nespresso.ecommerce.naw.controller;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BeanPropertyBindingResult;
import org.springframework.validation.BindingResult;
import org.springframework.validation.Errors;
import org.springframework.validation.ObjectError;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

import javax.validation.Valid;


@Controller
@RequestMapping("/my_form")
public class MyFormController extends FrontEndController {
    final String MY_FORM_OBJECT_NAME = "myForm";
    final String BINDING_RESULT_KEY = BindingResult.MODEL_KEY_PREFIX + "myForm";

    @RequestMapping(method = RequestMethod.POST)
    public String post(@Valid @ModelAttribute("myForm") MyForm myForm, Errors errors, Model model) {
        if (errors.hasErrors()) {
            emptyMyFormWhileKeepingFormErrors(model);
            return "my_form";
        }

        return "redirect:/confirmation";
    }

    @ModelAttribute("myForm")
    public MyForm myForm() {
        return new MyForm();
    }

    private void emptyMyFormWhileKeepingFormErrors(Model model) {
        BeanPropertyBindingResult bindingResult = (BeanPropertyBindingResult) model.asMap().get(BINDING_RESULT_KEY);
        if (bindingResult == null) {
            return;
        }
        MyForm emptyForm = myForm();

        // set the empty form, so the form is not pre-filled with the previous values.
        // However, this clears the validation errors also
        model.addAttribute(MY_FORM_OBJECT_NAME, emptyForm);

        // re-attach the validation errors, and empty the rejectedValue
        BeanPropertyBindingResult updatedBindingResult = new BeanPropertyBindingResult(emptyForm, MY_FORM_OBJECT_NAME);
        for (ObjectError oe : bindingResult.getAllErrors()) {
            if (!(oe instanceof FieldError)) {
                updatedBindingResult.addError(oe);
            } else {
                FieldError fieldError = (FieldError) oe;

                String rejectedValue = null;   // that's the point, create a copy of the FieldError, emptying the rejectedValue;

                FieldError updatedFieldError = new FieldError(
                        MY_FORM_OBJECT_NAME,
                        fieldError.getField(),
                        rejectedValue,
                        fieldError.isBindingFailure(),
                        fieldError.getCodes(),
                        fieldError.getArguments(),
                        fieldError.getDefaultMessage());
                updatedBindingResult.addError(updatedFieldError);
            }
        }

        model.addAttribute(BINDING_RESULT_KEY, updatedBindingResult);
    }
}

【问题讨论】:

  • 要么您使用<form:*> 标签来显示模型上的值(以及在参数绑定期间拒绝的值)和验证错误,要么您使用带有<form:errors> 标签的普通<input>。没有别的办法。
  • “出于安全原因” - 您能分享您的安全问题吗?你可能想太多了。
  • 帕维尔,说得好。我正在使用 thymeleaf <input th:field="*{companyName}"/> 标签,它被扩展为 <input id="compayName" name="companyName" th:value="*{companyName}"/>。所以,我应该改成<input name="companyName"/>thymeleaf.org/doc/thymeleafspring.html#inputs 但是,这不是 java 代码,那么如何对未显示的值进行单元测试呢?

标签: spring-mvc


【解决方案1】:

编辑:添加另一个解决方案

您可以拥有一个空模型对象,同时保留以前的错误,只需通过org.springframework.beans.BeanUtils 将新值(来自新初始化的对象)复制到您的模型属性中:

@RequestMapping(method = RequestMethod.POST)
public String post(@Valid @ModelAttribute("myForm") MyForm myForm, Errors errors, Model model) {
    if (errors.hasErrors()) {
        MyForm emptyForm = new MyForm();
        BeanUtils.copyProperties(emptyForm, myForm);
        return "my_form";
    }

    return "redirect:/confirmation";
}

这样,您仍然可以显示以前的错误,并且表单是空的。

还有一个警告:如果在我使用 <input> 字段时可以正常工作,但对于确实使用来自 errors 的拒绝值的 <form:input> spring 增强字段则不行。

如果您更喜欢继续使用 <form:input> 字段,则必须创建另一个 BindingResult 对象并使用当前错误对其进行初始化,只需将拒绝的值设置为 null。您甚至可以重置所有字段或仅重置错误字段:

@RequestMapping(value = "/form", method=RequestMethod.POST)
public String update(@Valid @ModelAttribute Form form, BindingResult result, Model model) {
    if (result.hasErrors()) {
        // uncomment line below to reset all fields - by default only offending ones are
        //form = new Form();
        BeanPropertyBindingResult result2 = new BeanPropertyBindingResult(form, result.getObjectName());
        for(ObjectError error: result.getGlobalErrors()) {
            result2.addError(error);
        }
        for (FieldError error: result.getFieldErrors()) {
            result2.addError(new FieldError(error.getObjectName(), error.getField(), null, error.isBindingFailure(), error.getCodes(), error.getArguments(), error.getDefaultMessage()));
        }
        model.addAllAttributes(result2.getModel());
        return "my_form";
    }

    return "redirect:/confirmation";
}

【讨论】:

  • 这不起作用,因为“errors”变量包含一个 FieldError 的列表,而 FieldError 有一个 rejectValue 变量。因此,如果其中一个字段未通过验证,则会显示该字段的值。
  • @DavidPortabella:我用另一个允许使用 <form:input> 的解决方案更新了我的帖子。
  • 好的,我明白了,与我在问题中发布的代码的区别在于您使用errors.getObjectName() 或result.getObjectName() 来获取MY_FORM_OBJECT_NAME。好点,谢谢。
  • 完整答案,另见 Pavel 的评论:stackoverflow.com/questions/25326591/…
【解决方案2】:

我能想到的最简单的解决方案是不使用<form:*> 标签。而是使用标准的<input> 标签。

例如:

<form:form modelAttribute="userForm" method="post">
    Username: <form:input path="username" /><br>

    <form:errors path="password" element="div" />
    Password: <input path="password" ><br>

    <input type="submit" value="Save" />
</form:form>

在上面的示例中,密码字段将始终保持空白,同时仍显示验证错误。

【讨论】:

  • 这很好;但是帕维尔已经在对该问题的评论中给出了相同的答案。我只能将赏金奖励给一个人,所以我将奖励给 Serge,因为他也有一个有效的解决方案。
  • 您对赏金的处理是您的决定,但我不会将其标记为已接受的答案。寻求相同问题答案的访问者将获得针对简单问题的过度设计的解决方案:)
猜你喜欢
  • 1970-01-01
  • 2018-05-19
  • 2017-06-27
  • 2016-11-24
  • 1970-01-01
  • 2011-03-05
  • 1970-01-01
  • 2012-02-20
  • 2011-05-06
相关资源
最近更新 更多