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 注解
相关推荐
咕噜咕噜啦啦16 小时前
Java期末习题速通
java·开发语言
盐真卿16 小时前
python2
java·前端·javascript
一嘴一个橘子17 小时前
mybatis - 动态语句、批量注册mapper、分页插件
java
组合缺一17 小时前
Json Dom 怎么玩转?
java·json·dom·snack4
危险、18 小时前
一套提升 Spring Boot 项目的高并发、高可用能力的 Cursor 专用提示词
java·spring boot·提示词
kaico201818 小时前
JDK11新特性
java
钊兵18 小时前
java实现GeoJSON地理信息对经纬度点的匹配
java·开发语言
jiayong2318 小时前
Tomcat性能优化面试题
java·性能优化·tomcat
秋刀鱼程序编程18 小时前
Java基础入门(五)----面向对象(上)
java·开发语言
纪莫18 小时前
技术面:MySQL篇(InnoDB的锁机制)
java·数据库·java面试⑧股