Spring 表单提交最少样板
Spring form submission with minum boilerplate
我一直在努力弄清楚使用 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";
}
}
有没有办法用更少样板代码来做到这一点,或者这是实现这个所需的最少代码量吗?
一种可能的方法是使用 Web 表单原型,而不是创建简单的项目,您可以选择从现有的 Web 表单原型创建项目。它将为您提供足够的肉鸡板代码。您也可以制作自己的原型。
查看此 link 以更深入地了解原型。
Link To Archetypes in Java Spring
首先,我不会违反 Post/Redirect/Get (PRG) 模式,这意味着我只会在表单发布成功时重定向。
其次,我会完全摆脱 BindingResult
风格。这对于简单的情况很好,但是一旦您需要更复杂的通知来从 service/domain/business 逻辑到达用户,事情就会变得棘手。此外,您的服务可重用性不高。
我要做的是将绑定的 DTO 直接传递给服务,这将验证 DTO 并在 errors/warning 的情况下发出通知。通过这种方式,您可以将业务逻辑验证与 JSR 303:Bean 验证 相结合。
为此,您可以在服务中使用 Notification Pattern。
按照通知模式,您需要一个通用的通知包装器:
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
可以包含必要的 messages/models,并使用@ControllerAdvice
捕获这个通用异常,从异常中提取模型和消息并将它们放入模型中。这样,您的控制器只会将绑定的 DTO 转发给服务。
根据的回答,如果重定向仅在成功验证后发生,代码可以简化为:
@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";
}
}
我一直在努力弄清楚使用 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";
}
}
有没有办法用更少样板代码来做到这一点,或者这是实现这个所需的最少代码量吗?
一种可能的方法是使用 Web 表单原型,而不是创建简单的项目,您可以选择从现有的 Web 表单原型创建项目。它将为您提供足够的肉鸡板代码。您也可以制作自己的原型。 查看此 link 以更深入地了解原型。 Link To Archetypes in Java Spring
首先,我不会违反 Post/Redirect/Get (PRG) 模式,这意味着我只会在表单发布成功时重定向。
其次,我会完全摆脱 BindingResult
风格。这对于简单的情况很好,但是一旦您需要更复杂的通知来从 service/domain/business 逻辑到达用户,事情就会变得棘手。此外,您的服务可重用性不高。
我要做的是将绑定的 DTO 直接传递给服务,这将验证 DTO 并在 errors/warning 的情况下发出通知。通过这种方式,您可以将业务逻辑验证与 JSR 303:Bean 验证 相结合。 为此,您可以在服务中使用 Notification Pattern。
按照通知模式,您需要一个通用的通知包装器:
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
可以包含必要的 messages/models,并使用@ControllerAdvice
捕获这个通用异常,从异常中提取模型和消息并将它们放入模型中。这样,您的控制器只会将绑定的 DTO 转发给服务。
根据
@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";
}
}