Spring Validation教程

由 Gemini-3.0-pro-preview 生成

Spring Core的Validation主要基于 JSR-380 (Bean Validation 2.0/3.0) 标准。Spring本身不直接实现校验逻辑,而是提供了一套机制来集成第三方的实现(通常是 Hibernate Validator)。

下面是一个完全不使用Spring Boot,仅使用Spring Framework 6 + Hibernate Validator的完整教程。


1. 理解核心概念

在开始写代码前,你需要明白三个角色的关系:

  1. JSR-380 API (Jakarta Validation) : 定义了校验的接口和注解(如 @NotNull, @Size)。这只是标准,没有代码逻辑。
  2. Hibernate Validator : 它是JSR-380的参考实现。真正干活(检查字段是否为空)的是它。
  3. Spring Core : 它是粘合剂 。通过 LocalValidatorFactoryBean 将 Hibernate Validator 集成到 Spring 容器中,让你能在代码里轻松注入并使用。

2. 环境搭建 (Maven依赖)

由于没有Spring Boot Starter,我们需要手动引入所有必要的包。

注意:这里我们使用 Spring 6 和 Jakarta EE (现代标准)。

pom.xml:

xml 复制代码
<dependencies>
    <!-- 1. Spring Context (Spring的核心) -->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>6.1.3</version>
    </dependency>

    <!-- 2. Jakarta Validation API (标准接口) -->
    <dependency>
        <groupId>jakarta.validation</groupId>
        <artifactId>jakarta.validation-api</artifactId>
        <version>3.0.2</version>
    </dependency>

    <!-- 3. Hibernate Validator (校验器的具体实现) -->
    <dependency>
        <groupId>org.hibernate.validator</groupId>
        <artifactId>hibernate-validator</artifactId>
        <version>8.0.1.Final</version>
    </dependency>

    <!-- 4. Expression Language (EL表达式支持) -->
    <!-- Hibernate Validator 依赖 EL 表达式来解析错误信息,Spring Boot内置了,但Core通过Main方法运行需要手动加 -->
    <dependency>
        <groupId>org.glassfish</groupId>
        <artifactId>jakarta.el</artifactId>
        <version>4.0.2</version>
    </dependency>
</dependencies>

3. 定义待校验的实体 (Java Bean)

创建一个普通的User类,使用标准注解。

java 复制代码
package com.example.demo.model;

import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;

public class User {

    @NotBlank(message = "用户名不能为空")
    private String username;

    @Size(min = 6, max = 20, message = "密码长度必须在6-20之间")
    private String password;

    @Min(value = 18, message = "未成年人禁止注册")
    private int age;

    @Email(message = "邮箱格式不正确")
    private String email;

    // Getters and Setters (必须有,否则Validation无法读取字段值)
    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; }
    public int getAge() { return age; }
    public void setAge(int age) { this.age = age; }
    public String getEmail() { return email; }
    public void setEmail(String email) { this.email = email; }
}

4. Spring 配置类

这是脱离Spring Boot最关键的一步。我们需要手动定义校验器的Bean。

核心类是:LocalValidatorFactoryBean。它既实现了Spring的校验接口,也实现了Jakarta的校验接口,会在启动时自动检测Classpath下的Hibernate Validator。

java 复制代码
package com.example.demo.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
import org.springframework.validation.beanvalidation.MethodValidationPostProcessor;

@Configuration
@ComponentScan(basePackages = "com.example.demo")
public class AppConfig {

    /**
     * 配置全局校验器工厂
     * 作用:让Spring识别Hibernate Validator,并将其注册到容器中。
     */
    @Bean
    public LocalValidatorFactoryBean validator() {
        return new LocalValidatorFactoryBean();
    }

    /**
     * 开启方法级别的校验支持 (可选,但在Service层校验很有用)
     * 作用:支持在方法参数上使用 @NotNull, @Min 等注解,
     * 以及在类上使用 @Validated 注解。
     */
    @Bean
    public MethodValidationPostProcessor methodValidationPostProcessor() {
        MethodValidationPostProcessor processor = new MethodValidationPostProcessor();
        processor.setValidator(validator());
        return processor;
    }
}

5. 两种使用方式

在后端开发中,通常有两种校验场景:

  1. 手动校验:在代码里显式调用校验逻辑(类似于Controller接收到JSON后)。
  2. 方法级校验 (AOP):调用Service方法时自动拦截校验参数。

方式一:手动注入 Validator 进行校验

创建一个Service,模拟处理业务逻辑。

java 复制代码
package com.example.demo.service;

