Spring MVC:验证,Post-Redirect-Get,部分更新,乐观并发,字段安全

4

[这是关于Spring MVC常见问题的列表,这些问题有相似的解决方式。我将它们发布在这里,以便我可以轻松地从其他问题中引用它们]

如何使用表单仅更新模型实体的几个字段?

如何在Spring MVC中使用Post-Redirect-Get模式,特别是在表单验证方面?

如何保护实体中的某些字段?

如何实现乐观锁定控制


请也看一下这个问题:https://dev59.com/f4jca4cB1Zd3GeqPwWg2 - Jack
1个回答

15
  1. 要部分更新实体,请使用@SessionAttributes将模型存储在请求之间的会话中。虽然可以使用隐藏表单字段,但是会话更加安全。

  2. 要在验证中使用P/R/G,请使用flashAttributes

  3. 要保护字段,请使用webDataBinder.setAllowedFields("field1","field2",...)或创建特定于表单的类,然后将值复制到您的实体中。如果使用Hibernate,则实体不需要id和version的setter。

  4. 要使用乐观锁控制,请在实体中使用@Version注释,并在控制器上使用@SessionAttributes

示例代码:

@Controller
@RequestMapping("/foo/edit/{id}")
@SessionAttributes({FooEditController.ATTRIBUTE_NAME})
public class FooEditController {

    static final String ATTRIBUTE_NAME = "foo";
    static final String BINDING_RESULT_NAME = "org.springframework.validation.BindingResult." + ATTRIBUTE_NAME;

    @Autowired
    private FooRepository fooRepository;

    /*
     Without this, user can set any Foo fields they want with a custom HTTP POST
     setAllowedFields disallows all other fields. 
     You don't even need setters for id and version, as Hibernate sets them using reflection
    */
    @InitBinder
    void allowFields(WebDataBinder webDataBinder){
        webDataBinder.setAllowedFields("name"); 
    }

    /*
     Get the edit form, or get the edit form with validation errors
    */
    @RequestMapping(method = RequestMethod.GET)
    String getForm(@PathVariable("id") long id, Model model) {

        /* if "fresh" GET (ie, not redirect w validation errors): */
        if(!model.containsAttribute(BINDING_RESULT_NAME)) {
            Foo foo = fooRepository.findOne(id);
            if(foo == null) throw new ResourceNotFoundException();
            model.addAttribute(ATTRIBUTE_NAME, foo);
        }

        return "foo/edit-form";
    }

    /*
     @Validated is better than @Valid as it can handle http://docs.jboss.org/hibernate/validator/5.1/reference/en-US/html/chapter-groups.html
     @ModelAttribute will load Foo from session but also set values from the form post
     BindingResult contains validation errors
     RedirectAttribute.addFlashAttribute() lets you put stuff in session for ONE request
     SessionStatus lets you clear your SessionAttributes
    */
    @RequestMapping(method = RequestMethod.POST)
    String saveForm(
       @Validated @ModelAttribute(ATTRIBUTE_NAME) Foo foo,
       BindingResult bindingResult, 
       RedirectAttributes redirectAttributes, 
       HttpServletRequest request, 
       SessionStatus sessionStatus
    ) {

        if(!bindingResult.hasErrors()) {
            try {
                fooRepository.save(foo);
            } catch (JpaOptimisticLockingFailureException exp){
                bindingResult.reject("", "This record was modified by another user. Try refreshing the page.");
            }
        }

        if(bindingResult.hasErrors()) {

            //put the validation errors in Flash session and redirect to self
            redirectAttributes.addFlashAttribute(BINDING_RESULT_NAME, bindingResult);
            return "redirect:" + request.getRequestURI();
        }

        sessionStatus.setComplete(); //remove Foo from session

        redirectAttributes.addFlashAttribute("message", "Success. The record was saved");
        return "redirect:" + request.getRequestURI();
    }
}

Foo.java:

@Entity
public class Foo {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Version //for optimistic concurrency control
    private int version;

    @NotBlank
    private String name;

    public Long getId() {
        return id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

}

edit-form.jsp(Twitter Bootstrap 兼容):


<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ taglib uri="http://www.springframework.org/tags" prefix="spring" %>
<%@ taglib uri="http://www.springframework.org/tags/form" prefix="form" %>

<form:form modelAttribute="foo">

    <spring:hasBindErrors name="foo">
        <c:if test="${errors.globalErrorCount > 0}">
            <div class="alert alert-danger" role="alert"><form:errors/></div>
        </c:if>
    </spring:hasBindErrors>

    <c:if test="${not empty message}">
      <div class="alert alert-success"><c:out value="${message}"/></div>
    </c:if>

    <div class="panel panel-default">
        <div class="panel-heading">
            <button class="btn btn-primary" name="btnSave">Save</button>
        </div>

        <div class="panel-body">

            <spring:bind path="name">
                <div class="form-group${status.error?' has-error':''}">
                    <form:label path="name" class="control-label">Name <form:errors path="name"/></form:label>
                    <form:input path="name" class="form-control" />
                </div>
            </spring:bind>

        </div>
    </div>

</form:form>

ResourceNotFoundException.java:

@ResponseStatus(HttpStatus.NOT_FOUND)
public class ResourceNotFoundException extends RuntimeException {
}

谢谢您的回答,但我还有一些问题。在我的情况下,只验证电子邮件地址,因此如果我没有输入有效的电子邮件地址,它将显示一个小消息,否则它不会,我甚至可以向服务器提交空白表单。另外,我应该像我这样显示成功提交消息吗? - Jack
确保正确设置了Hibernate Validator。在Maven中: <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-validator</artifactId> <version>5.1.2.Final</version> </dependency> - Neil McGuigan
现在它可以工作了,非常感谢。请问如何自定义消息?还有,如何对可选字段设置约束?其中一个字段我已经使用了 @Digits(fraction = 0, integer = 14) 和 @Size(min = 10, max = 14),但如果我不输入任何值,它会显示大小必须在7到14之间,而且数字超出范围(期望<14个数字>。<0个数字>)。然而,该字段是可选的,只有在输入时才应用约束。 - Jack
让我们在聊天中继续这个讨论 - Neil McGuigan
"redirect:" + request.getRequestURI(); didn't work in my case, it resulted in the servlet context appearing twice in the HTTP 302 response header so it gave my browser a 404. Instead, I had to do "redirect:" + "/.." + request.getRequestURI(); - Marcus Junius Brutus
显示剩余5条评论

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