Spring Boot修改BindingResult后仍返回全部校验错误的问题
问题:Spring Boot校验优先级控制异常
练习Spring Boot校验功能时遇到如下问题:
需求是:带@NotEmpty注解的字段,若该注解触发(比如字段为空),则仅显示@NotEmpty的错误信息;只有当@NotEmpty未触发时,才显示该字段其他校验注解(如@Length、@Pattern)的错误。但提交空表单时,username这类字段会同时显示@NotEmpty和@Length的错误提示,不符合预期。
实体类代码
public class Student { @NotEmpty @NotNull @Length(min = 3, max = 10) private String username; @NotEmpty @Pattern(regexp = "((?=.*\\d)(?=.*[a-z])(?=.*[A-Z])(?=.*[@#$%]).{6,20})") private String password; @NotEmpty @Email private String email; @URL private String website; @Min(18) @Max(30) private int age; public Student() { super(); } public Student(String username, String password, String email, String website, int age) { super(); this.username = username; this.password = password; this.email = email; this.website = website; this.age = age; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public String getWebsite() { return website; } public void setWebsite(String website) { this.website = website; } public String getEmail() { return email; } public void setEmail(String email) { this.email = email; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } @Override public String toString() { return "Student [username=" + username + ", password=" + password + ", email=" + email + ", website=" + website + ", age=" + age + "]"; } }
Controller代码
@Controller @RequestMapping({ "validation", "" }) public class ValidationController { @RequestMapping(value = { "index1", "/" }, method = RequestMethod.GET) public String index(ModelMap modelMap) { modelMap.put("student", new Student()); return "/validation/index"; } @RequestMapping(value = { "resgister" }, method = RequestMethod.POST) public String resgister(ModelMap modelMap, RedirectAttributes redirectAttributes, @ModelAttribute("student") @Valid Student student, BindingResult bindingResult) { if(bindingResult.hasErrors()) { var errors = Helper.customErrorMessageOrder(bindingResult.getFieldErrors()); bindingResult = new BeanPropertyBindingResult(student, "student"); for(var e : errors) { bindingResult.addError(e); } return "/validation/index"; } redirectAttributes.addFlashAttribute("message", "Success"); return "redirect:/validation/index1"; } }
自定义错误处理方法(Helper类)
public static List<FieldError> customErrorMessageOrder(List<FieldError> listErrors){ var notEmptyListErrors = listErrors .stream() .filter(e -> e.getCode().equals("NotEmpty")) .collect(Collectors.toList()); if(notEmptyListErrors.size()==0) { return listErrors; } var alterListErrors = listErrors.stream().collect(Collectors.toList()); var newListErrors = notEmptyListErrors; for(var e : notEmptyListErrors) { alterListErrors.removeIf(e1->e1.getField().equals(e.getField())); } newListErrors.addAll(alterListErrors); return newListErrors; }
前端Thymeleaf表单代码
<form th:action="@{/validation/resgister}" method="post" th:object="${student}"> <table border="1"> <tr> <td>Username</td> <td><input type="text" th:field="*{username}"></td> <td th:if="${#fields.hasErrors('username')}"> <span th:errors="*{username}"></span> </td> </tr> <tr> <td>Password</td> <td><input type="text" th:field="*{password}"></td> <td th:if="${#fields.hasErrors('password')}"> <span th:errors="*{password}"></span> </td> </tr> <tr> <td>Email</td> <td><input type="text" th:field="*{email}"></td> <td th:if="${#fields.hasErrors('email')}"> <span th:errors="*{email}"></span> </td> </tr> <tr> <td>Website</td> <td><input type="text" th:field="*{website}"></td> <td th:if="${#fields.hasErrors('website')}"> <span th:errors="*{website}"></span> </td> </tr> <tr> <td>Age</td> <td><input type="text" th:field="*{age}"></td> <td th:if="${#fields.hasErrors('age')}"> <span th:errors="*{age}"></span> </td> </tr> <tr> <td colspan="2" style="text-align: center;"><input type="submit" value="submit"></td> </tr> </table> </form>
问题详情
已尝试在Controller中重新构建BindingResult,打印显示处理后的错误列表是正确的,但前端仍会显示该字段的所有错误信息,比如username字段同时出现@NotEmpty和@Length的提示,不符合需求。
解决方案
核心问题分析
你重新创建了BeanPropertyBindingResult对象,但这个新对象并没有被Spring MVC传递到视图中——方法内部重新赋值的bindingResult变量只是局部变量,Spring MVC使用的是方法参数中传入的原始BindingResult实例。
修正Controller逻辑
不要重新创建BindingResult,直接修改原始实例:清除所有错误后,添加过滤后的错误列表。
@RequestMapping(value = { "resgister" }, method = RequestMethod.POST) public String resgister(ModelMap modelMap, RedirectAttributes redirectAttributes, @ModelAttribute("student") @Valid Student student, BindingResult bindingResult) { if(bindingResult.hasErrors()) { List<FieldError> filteredErrors = Helper.customErrorMessageOrder(bindingResult.getFieldErrors()); // 清除原始错误 bindingResult.getAllErrors().clear(); // 添加过滤后的错误 filteredErrors.forEach(bindingResult::addError); return "/validation/index"; } redirectAttributes.addFlashAttribute("message", "Success"); return "redirect:/validation/index1"; }
优化错误过滤方法
简化customErrorMessageOrder方法,直接保留目标错误:
public static List<FieldError> customErrorMessageOrder(List<FieldError> listErrors){ // 收集所有触发NotEmpty错误的字段名 Set<String> notEmptyFields = listErrors.stream() .filter(e -> "NotEmpty".equals(e.getCode())) .map(FieldError::getField) .collect(Collectors.toSet()); // 过滤规则:触发NotEmpty的字段只保留NotEmpty错误,其他字段保留所有错误 return listErrors.stream() .filter(e -> !notEmptyFields.contains(e.getField()) || "NotEmpty".equals(e.getCode())) .collect(Collectors.toList()); }
这样处理后,当字段触发@NotEmpty时,只会显示该错误;未触发@NotEmpty的字段则正常显示所有校验错误,完全符合需求。
内容的提问来源于stack exchange,提问作者FalAn
相关产品推荐
相关产品推荐

