Spring MVC: проверка, получение после перенаправления, частичные обновления, оптимистичный параллелизм, полевая безопасность

[Это список часто задаваемых вопросов о Spring MVC, которые решаются аналогичным образом. Я разместил их здесь, так что я могу легко ссылаться на них из других вопросов]

Как обновить только несколько полей объекта модели с помощью форм?

Как использовать шаблон Post-Redirect-Get с Spring MVC, особенно с проверка формы?

Как защитить определенные поля в моих сущностях?

Как реализовать оптимистический контроль параллелизма?


person Neil McGuigan    schedule 13.03.2015    source источник
comment
Пожалуйста, взгляните также на этот вопрос stackoverflow.com/questions/28845387/   -  person Jack    schedule 17.03.2015


Ответы (1)


  1. Чтобы частично обновить объект, вы должны использовать @SessionAttributes для сохранения модели в сеансе между запросами. Вы можете использовать скрытые поля формы, но сессия более безопасна.

  2. Чтобы использовать P/R/G с проверкой, используйте flashAttributes

  3. Чтобы защитить поля, используйте webDataBinder.setAllowedFields("field1","field2",...) или создайте класс, специфичный для формы, а затем скопируйте значения в свою сущность. Сущностям не требуются сеттеры для идентификатора и версии (при использовании Hibernate).

  4. Чтобы использовать Optimistic Concurrency Control, используйте аннотацию @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();
    }
}

Фу.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 {
}
person Neil McGuigan    schedule 13.03.2015
comment
Спасибо за ваш ответ, однако, у меня есть несколько проблем. Первая проблема в моем случае будет проверена только адрес электронной почты, поэтому, если я не введу действительный адрес электронной почты, он покажет небольшое сообщение, в противном случае я даже могу отправить пустую форму на сервер. Также я должен показать сообщение об успешной отправке так, как я это сделал? - person Jack; 17.03.2015
comment
@Jack использует проверки @NotBlank и @Email из Hibernate Validator. Для сообщения об успешном завершении установите атрибут flash с именем message или аналогичный, а затем отобразите это сообщение после перенаправления в jsp. - person Neil McGuigan; 17.03.2015
comment
Убедитесь, что Hibernate Validator настроен правильно. В мавене: <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-validator</artifactId> <version>5.1.2.Final</version> </dependency> - person Neil McGuigan; 17.03.2015
comment
Я понял, что форма не сохраняется, однако сообщения об ошибках не отображаются. - person Jack; 17.03.2015
comment
Теперь все работает, большое спасибо, могу я узнать, как настроить сообщения? Также как иметь ограничения на поле, которое является необязательным? в одном из полей у меня есть @Digits (фракция = 0, целое число = 14) @Size (минимум = 10, максимум = 14), и если я не ввожу какое-либо значение, оно показывает, что размер должен быть между 7 и 14 числовое значение за пределами ( ‹14 цифр›.‹0 цифр› ожидается), однако это поле является необязательным, и только при его вводе должны применяться ограничения. - person Jack; 17.03.2015
comment
Спасибо, последний вопрос: поскольку я не использую для этого EscapeXML, а значение действия добавляется автоматически, должен ли я что-либо сделать для защиты формы? - person Jack; 17.03.2015
comment
Давайте продолжим это обсуждение в чате. - person Neil McGuigan; 17.03.2015
comment
Это мне очень помогло. Спасибо. Я хотел бы больше объяснений о SessionStatus. - person Al Grant; 11.04.2017
comment
"redirect:" + request.getRequestURI(); в моем случае не сработало, это привело к тому, что контекст сервлета дважды появлялся в заголовке ответа HTTP 302, поэтому мой браузер получил ошибку 404. Вместо этого мне пришлось сделать "redirect:" + "/.." + request.getRequestURI(); - person Marcus Junius Brutus; 24.05.2017