使用最小样板提交弹簧表单



我一直在试图弄清楚使用 spring 提交表单的最佳实践是什么,以及实现这一目标的最低样板是什么。

我认为以下内容是最佳实践特质

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

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

@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";
}
}

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

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

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

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

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

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;
}
}

您的服务如下所示:

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;
}

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

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";

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

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

根据@isah的答案,如果重定向仅在成功验证后发生,则代码可以简化为:

@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,
RedirectAttributes redirectAttrs) throws Exception {
if (bindingResult.hasErrors()) {
return "thepage";
}
// service logic
redirectAttrs.addFlashAttribute("success", "My Model was added successfully");
return "redirect:/thepage";
}
}

一种可能的方法是将原型用于 Web 窗体,而不是创建简单的项目,您可以选择从现有的 Web 窗体原型创建项目。它将为您提供足够的肉鸡板代码。您还可以制作自己的原型。 查看此链接以更深入地了解原型。 链接到 Java Spring 中的原型

最新更新