Spring MVC数据校验案例

wen java案例 2

本文目录导读:

Spring MVC数据校验案例

  1. 项目依赖配置
  2. 实体类 - User.java
  3. 控制器 - UserController.java
  4. 分组接口定义
  5. 修改实体类支持分组校验
  6. 全局异常处理 - GlobalExceptionHandler.java
  7. JSP页面 - register.jsp
  8. Spring配置文件
  9. 自定义校验注解示例
  10. RESTful API示例
  11. 测试用例

我将为您展示一个完整的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数据校验案例展示了:

  1. 核心功能:表单验证、参数验证、分组验证
  2. 技术特点:使用JSR-380规范、自定义注解、全局异常处理
  3. 实现方式:注解驱动、验证器、分组校验
  4. UI展示:错误消息显示、错误样式定制
  5. API支持:RESTful API的参数校验

这个案例可以很好地集成到实际项目中,提供完善的数据校验机制。

抱歉,评论功能暂时关闭!