表单验证
Spring Boot 表单验证完整指南
Spring Boot 提供了强大的表单验证功能,主要基于 Java Bean Validation API (JSR 380) 实现。以下是完整的表单验证解决方案:
- 基础配置
添加依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
启用验证
Spring Boot 自动配置了验证功能,无需额外配置。
- 常用验证注解
字段级验证
| 注解 | 说明 |
|——|——|
| @NotNull
| 不能为null |
| @NotEmpty
| 不能为null或空(字符串、集合、Map、数组) |
| @NotBlank
| 字符串不能为null且必须包含非空白字符 |
| @Size(min=, max=)
| 长度限制 |
| @Min(value)
| 数字最小值 |
| @Max(value)
| 数字最大值 |
| @DecimalMin(value)
| 小数值最小值 |
| @DecimalMax(value)
| 小数值最大值 |
| @Digits(integer=, fraction=)
| 数字位数限制 |
| @Email
| 电子邮件格式 |
| @Pattern(regexp=)
| 正则表达式匹配 |
| @Past
| 必须是过去日期 |
| @PastOrPresent
| 过去或现在日期 |
| @Future
| 必须是将来日期 |
| @FutureOrPresent
| 将来或现在日期 |
类级验证
| 注解 | 说明 |
|——|——|
| @Valid
| 级联验证关联对象 |
| @Validated
| 分组验证 |
- 完整实现示例
实体类定义
public class UserForm {
@NotBlank(message = "用户名不能为空")
@Size(min = 2, max = 20, message = "用户名长度必须在2-20个字符之间")
private String username;
@NotBlank(message = "密码不能为空")
@Size(min = 6, max = 20, message = "密码长度必须在6-20个字符之间")
private String password;
@Email(message = "邮箱格式不正确")
private String email;
@Pattern(regexp = "^1[3-9]\\d{9}$", message = "手机号格式不正确")
private String phone;
@Min(value = 18, message = "年龄必须大于18岁")
@Max(value = 100, message = "年龄必须小于100岁")
private Integer age;
@NotNull(message = "生日不能为空")
@Past(message = "生日必须是过去日期")
private LocalDate birthday;
// getters and setters
}
控制器处理
@RestController
@RequestMapping("/users")
public class UserController {
@PostMapping
public ResponseEntity<?> createUser(@Valid @RequestBody UserForm userForm,
BindingResult bindingResult) {
if (bindingResult.hasErrors()) {
// 处理验证错误
return ResponseEntity.badRequest().body(bindingResult.getAllErrors());
}
// 业务逻辑处理
return ResponseEntity.ok("用户创建成功");
}
}
- 高级功能
自定义验证注解
// 定义注解
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = PhoneValidator.class)
public @interface Phone {
String message() default "无效的手机号码";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
// 实现验证逻辑
public class PhoneValidator implements ConstraintValidator<Phone, String> {
private static final Pattern PHONE_PATTERN = Pattern.compile("^1[3-9]\\d{9}$");
@Override
public boolean isValid(String value, ConstraintValidatorContext context) {
if (value == null) {
return true; // 结合@NotNull使用
}
return PHONE_PATTERN.matcher(value).matches();
}
}
分组验证
public interface CreateGroup {}
public interface UpdateGroup {}
public class UserForm {
@Null(groups = CreateGroup.class, message = "ID必须为空")
@NotNull(groups = UpdateGroup.class, message = "ID不能为空")
private Long id;
// 其他字段...
}
// 控制器中使用
@PostMapping
public ResponseEntity<?> createUser(@Validated(CreateGroup.class) @RequestBody UserForm userForm) {
// ...
}
国际化错误消息
在
resources/messages.properties
中添加:userForm.username.notBlank=用户名不能为空 userForm.username.size=用户名长度必须在{min}到{max}个字符之间
在实体类中使用:
@NotBlank(message = "{userForm.username.notBlank}") @Size(min = 2, max = 20, message = "{userForm.username.size}") private String username;
统一异常处理
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<Map<String, Object>> handleValidationException(
MethodArgumentNotValidException ex) {
List<String> errors = ex.getBindingResult()
.getFieldErrors()
.stream()
.map(error -> error.getField() + ": " + error.getDefaultMessage())
.collect(Collectors.toList());
Map<String, Object> body = new LinkedHashMap<>();
body.put("timestamp", LocalDateTime.now());
body.put("status", HttpStatus.BAD_REQUEST.value());
body.put("errors", errors);
return ResponseEntity.badRequest().body(body);
}
}
- 前端表单验证配合
Thymeleaf 表单示例
<form th:action="@{/users}" th:object="${userForm}" method="post">
<div>
<label>用户名:</label>
<input type="text" th:field="*{username}">
<span th:if="${#fields.hasErrors('username')}"
th:errors="*{username}" class="error"></span>
</div>
<!-- 其他字段... -->
<button type="submit">提交</button>
</form>
- 测试验证
@SpringBootTest
@AutoConfigureMockMvc
class UserControllerTest {
@Autowired
private MockMvc mockMvc;
@Test
void createUserWithInvalidData() throws Exception {
String invalidUserJson = "{\"username\":\"a\",\"password\":\"123\",\"email\":\"invalid\"}";
mockMvc.perform(post("/users")
.contentType(MediaType.APPLICATION_JSON)
.content(invalidUserJson))
.andExpect(status().isBadRequest())
.andExpect(jsonPath("$.errors").isArray())
.andDo(print());
}
}
总结
Spring Boot 表单验证提供了从简单到复杂的完整解决方案:
- 使用标准注解快速实现基础验证
- 通过自定义注解扩展验证规则
- 利用分组验证实现不同场景的验证逻辑
- 结合国际化支持多语言错误消息
- 统一异常处理返回友好的错误信息
- 前端后端协同验证提升用户体验
对于复杂业务场景,还可以结合 Spring 的 Validator
接口实现更灵活的验证逻辑。
在 Java Web 开发中实现类似 Laravel 的表单错误处理机制,可以通过以下两种方式实现(分别演示基础 Servlet 和 Spring MVC 的实现):
方法一:基础 Servlet + JSP 实现
- 表单页面(form.jsp)
jsp
复制
<%@ page contentType="text/html;charset=UTF-8" %>
<html>
<body>
<%-- 显示错误信息 --%>
<% if (request.getSession().getAttribute("errors") != null) { %>
<div class="alert alert-danger">
<%= request.getSession().getAttribute("errors") %>
</div>
<% request.getSession().removeAttribute("errors"); %>
<% } %>
<form action="/submit" method="post">
<input type="text" name="username"
value="<%= session.getAttribute("old_username") != null ?
session.getAttribute("old_username") : "" %>">
<button type="submit">提交</button>
</form>
</body>
</html>
- 处理表单的 Servlet
java
复制
@WebServlet("/submit")
public class FormServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 获取表单数据
String username = request.getParameter("username");
// 验证逻辑
if (username == null || username.trim().isEmpty()) {
HttpSession session = request.getSession();
// 存储旧数据和错误信息
session.setAttribute("old_username", username);
session.setAttribute("errors", "用户名不能为空");
// 重定向回表单页
response.sendRedirect("/form.jsp");
return;
}
// 验证通过的处理...
response.sendRedirect("/success");
}
}
方法二:Spring MVC 实现(推荐)
- 控制器类
java
复制
@Controller
public class FormController {
@GetMapping("/form")
public String showForm(Model model) {
// 初始化表单对象
if (!model.containsAttribute("form")) {
model.addAttribute("form", new FormData());
}
return "form";
}
@PostMapping("/submit")
public String handleSubmit(
@Valid @ModelAttribute("form") FormData formData,
BindingResult bindingResult,
RedirectAttributes redirectAttributes) {
if (bindingResult.hasErrors()) {
// 自动携带错误信息和表单数据回传
redirectAttributes.addFlashAttribute("org.springframework.validation.BindingResult.form", bindingResult);
redirectAttributes.addFlashAttribute("form", formData);
return "redirect:/form";
}
// 验证通过的处理...
return "redirect:/success";
}
}
- 表单对象(DTO)
java
复制
public class FormData {
@NotBlank(message = "用户名不能为空")
private String username;
// Getter/Setter
}
- Thymeleaf 模板(form.html)
html
运行
复制
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<body>
<!-- 错误信息显示 -->
<div th:if="${#fields.hasErrors('*')}" class="alert alert-danger">
<ul>
<li th:each="err : ${#fields.errors('*')}" th:text="${err}"></li>
</ul>
</div>
<form action="/submit" method="post" th:object="${form}">
<input type="text" th:field="*{username}"
th:class="${#fields.hasErrors('username') ? 'is-invalid' : ''}">
<button type="submit">提交</button>
</form>
</body>
</html>
关键实现要点:
错误信息传递:
• 使用RedirectAttributes
(Spring)或HttpSession
(Servlet)存储错误信息• 通过
addFlashAttribute()
实现数据自动清除旧数据保持:
• 在表单字段中使用th:field
(Thymeleaf)或 JSP 表达式回显旧值• Spring 会自动保持绑定对象的数据
验证机制:
• 使用 JSR-303 验证注解(如@NotBlank
)• 通过
BindingResult
获取验证错误视图技术集成:
• Thymeleaf 的#fields
工具对象可简化错误显示• 支持动态添加错误样式(如
is-invalid
)
工作流程示例:
用户访问
/form
显示空表单提交无效数据到
/submit
服务器验证失败后:
• 将错误信息和表单数据存入 Flash 域• 重定向回
/form
表单页面自动显示错误信息
用户修正后重新提交
扩展功能建议:
- 自定义错误格式:
java
复制
@ControllerAdvice
public class CustomExceptionHandler {
@ExceptionHandler(MethodArgumentNotValidException.class)
public String handleValidationExceptions(MethodArgumentNotValidException ex, RedirectAttributes redirectAttributes) {
redirectAttributes.addFlashAttribute("errors", ex.getBindingResult().getAllErrors());
return "redirect:/form";
}
}
- 国际化错误消息:
properties
复制
# messages.properties
NotBlank.formData.username=用户名不能为空
- AJAX 提交支持:
javascript
复制
$.ajax({
type: "POST",
url: "/submit",
data: formData,
success: function(response) {
if (response.errors) {
// 动态更新错误显示
}
}
});
这种方式既保持了类似 Laravel 的便捷性,又符合 Java 企业级应用的开发规范,推荐在 Spring Boot 项目中结合 Thymeleaf 模板引擎使用。