弹簧表单提交与minum样板

Joh*_*erg 10 java spring spring-mvc boilerplate spring-web

我一直试图弄清楚弹簧表单提交的最佳实践是什么,以及实现这一目标的最小样板是什么.

我认为以下是最佳实践特征

  • 启用验证并在验证失败时保留表单值
  • 禁用表单重新提交F5(即使用重定向)
  • 防止模型值出现在重定向(model.clear())之间的URL中

到目前为止,我已经想出了这个.

@Controller
@RequestMapping("/")
public class MyModelController {

    @ModelAttribute("myModel")
    public MyModel myModel() {
        return new MyModel();
    }

    @GetMapping
    public String showPage() {
        return "thepage";
    }

    @PostMapping
    public String doAction(
            @Valid @ModelAttribute("myModel") MyModel myModel,
            BindingResult bindingResult,
            Map<String, Object> model,
            RedirectAttributes redirectAttrs) throws Exception {
        model.clear();
        if (bindingResult.hasErrors()) {
            redirectAttrs.addFlashAttribute("org.springframework.validation.BindingResult.myModel", bindingResult);
            redirectAttrs.addFlashAttribute("myModel", myModel);
        } else {
            // service logic
        }
        return "redirect:/thepage";
    }
}
Run Code Online (Sandbox Code Playgroud)

有没有办法用更少的样板代码来做到这一点,或者这是实现这一目标所需的最少代码量?

isa*_*sah 4

首先,我不会违反Post/Redirect/Get (PRG)模式,这意味着我只会在表单成功发布时进行重定向。

其次,我会完全摆脱这种BindingResult风格。对于简单的情况来说这很好,但是一旦您需要更复杂的通知从服务/域/业务逻辑到达用户,事情就会变得很棘手。此外,您的服务可重用性并不高。

我要做的是将绑定的 DTO 直接传递给服务,这将验证 DTO 并在出现错误/警告时发出通知。通过这种方式,您可以将业务逻辑验证与JSR 303:Bean Validation结合起来。为此,您可以在服务中使用通知模式。

遵循通知模式,您将需要一个通用的通知包装器:

public class Notification<T> {
    private List<String> errors = new ArrayList<>();
    private T model; // model for which the notifications apply

    public Notification<T> pushError(String message) {
        this.errors.add(message);
        return this;
    }

    public boolean hasErrors() {
        return !this.errors.isEmpty();
    }

    public void clearErrors() {
        this.errors.clear();
    }

    public String getFirstError() {
        if (!hasErrors()) {
            return "";
        }
        return errors.get(0);
    }

    public List<String> getAllErrors() {
        return this.errors;
    }

    public T getModel() {
        return model;
    }

    public void setModel(T model) {
        this.model = model;
    }
}
Run Code Online (Sandbox Code Playgroud)

你的服务会是这样的:

public Notification<MyModel> addMyModel(MyModelDTO myModelDTO){
    Notification<MyModel> notification = new Notification();
    //if(JSR 303 bean validation errors) -> notification.pushError(...); return notification;
    //if(business logic violations) -> notification.pushError(...); return notification;
    return notification;
}
Run Code Online (Sandbox Code Playgroud)

然后你的控制器将是这样的:

Notification<MyModel> addAction = service.addMyModel(myModelDTO);
if (addAction.hasErrors()) {
    model.addAttribute("myModel", addAction.getModel());
    model.addAttribute("notifications", addAction.getAllErrors());
    return "myModelView"; // no redirect if errors
} 
redirectAttrs.addFlashAttribute("success", "My Model was added successfully");
return "redirect:/thepage";
Run Code Online (Sandbox Code Playgroud)

尽管hasErrors()检查仍然存在,但此解决方案更具可扩展性,因为您的服务可以通过新的业务规则通知继​​续发展。

我将保持非常简短的另一种方法RuntimeException是从您的服务中抛出一个自定义,该自定义RuntimeException可以包含必要的消息/模型,并用于@ControllerAdvice捕获此通用异常,从异常中提取模型和消息并将它们放入模型中。这样,您的控制器除了将绑定的 DTO 转发到服务之外什么也不做。