本文目录导读:

- 项目依赖配置
- 实体类 - User.java
- 控制器 - UserController.java
- 分组接口定义
- 修改实体类支持分组校验
- 全局异常处理 - GlobalExceptionHandler.java
- JSP页面 - register.jsp
- Spring配置文件
- 自定义校验注解示例
- RESTful API示例
- 测试用例
我将为您展示一个完整的Spring MVC数据校验案例,涵盖从依赖配置到控制器处理的完整流程。
项目依赖配置
Maven依赖 (pom.xml)
<dependencies>
<!-- Spring MVC -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.3.30</version>
</dependency>
<!-- Hibernate Validator (JSR-380实现) -->
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
<version>6.2.5.Final</version>
</dependency>
<!-- Java Bean Validation API -->
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
<version>2.0.1.Final</version>
</dependency>
<!-- JSTL标签库 -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
<!-- Servlet API -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
<scope>provided</scope>
</dependency>
</dependencies>
实体类 - User.java
package com.example.entity;
import javax.validation.constraints.*;
import java.util.Date;
import org.springframework.format.annotation.DateTimeFormat;
public class User {
@NotBlank(message = "用户名不能为空")
@Size(min = 3, max = 20, message = "用户名长度必须在3-20字符之间")
@Pattern(regexp = "^[a-zA-Z0-9_]+$", message = "用户名只能包含字母、数字和下划线")
private String username;
@NotBlank(message = "密码不能为空")
@Size(min = 6, max = 20, message = "密码长度必须在6-20字符之间")
private String password;
@NotBlank(message = "确认密码不能为空")
private String confirmPassword;
@NotBlank(message = "邮箱不能为空")
@Email(message = "邮箱格式不正确")
private String email;
@Min(value = 18, message = "年龄不能小于18岁")
@Max(value = 100, message = "年龄不能超过100岁")
private Integer age;
@NotBlank(message = "手机号不能为空")
@Pattern(regexp = "^1[3-9]\\d{9}$", message = "手机号格式不正确")
private String phone;
@DateTimeFormat(pattern = "yyyy-MM-dd")
@NotNull(message = "生日不能为空")
@Past(message = "生日必须是过去的时间")
private Date birthday;
@NotNull(message = "性别不能为空")
private String gender;
@Size(max = 200, message = "个人简介不能超过200个字符")
private String bio;
// 自定义校验方法 - 密码确认
@AssertTrue(message = "两次密码输入不一致")
public boolean isPasswordConfirmed() {
if (password == null || confirmPassword == null) {
return false;
}
return password.equals(confirmPassword);
}
// Getters and Setters
// ... 省略getter/setter方法
}
控制器 - UserController.java
package com.example.controller;
import com.example.entity.User;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
import java.util.HashMap;
import java.util.Map;
@Controller
@RequestMapping("/user")
public class UserController {
// 显示注册页面
@GetMapping("/register")
public String showRegisterForm(Model model) {
model.addAttribute("user", new User());
return "register";
}
// 传统表单校验
@PostMapping("/register")
public String register(@Valid @ModelAttribute("user") User user,
BindingResult bindingResult,
Model model) {
// 如果有验证错误
if (bindingResult.hasErrors()) {
// 收集所有验证错误信息
Map<String, String> errors = new HashMap<>();
for (FieldError error : bindingResult.getFieldErrors()) {
errors.put(error.getField(), error.getDefaultMessage());
}
model.addAttribute("errors", errors);
model.addAttribute("user", user);
return "register";
}
// 校验通过,处理注册逻辑
model.addAttribute("message", "注册成功!");
return "success";
}
// 使用@Validated校验单个参数
@PostMapping("/checkUsername")
@ResponseBody
public Map<String, Object> checkUsername(@RequestParam @Pattern(regexp = "^[a-zA-Z0-9_]{3,20}$",
message = "用户名格式不正确") String username) {
Map<String, Object> result = new HashMap<>();
// 模拟检查用户名是否可用
boolean available = !username.equalsIgnoreCase("admin");
result.put("available", available);
result.put("message", available ? "用户名可用" : "用户名已被使用");
return result;
}
// 使用分组校验
@PostMapping("/update")
public String updateUser(@Validated(UpdateGroup.class) @ModelAttribute("user") User user,
BindingResult bindingResult) {
if (bindingResult.hasErrors()) {
return "register";
}
return "success";
}
}
分组接口定义
package com.example.validation;
// 注册分组
public interface RegisterGroup {
}
// 更新分组
public interface UpdateGroup {
}
修改实体类支持分组校验
package com.example.entity;
import javax.validation.constraints.*;
import java.util.Date;
import org.springframework.format.annotation.DateTimeFormat;
import com.example.validation.RegisterGroup;
import com.example.validation.UpdateGroup;
public class User {
@NotBlank(message = "用户名不能为空", groups = RegisterGroup.class)
@Size(min = 3, max = 20, message = "用户名长度必须在3-20字符之间", groups = {RegisterGroup.class, UpdateGroup.class})
private String username;
@NotBlank(message = "密码不能为空", groups = RegisterGroup.class)
@Size(min = 6, max = 20, message = "密码长度必须在6-20字符之间", groups = RegisterGroup.class)
private String password;
@NotBlank(message = "邮箱不能为空", groups = RegisterGroup.class)
@Email(message = "邮箱格式不正确", groups = {RegisterGroup.class, UpdateGroup.class})
private String email;
@Min(value = 18, message = "年龄不能小于18岁", groups = {RegisterGroup.class, UpdateGroup.class})
@Max(value = 100, message = "年龄不能超过100岁", groups = {RegisterGroup.class, UpdateGroup.class})
private Integer age;
// 其他字段省略...
}
全局异常处理 - GlobalExceptionHandler.java
package com.example.exception;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
@ControllerAdvice
public class GlobalExceptionHandler {
// 处理参数校验异常
@ExceptionHandler(MethodArgumentNotValidException.class)
@ResponseBody
@ResponseStatus(HttpStatus.BAD_REQUEST)
public Map<String, Object> handleMethodArgumentNotValid(MethodArgumentNotValidException ex) {
Map<String, Object> result = new HashMap<>();
Map<String, String> errors = new HashMap<>();
for (FieldError error : ex.getBindingResult().getFieldErrors()) {
errors.put(error.getField(), error.getDefaultMessage());
}
result.put("code", 400);
result.put("message", "参数校验失败");
result.put("errors", errors);
return result;
}
// 处理参数校验异常(基于方法参数)
@ExceptionHandler(ConstraintViolationException.class)
@ResponseBody
@ResponseStatus(HttpStatus.BAD_REQUEST)
public Map<String, Object> handleConstraintViolation(ConstraintViolationException ex) {
Map<String, Object> result = new HashMap<>();
Map<String, String> errors = new HashMap<>();
Set<ConstraintViolation<?>> violations = ex.getConstraintViolations();
for (ConstraintViolation<?> violation : violations) {
errors.put(violation.getPropertyPath().toString(), violation.getMessage());
}
result.put("code", 400);
result.put("message", "参数校验失败");
result.put("errors", errors);
return result;
}
}
JSP页面 - register.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<!DOCTYPE html>
<html>
<head>用户注册</title>
<style>
.error { color: red; font-size: 12px; }
.form-group { margin-bottom: 15px; }
label { display: inline-block; width: 100px; }
</style>
</head>
<body>
<h2>用户注册</h2>
<form:form modelAttribute="user" method="post" action="register">
<div class="form-group">
<label>用户名:</label>
<form:input path="username" />
<form:errors path="username" cssClass="error" />
<c:if test="${errors['username'] != null}">
<span class="error">${errors['username']}</span>
</c:if>
</div>
<div class="form-group">
<label>密码:</label>
<form:password path="password" />
<form:errors path="password" cssClass="error" />
</div>
<div class="form-group">
<label>确认密码:</label>
<form:password path="confirmPassword" />
<form:errors path="passwordConfirmed" cssClass="error" />
</div>
<div class="form-group">
<label>邮箱:</label>
<form:input path="email" />
<form:errors path="email" cssClass="error" />
</div>
<div class="form-group">
<label>年龄:</label>
<form:input path="age" />
<form:errors path="age" cssClass="error" />
</div>
<div class="form-group">
<label>手机号:</label>
<form:input path="phone" />
<form:errors path="phone" cssClass="error" />
</div>
<div class="form-group">
<label>生日:</label>
<form:input path="birthday" />
<form:errors path="birthday" cssClass="error" />
</div>
<div class="form-group">
<label>性别:</label>
<form:radiobutton path="gender" value="male" />男
<form:radiobutton path="gender" value="female" />女
<form:errors path="gender" cssClass="error" />
</div>
<div class="form-group">
<label>个人简介:</label>
<form:textarea path="bio" rows="3" cols="30" />
<form:errors path="bio" cssClass="error" />
</div>
<div class="form-group">
<input type="submit" value="注册" />
</div>
</form:form>
</body>
</html>
Spring配置文件
spring-mvc.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!-- 启用Spring MVC注解驱动 -->
<mvc:annotation-driven />
<!-- 扫描控制器 -->
<context:component-scan base-package="com.example.controller" />
<!-- 配置视图解析器 -->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/views/" />
<property name="suffix" value=".jsp" />
</bean>
<!-- 配置本地化解析器和消息源 -->
<bean id="messageSource" class="org.springframework.context.support.ReloadableResourceBundleMessageSource">
<property name="basename" value="classpath:ValidationMessages" />
<property name="defaultEncoding" value="UTF-8" />
</bean>
</beans>
自定义校验注解示例
package com.example.annotation;
import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.*;
@Documented
@Constraint(validatedBy = PhoneValidator.class)
@Target({ElementType.FIELD, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface ValidPhone {
String message() default "手机号格式不正确";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
// 校验器实现
package com.example.validation;
import com.example.annotation.ValidPhone;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
public class PhoneValidator implements ConstraintValidator<ValidPhone, String> {
private static final String PHONE_PATTERN = "^1[3-9]\\d{9}$";
@Override
public void initialize(ValidPhone constraintAnnotation) {
}
@Override
public boolean isValid(String phone, ConstraintValidatorContext context) {
if (phone == null || phone.isEmpty()) {
return true; // 是否为空由@NotBlank处理
}
return phone.matches(PHONE_PATTERN);
}
}
RESTful API示例
package com.example.controller;
import com.example.entity.User;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
import javax.validation.constraints.Email;
import javax.validation.constraints.Min;
import java.util.HashMap;
import java.util.Map;
@RestController
@RequestMapping("/api/users")
@Validated
public class UserApiController {
// 创建用户
@PostMapping
public Map<String, Object> createUser(@Valid @RequestBody User user) {
Map<String, Object> result = new HashMap<>();
// 处理创建逻辑
result.put("code", 200);
result.put("message", "用户创建成功");
result.put("data", user);
return result;
}
// 查询用户(带参数校验)
@GetMapping("/{id}")
public Map<String, Object> getUser(@PathVariable @Min(value = 1, message = "用户ID必须大于0") Long id) {
Map<String, Object> result = new HashMap<>();
result.put("code", 200);
result.put("data", "用户" + id + "的信息");
return result;
}
// 更新邮箱
@PutMapping("/{id}/email")
public Map<String, Object> updateEmail(@PathVariable Long id,
@RequestParam @Email(message = "邮箱格式不正确") String email) {
Map<String, Object> result = new HashMap<>();
result.put("code", 200);
result.put("message", "邮箱更新成功");
return result;
}
}
测试用例
package com.example.controller;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.result.MockMvcResultMatchers;
@SpringBootTest
@AutoConfigureMockMvc
public class UserControllerTest {
@Autowired
private MockMvc mockMvc;
@Test
public void testRegisterWithInvalidData() throws Exception {
mockMvc.perform(MockMvcRequestBuilders.post("/user/register")
.contentType(MediaType.APPLICATION_FORM_URLENCODED)
.param("username", "a") // 太短
.param("password", "123") // 太短
.param("email", "invalid-email") // 无效邮箱
.param("age", "15")) // 年龄太小
.andExpect(MockMvcResultMatchers.status().isOk())
.andExpect(MockMvcResultMatchers.view().name("register"));
}
@Test
public void testRegisterWithValidData() throws Exception {
mockMvc.perform(MockMvcRequestBuilders.post("/user/register")
.contentType(MediaType.APPLICATION_FORM_URLENCODED)
.param("username", "john123")
.param("password", "password123")
.param("confirmPassword", "password123")
.param("email", "john@example.com")
.param("age", "25")
.param("phone", "13812345678")
.param("birthday", "1995-01-01")
.param("gender", "male"))
.andExpect(MockMvcResultMatchers.status().isOk())
.andExpect(MockMvcResultMatchers.view().name("success"));
}
}
这个完整的Spring MVC数据校验案例展示了:
- 核心功能:表单验证、参数验证、分组验证
- 技术特点:使用JSR-380规范、自定义注解、全局异常处理
- 实现方式:注解驱动、验证器、分组校验
- UI展示:错误消息显示、错误样式定制
- API支持:RESTful API的参数校验
这个案例可以很好地集成到实际项目中,提供完善的数据校验机制。