import com.example.demo.model.User;
import jakarta.validation.ConstraintViolation;
import jakarta.validation.Validator; // 注意是 jakarta 包
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.Set;

@Service
public class UserService {

    @Autowired
    private Validator validator; // 注入我们在AppConfig里配置的LocalValidatorFactoryBean

    public void registerUser(User user) {
        // 1. 执行校验
        // validate方法会返回一个Set,包含所有违规信息。如果Set为空,说明校验通过。
        Set<ConstraintViolation<User>> violations = validator.validate(user);

        // 2. 判断结果
        if (!violations.isEmpty()) {
            // 有错误,打印出来或抛出异常
            for (ConstraintViolation<User> violation : violations) {
                System.out.println("校验失败: " + violation.getPropertyPath() + " " + violation.getMessage());
            }
            throw new IllegalArgumentException("参数校验失败");
        }

        System.out.println("用户 " + user.getUsername() + " 注册成功!");
    }
}

方式二:方法级别的自动校验 (@Validated)

这种方式利用了Spring AOP,通过 MethodValidationPostProcessor 实现。

java 复制代码
package com.example.demo.service;

import jakarta.validation.constraints.NotNull;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;

@Service
@Validated // 1. 告诉Spring这个类需要被AOP代理进行校验
public class OrderService {

    // 2. 直接在参数前加注解
    public void createOrder(@NotNull(message = "订单ID不能为空") String orderId) {
        System.out.println("订单 " + orderId + " 创建成功");
    }
}

6. 运行测试 (Main方法)

最后,我们通过 AnnotationConfigApplicationContext 启动Spring容器来测试一下。

java 复制代码
package com.example.demo;

import com.example.demo.config.AppConfig;
import com.example.demo.model.User;
import com.example.demo.service.OrderService;
import com.example.demo.service.UserService;
import jakarta.validation.ConstraintViolationException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class Main {
    public static void main(String[] args) {
        // 1. 启动 Spring 容器
        ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);

        // --- 测试 1: 手动校验 (UserService) ---
        System.out.println("--- 测试 1: 手动校验 ---");
        UserService userService = context.getBean(UserService.class);
        
        User user = new User();
        user.setUsername(""); // 违规:NotBlank
        user.setAge(10);      // 违规:Min(18)
        
        try {
            userService.registerUser(user);
        } catch (Exception e) {
            System.out.println("捕获异常: " + e.getMessage());
        }

        // --- 测试 2: 方法级自动校验 (OrderService) ---
        System.out.println("\n--- 测试 2: 方法级自动校验 ---");
        OrderService orderService = context.getBean(OrderService.class);
        
        try {
            orderService.createOrder(null); // 传入null,触发 @NotNull
        } catch (ConstraintViolationException e) {
            System.out.println("捕获方法校验异常: " + e.getMessage());
        }
    }
}

总结 (Junior Developer 知识点复盘)

  1. 为什么没有Spring Boot也能跑? Spring Boot只是帮你把 LocalValidatorFactoryBean 自动配置好了(放在了 ValidationAutoConfiguration 类里)。在这里,我们在 AppConfig 里自己 new 了一个,效果是一样的。

  2. jakarta.validation vs org.springframework.validation

    • 我们在实体类上用的是 jakarta.validation (JSR标准)。
    • Spring也有自己的 org.springframework.validation.Validator 接口,但 LocalValidatorFactoryBean 很聪明,它同时实现了这两个接口。最佳实践是使用标准的 JSR 注解
相关推荐
Hello.Reader2 小时前
DTO / VO / BO / Entity 分层到底怎么用?
java·分层
云梦谭2 小时前
AI 生成的FreeSWITCH 呼出流程深度分析freeswitch-1.10.12.-release
java·前端·php
随机昵称_1234562 小时前
RSA私钥解密乱码问题
java·非对称加密
龙亘川2 小时前
【课程2.4】开发环境搭建:K8s集群部署、芋道框架集成、ThingsBoard对接
java·容器·kubernetes·智慧城市·智慧城市一网统管 ai 平台
pyniu2 小时前
项目实站day7--功能之营业额统计,用户数量统计
java·开发语言·spring boot·spring
一周困⁸天.2 小时前
K8S-NetworkPolicy
java·开发语言
真上帝的左手2 小时前
3. 代码管理-构建工具-Maven
java·maven
JIngJaneIL3 小时前
基于Java旅游信息推荐系统(源码+数据库+文档)
java·开发语言·数据库·vue.js·spring boot·旅游
梦未3 小时前
Java多态性与类生命周期
java