当前位置: 首页 > news >正文

SpringBoot11-Spring Validation讲解

一、为什么要使用校验(Validation)

  • 输入可靠性:在请求一进系统边界(Controller)就拦截非法数据,避免脏数据深入业务层。

  • 安全与合规:隐藏/拒绝敏感或越权字段,减少注入与越权风险。

  • 开发效率:声明式注解代替大量 if-else 判空与格式判断。

  • 一致的错误返回:统一错误格式,前后端对齐更顺滑。

  • 可测试可维护:规则集中在 DTO/注解上,变更时定位清晰。

下面给你两个真实场景,能直观感受到为什么需要 Validation


场景 1:用户注册接口

如果没有校验:

@PostMapping("/register")
public UserVO register(@RequestBody UserDTO dto) {// dto.username 可能是空字符串// dto.email 可能是 "aaa"// dto.password 可能是 null// 业务逻辑里必须写一堆 if 判断,否则脏数据会直接进数据库
}

可能出现:

  • 用户名为空或长度为 1;

  • 邮箱不是有效格式;

  • 密码为空或太短;

  • 数据存进数据库后出错或以后出 bug。

用 Spring Validation:

public class UserDTO {@NotBlank(message = "用户名不能为空")@Size(min = 3, max = 20)private String username;@NotBlank @Emailprivate String email;@NotBlank @Size(min = 8)private String password;
}@PostMapping("/register")
public UserVO register(@Valid @RequestBody UserDTO dto) { ... }

当用户提交:

{ "username": "", "email": "abc", "password": "123" }

直接被框架拦截,返回:

{"code": "VALIDATION_ERROR","errors": [{"field": "username", "message": "用户名不能为空"},{"field": "email", "message": "必须是一个有效的电子邮件地址"},{"field": "password", "message": "长度必须至少为8"}]
}

开发者无需写大量 if-else,接口统一返回错误,数据库不会出现垃圾数据。


场景 2:修改用户资料(部分更新)

  • 创建新用户时:username 必填。

  • 修改资料时:id 必填,但 username 可以不改。

分组校验

public interface Create {}
public interface Update {}public class UserUpsertDTO {@NotNull(groups = Update.class) // 更新必须提供idprivate Long id;@NotBlank(groups = Create.class)private String username;@Email private String email;
}@PostMapping
public void create(@Validated(Create.class) @RequestBody UserUpsertDTO dto) { }@PutMapping
public void update(@Validated(Update.class) @RequestBody UserUpsertDTO dto) { }
  • 调用创建接口缺少 username 会报错;

  • 调用更新接口缺少 id 会报错;

  • 规则可复用,避免在代码里写大量条件判断。


💡总结

  • 没有 Validation:需要手动写 if 判断,代码冗余、易漏、难维护。

  • 有 Validation:只需在字段上加注解 + 全局异常处理,就能在请求入口自动拦截不合法数据,统一返回错误信息。

  • 对开发效率、数据安全和系统健壮性都有明显提升。

不要相信前端传来的参数是正确的,一定要做后台的表单校验!

二、开发步骤

2-1、添加依赖

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-validation</artifactId>
</dependency>

2-2、编写请求 DTO(带注解)

// Boot 3.x
import jakarta.validation.constraints.*;
import java.time.LocalDate;
import java.util.List;public class UserCreateDTO {@NotBlank(message = "用户名不能为空")@Size(min = 3, max = 20, message = "用户名长度应在 {min}-{max} 之间")private String username;@Email(message = "邮箱格式不正确")@NotBlank(message = "邮箱不能为空")private String email;@NotBlank@Size(min = 8, message = "密码至少 {min} 位")private String password;@Past(message = "生日必须是过去的日期")private LocalDate birthday;@Valid // 级联到嵌套对象@NotNullprivate AddressDTO address;@Valid // 校验集合中每个元素private List<@NotBlank(message = "标签不可为空字符串") String> tags;// getters/setters...
}class AddressDTO {@NotBlank private String city;@NotBlank private String street;
}

【注意】:

  • @NotBlank 仅适用于字符串;@NotEmpty 适用于集合/数组/字符串@NotNull 只校验非 null。

  • 基本类型(如 int不能为 null;要可空就用包装类型(如 Integer)。

2-3、在 Controller 中启用校验

import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import jakarta.validation.Valid;
import jakarta.validation.constraints.Positive;@RestController
@RequestMapping("/users")
@Validated // 启用方法级与参数级校验(如 PathVariable/RequestParam)
public class UserController {@PostMappingpublic UserVO create(@Valid @RequestBody UserCreateDTO dto) {// dto 已按注解校验,失败会抛 MethodArgumentNotValidExceptionreturn userService.create(dto);}@GetMapping("/{id}")public UserVO getById(@Positive @PathVariable Long id) {// 对路径变量做约束(需类上有 @Validated)return userService.getById(id);}
}

三、@Valid vs @Validated

  • @Valid:标准注解,级联/嵌套对象、请求体校验首选。

  • @Validated:Spring 扩展,启用分组方法级(参数/返回值)校验;通常加在 方法参数 上。

可以把 @Valid@Validated 理解成两个“触发校验的开关”,但它们的能力范围不一样。

这里说的 “触发校验的开关”,指的是:

Spring 本身并不会自动去检查你写在 Bean 上的 @NotNull @Size … 这些注解;
只有在某个地方加上 @Valid@Validated 时,Spring 才会调用底层的 Bean Validation 引擎(Hibernate Validator)去做校验。这就是“触发器”或者“开关”的意思。

所以,底层实现:
Spring 本身不做校验逻辑,它只是提供集成支持,真正做校验的是 Hibernate Validator(这是 JSR 380 的一个官方实现)。

注解来自主要用途
@ValidJSR-303 / Bean Validation 标准jakarta.validation.Validjavax.validation.Valid触发 标准 Bean 校验,支持 级联校验嵌套对象)。
@ValidatedSpring 自家扩展org.springframework.validation.annotation.Validated除了触发 Bean 校验,还支持分组校验方法级参数/返回值校验

在 Spring Controller 中进行输入校验

通常我们在 Spring 中实现 REST Controller 时,需要验证客户端传入的输入数据。我们可以验证的内容主要有三类:

  1. 请求体(request body)

  2. 路径变量(path variables)

  3. 查询参数(query parameters)--URL 上的查询参数

比如一个用户注册接口,你希望:

  • 请求体里的 JSON 数据(request body)必须符合要求;

  • 路径里的变量(path variables)不能乱填;

  • URL 上的查询参数(query parameters)也要合法。

常见的 3 种输入校验位置:

  1. Request Body —— 客户端发来的 JSON 或表单数据,比如注册时的用户信息。

  2. Path Variable —— URL 里的动态参数,比如 /users/{id} 中的 id

  3. Query Parameter —— URL 问号后面的参数,比如 ?page=1&size=10

示例1:请求体校验——嵌套对象

In the example, each user has an id, name, and address.

Name cannot be an empty string nor null and address cannot be null. Address class has street and city that cannot be empty nor null and postcode with size between 4 and 6 characters.

注解允许 null允许 "" 空串允许只包含空格
@NotNull❌ 不允许✅ 允许✅ 允许
@NotEmpty❌ 不允许❌ 不允许✅ 允许
@NotBlank❌ 不允许❌ 不允许❌ 不允许(会 trim)

编写controller:

postman发送请求:

校验通过,明显嵌套的Address的校验没有生效!

解决方式:@Valid嵌套校验

此时,postman原来的请求会报错:

示例2:请求参数(Request Parameters)和路径变量(Path Variables)的校验

对请求参数和路径变量的校验方式与之前校验 Java 对象不同。因为路径变量和请求参数通常是原始类型(primitive types),而不是 Java Bean。
因此,我们不能像以前那样在类的字段上加注解,而是需要直接在 Controller 方法的参数上添加约束注解。
例如:在路径变量 postcode 上使用 @Size 注解限制字符串长度。

此外,我们需要在 Controller 类上添加 @Validated 注解,这样 Spring 才能在方法参数上识别并执行这些校验注解。

时候我们不是接收一个对象,而是通过 URL 路径变量请求参数来接值,比如:

GET /users/123?postcode=510000

这里的 123510000 是基本类型(如 Stringint),不是 Java Bean

如果你想对这些值做校验,比如要求 postcode 长度必须是 6 位,就不能在类字段上加注解了,只能直接在方法参数上加:

⚠️ 关键点:

  1. 方法参数也能加校验注解(如 @Size@NotBlank 等),但要在类上加 @Validated 才会生效。

  2. 如果不加 @Validated,这些注解不会触发校验,也不会自动抛出异常。

  3. 方法参数校验常用于:

    • 路径变量 @PathVariable

    • 查询参数 @RequestParam

    • 甚至方法里的普通参数

总结:

  • 对象校验@Valid + Bean 字段注解(常用于 @RequestBody)。

  • 基本类型参数校验:注解加在方法参数上 + 在上加 @Validated

  • 没有 @Validated → 校验不会触发

如果我们传入的 路径变量(Path Variable)长度比允许的短,校验会失败,并抛出 ConstraintViolationException,而不是像校验请求体(request body)时那样抛出 MethodArgumentNotValidException

Spring 对 ConstraintViolationException 没有默认的异常处理器,所以默认会返回 500 Internal Server Error

如果我们想要像以前一样返回 400 Bad Request,保持返回格式的统一,就需要自己编写自定义异常处理器

对于 请求参数(Request Parameters) 的校验,工作原理和路径变量是一样的。

示例3:验证分组(Validation Groups)

当我们希望在不同的场景不同的目的下触发校验,但又想复用同一个被校验的对象时,可以使用 校验分组(Validation Groups)

如果查看 @NotEmpty 注解的源码,你会发现它(和其它任何校验注解一样)都有一个名为 groups 的属性

有时候,我们有一个同样的 Java Bean,但在不同的场景下,对字段的校验规则不一样。例如:

假设我们有一个用户类
public class User {@NotEmptyprivate String username;@NotEmptyprivate String password;@NotEmptyprivate String email;
}
  • 场景1:注册
    需要校验 usernamepasswordemail 都不能为空。

  • 场景2:登录
    只需要校验 usernamepasswordemail 不需要。

如果不用分组,通常我们要写两个不同的类,很麻烦。
有了 Validation Groups,就可以在一个类里定义不同场景的分组。


🏷️ 定义分组接口
public interface RegisterGroup {}
public interface LoginGroup {}
🏷️ 在 Bean 字段上指定分组
public class User {@NotEmpty(groups = {RegisterGroup.class, LoginGroup.class})private String username;@NotEmpty(groups = {RegisterGroup.class, LoginGroup.class})private String password;@NotEmpty(groups = RegisterGroup.class) // 注册时必填,但登录不需要private String email;
}

🏷️ 在 Controller 中指定分组
@RestController
public class UserController {// 注册时使用 RegisterGroup@PostMapping("/register")public void register(@Validated(RegisterGroup.class) @RequestBody User user) {// 注册逻辑}// 登录时使用 LoginGroup@PostMapping("/login")public void login(@Validated(LoginGroup.class) @RequestBody User user) {// 登录逻辑}
}

这样:

  • /register 接口时,三个字段都要校验;

  • /login 接口时,只校验 usernamepassword,不会校验 email


🔹关键点总结

  1. groups 是每个校验注解自带的属性,默认是 Default.class

  2. 定义分组接口(只是个空接口,用来做标记)。

  3. 在字段上用 groups = {...} 指定属于哪些分组。

  4. 在 Controller 方法上用 @Validated(SomeGroup.class) 指定要触发哪一组校验。

  5. 如果没有指定分组,默认会用 Default.class 组。


简单理解

Validation Groups 就是让你用同一个对象,按不同“场景”触发不同的校验规则,避免为了不同接口写很多重复的 Java Bean。


小结:

下面是这段内容的 翻译 + 通俗易懂讲解


🔹英文原文

Key takeaways

  • @Valid comes from Java Validation API

  • @Validated comes from Spring Framework Validation, it is a variant of @Valid with support for validation groups

  • @Valid use on method parameters and fields, don’t forget to use it on complex objects if they need to be validated

  • @Validated use on methods and method parameters when using validation groups, use on classes to support method parameter constraint validations


🔹中文翻译

重点总结:

  • @Valid 来自 Java 标准的 Bean Validation API(JSR-303/JSR-380)。

  • @Validated 来自 Spring Framework,它是 @Valid 的扩展版本,支持 校验分组(Validation Groups)

  • @Valid 常用于方法参数和字段上,如果你的方法参数是复杂对象(比如 @RequestBody User user),别忘了加上 @Valid 来触发校验。

  • @Validated 常用于方法、方法参数,特别是需要分组校验时使用;也可以加在类上,用于支持方法参数(如 PathVariable、RequestParam)的约束校验。


🔹通俗易懂解释

  1. @Valid = Java 标准版

    • 属于 JSR-303 规范,不依赖 Spring,也能在其他框架用。

    • 主要用来校验对象里的字段,比如接收前端传过来的 JSON。

    • 常见写法:

      public void addUser(@Valid @RequestBody User user) { ... }
      
  2. @Validated = Spring 增强版

    • Spring 在 @Valid 的基础上增加了 分组校验方法级参数校验 的支持。

    • 如果你用到了 Validation Groups,就必须用 @Validated

    • 如果要校验 PathVariableRequestParam,要在类上加 @Validated 才生效。

    • 常见写法:

      @RestController
      @Validated // 支持方法参数校验
      public class UserController {@GetMapping("/users/{id}")public void getUser(@PathVariable @Min(1) Long id) { ... }@PostMapping("/register")public void register(@Validated(RegisterGroup.class) @RequestBody User user) { ... }
      }
      
  3. 简单对比
    | 功能 | @Valid | @Validated |
    |------|--------|-----------|
    | 来源 | JSR-303 标准 | Spring 框架 |
    | 校验分组 | ❌ 不支持 | ✅ 支持 |
    | 方法参数(PathVariable、RequestParam)校验 | ❌ 不能单独触发 | ✅ 可用 |
    | 常用场景 | @RequestBody Java Bean | 分组校验、路径参数校验 |


一句话记忆

  • 只要是复杂对象(RequestBody)用 @Valid 就够了。

  • 如果要分组校验校验方法参数(PathVariable、RequestParam),用 @Validated


四、其他常用的校验方式

4-1、返回值的校验

Bean Validation 不只是能校验输入参数,也能用来校验 方法的返回值(Return Value Validation)。


🔹1. 原理与支持

  • Bean Validation 2.0(JSR-380) 开始支持方法级别约束(Method-level constraints),包括:

    • 参数(Parameters)校验

    • 返回值(Return Value)校验

  • Spring Boot 已经集成 Hibernate Validator,因此可以直接使用。


🔹2. 如何写返回值校验

🏷️ 示例 1:用在方法返回值上
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import org.springframework.validation.annotation.Validated;
import org.springframework.stereotype.Service;@Service
@Validated  // ⚠️ 必须加在类上,方法级约束才会生效
public class UserService {@NotNull@Size(min = 2, message = "返回的用户名至少要2个字符")public String getUserName() {return ""; // 这里返回空字符串会触发校验异常}
}

⚠️ 关键点

  • @Validated 要加在类上,否则返回值约束不会生效。

  • 方法上的约束注解(如 @NotNull@Size)直接标记在返回值类型上。


🏷️ 示例 2:Controller 返回值校验
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotEmpty;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;@RestController
@Validated
public class UserController {@GetMapping("/user")@NotEmpty(message = "返回值不能为空")public String getUser() {return "";  // 会抛出 ConstraintViolationException}
}

当返回值不符合约束条件时,Spring 会抛出 ConstraintViolationException

⚡ 总结

  • 可以用 Bean Validation 注解约束返回值,包括 Controller 和 Service。

  • ⚠️ 必须在类上加 @Validated 才会触发。

  • ❌ Spring 默认返回 500,需要自定义异常处理器把 ConstraintViolationException 转为 400 或自定义响应格式。


4-2、自定义约束(如强密码、跨字段)

在 Spring(Hibernate Validator)里你可以自定义校验注解,用于字段、方法参数,甚至整个对象的“跨字段校验”。

做法很固定:定义注解 → 写校验器 →(可选)配置消息文件(国家化) → 使用


一、最常见:字段级自定义注解

下面以“密码强度校验”为例:@StrongPassword

1) 定义注解
package com.example.validation;import jakarta.validation.Constraint;
import jakarta.validation.Payload;
import java.lang.annotation.*;
import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.RetentionPolicy.RUNTIME;@Documented
@Target({ FIELD, PARAMETER })      // 用在字段或方法参数上
@Retention(RUNTIME)
@Constraint(validatedBy = StrongPasswordValidator.class) // 指定校验器
public @interface StrongPassword {String message() default "{password.weak}"; // 支持国际化占位符Class<?>[] groups() default {};Class<? extends Payload>[] payload() default {};// 也可以暴露参数,供校验器读取int min() default 8;boolean requireUppercase() default true;boolean requireNumber() default true;boolean requireSpecial() default true;
}
2) 编写校验器
package com.example.validation;import jakarta.validation.ConstraintValidator;
import jakarta.validation.ConstraintValidatorContext;public class StrongPasswordValidator implements ConstraintValidator<StrongPassword, String> {private int min;private boolean requireUppercase;private boolean requireNumber;private boolean requireSpecial;@Overridepublic void initialize(StrongPassword anno) {// 读取注解上的参数this.min = anno.min();this.requireUppercase = anno.requireUppercase();this.requireNumber = anno.requireNumber();this.requireSpecial = anno.requireSpecial();}@Overridepublic boolean isValid(String value, ConstraintValidatorContext ctx) {if (value == null) return true; // 为空交给 @NotNull 管if (value.length() < min) return false;if (requireUppercase && !value.matches(".*[A-Z].*")) return false;if (requireNumber && !value.matches(".*\\d.*")) return false;if (requireSpecial && !value.matches(".*[^\\w\\s].*")) return false;return true;}
}

在 Spring Boot 中,这个校验器可以直接使用(Boot 默认启用 Hibernate Validator 并能注入 Spring Bean;若你需要在校验器里用服务/DAO,可把校验器 @Component 并直接 @Autowired)。

📌 官方建议

自定义约束的校验器一般只处理非空的值
空值让 @NotNull@NotEmpty@NotBlank 等专门的注解去判断

3) 国际化消息(可选)

src/main/resources/ValidationMessages.properties

password.weak=密码不够安全:至少{min}位,且包含大写字母、数字和特殊字符
4) 使用
public class RegisterDTO {@NotNull(message = "密码不能为空")@StrongPassword(min = 10, message = "密码至少10位并包含多种字符")private String password;// getters/setters...
}

组合使用:空值交给@NotNull


二、跨字段校验(类级别)

比如“确认密码必须等于密码”:@FieldsMatch(first="password", second="confirmPassword")

1) 注解
@Target({ TYPE })
@Retention(RUNTIME)
@Documented
@Constraint(validatedBy = FieldsMatchValidator.class)
public @interface FieldsMatch {String message() default "{fields.not.match}";Class<?>[] groups() default {};Class<? extends Payload>[] payload() default {};String first();String second();
}
2) 校验器
import jakarta.validation.ConstraintValidator;
import jakarta.validation.ConstraintValidatorContext;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;public class FieldsMatchValidator implements ConstraintValidator<FieldsMatch, Object> {private String first;private String second;@Overridepublic void initialize(FieldsMatch anno) {this.first = anno.first();this.second = anno.second();}@Overridepublic boolean isValid(Object value, ConstraintValidatorContext ctx) {try {Object v1 = readProperty(value, first);Object v2 = readProperty(value, second);return v1 == null ? v2 == null : v1.equals(v2);} catch (Exception e) {// 读取失败按不通过处理,或返回 true 并记录日志视需求return false;}}private Object readProperty(Object bean, String name) throws Exception {for (PropertyDescriptor pd : Introspector.getBeanInfo(bean.getClass()).getPropertyDescriptors()) {if (pd.getName().equals(name)) {return pd.getReadMethod().invoke(bean);}}return null;}
}
3) 使用
@FieldsMatch(first = "password", second = "confirmPassword", message = "两次输入的密码不一致")
public class RegisterDTO {@jakarta.validation.constraints.NotBlankprivate String password;@jakarta.validation.constraints.NotBlankprivate String confirmPassword;
}

三、方法返回值/参数也可用

类上@Validated 后,方法参数/返回值上的自定义注解同样生效:

@Service
@org.springframework.validation.annotation.Validated
public class UserService {public void updatePassword(@StrongPassword String newPwd) { ... }@StrongPasswordpublic String generateTempPassword() { return "abc"; } // 将会校验返回值
}

四、和校验分组一起用(可选)

自定义注解天然支持 groups

public interface CreateGroup {}
public interface UpdateGroup {}public class UserDTO {@StrongPassword(groups = CreateGroup.class)private String password;
}

Controller:

@PostMapping("/users")
public void create(@Validated(CreateGroup.class) @RequestBody UserDTO dto) { ... }

五、组合注解(合成/复合约束)

可以把多个现成约束“打包”为一个注解:

@Documented
@Target({ FIELD, PARAMETER })
@Retention(RUNTIME)
@Constraint(validatedBy = {})          // 没有自定义校验器
@jakarta.validation.ReportAsSingleViolation
@jakarta.validation.constraints.NotBlank
@jakarta.validation.constraints.Email
public @interface NonEmptyEmail {String message() default "邮箱格式不正确";Class<?>[] groups() default {};Class<? extends Payload>[] payload() default {};
}

五、国际化与消息定制


1. 什么是国际化(i18n)与消息定制

  • 国际化 (Internationalization, i18n)
    让系统根据不同语言或地区,显示对应的提示信息
    Spring + Bean Validation 中,校验失败后显示的默认信息(比如 must not be blank)通常是英文。
    如果你希望对用户显示中文、马来语或其他语言,就要做国际化消息定制

  • 消息定制
    你可以为不同的字段和校验规则指定自定义的提示文字,而不是使用 Hibernate Validator 内置的默认消息。


2. 消息配置文件

通常在 src/main/resources 下创建文件:

ValidationMessages.properties          // 默认语言
ValidationMessages_zh_CN.properties    // 中文
ValidationMessages_en_US.properties    // 英文

如果只需要中文,也可以只保留 ValidationMessages.properties


例子:

# ---- ValidationMessages.properties ----
userCreateDTO.username.NotBlank=用户名必填
Size.userCreateDTO.username=用户名长度应在 {min}-{max} 之间

含义:

  1. userCreateDTO.username.NotBlank

    • 对应 UserCreateDTO 类中字段 username 上的 @NotBlank 注解。

    • 当这个字段校验失败(为空或空白字符)时,显示 "用户名必填"

  2. Size.userCreateDTO.username

    • 对应同一字段上 @Size(min=, max=) 注解。

    • 当长度不符合 minmax 时,显示 "用户名长度应在 {min}-{max} 之间"

    • {min}{max} 是注解里的参数占位符,会被实际数值替换。

3. 规则

{注解名}.{类名(首字母小写)}.{字段名}

比如:NotBlank.userCreateDTO.username
或者:Size.userCreateDTO.username


对应的 Java Bean

public class UserCreateDTO {@NotBlank@Size(min = 3, max = 20)private String username;
}

对应的 Controller

@RestController
public class UserController {@PostMapping("/user")public void createUser(@Valid @RequestBody UserCreateDTO dto) {// 如果 username 为空或长度不符,会自动返回上面定义的中文提示}
}

4. 默认与覆盖

  • 如果只写 @NotBlank(message="用户名不能为空"),直接在注解上指定信息,会覆盖国际化配置

  • 如果注解上不写 message,就会去 ValidationMessages.properties

    1. 精确匹配:NotBlank.userCreateDTO.username

    2. 如果没有精确匹配,退回到通用的 NotBlank.message(框架默认的英文)


5. 国际化切换

如果项目中启用了 Spring MVC 的国际化支持(LocaleResolver),

  • 请求头里带 Accept-Language: zh-CN → 使用 ValidationMessages_zh_CN.properties

  • 当带 Accept-Language: en-US → 使用 ValidationMessages_en_US.properties


总结

功能说明
自定义消息在注解上直接 message = "xxx" 或在 ValidationMessages.properties 定义
国际化用不同语言的 ValidationMessages_xx.properties 文件
占位符{min}{max}{value} 等会被注解参数自动替换
查找顺序注解名.类名.字段名注解名.message(通用默认)

六、快速对照表

  • @NotNull:值必须有;不检查空串/空集合。

  • @NotEmpty:非 null 且 size>0(字符串/集合/数组)。

  • @NotBlank:非 null 且去空白后长度>0(仅字符串)。

  • @Size(min,max):长度/大小范围(字符串/集合/数组)。

  • 数值@Min/@Max/@Positive/@Negative/@Digits/@DecimalMin/@DecimalMax

  • 时间@Past/@Future/...(基于系统时钟)。

  • 格式@Email/@Pattern 内部编写正则校验

  • 逻辑@AssertTrue/@AssertFalse(多用于类内衍生校验)。

七、全局异常处理器

1. 为什么需要全局异常处理

在 Web 项目中,请求处理可能会抛出各种异常,例如:

  • 参数校验不通过 (MethodArgumentNotValidException)

  • 数据库访问错误 (DataAccessException)

  • 自定义业务异常(如 BusinessException

  • 运行时错误(NullPointerExceptionIllegalArgumentException

如果不统一处理,这些异常会直接冒泡到前端,可能返回堆栈信息(安全风险)或不友好的 500 错误页面。
全局异常处理器可以统一拦截异常,返回结构化的响应(JSON),提高用户体验和代码维护性。


2. Spring Boot 提供的核心机制

Spring Boot 使用 Spring MVC 的异常处理机制

  1. @ControllerAdvice

    • 表示一个全局的控制器增强类,可以拦截项目中所有被 @Controller / @RestController 注解的类。

    • 可处理异常、数据绑定、全局数据预处理等。

  2. @ExceptionHandler

    • 标注在方法上,声明该方法处理指定类型的异常。

    • 可以放在单个 Controller 中(只处理该类异常),也可以放在 @ControllerAdvice 类中(全局处理)。

  3. 返回值处理

    • 如果是 @RestControllerAdvice等同于 @ControllerAdvice + @ResponseBody),默认返回 JSON

    • 如果是 @ControllerAdvice,则返回视图。

3、代码实现

当然也可以写的详细一点,不同的异常,不同的处理:

@RestControllerAdvice
public class GlobalExceptionHandler {@ExceptionHandler(MethodArgumentNotValidException.class)public ApiResponse<Void> handleMethodArgNotValid(MethodArgumentNotValidException ex) {return ApiResponse.error(400, ex.getBindingResult().getFieldError().getDefaultMessage());}@ExceptionHandler(BindException.class)public ApiResponse<Void> handleBindException(BindException ex) {return ApiResponse.error(400, ex.getBindingResult().getFieldError().getDefaultMessage());}@ExceptionHandler(ConstraintViolationException.class)public ApiResponse<Void> handleConstraintViolation(ConstraintViolationException ex) {return ApiResponse.error(400, ex.getMessage());}@ExceptionHandler(Exception.class)public ApiResponse<Void> handleOther(Exception ex) {return ApiResponse.error(500, "系统异常");}
}

返回格式:

八、自定义异常(回顾)

8-1、常见的异常处理方法

如果我们不自己定义业务异常类,Spring Boot 里常用的异常处理方式主要有以下几类。它们基本可以满足“异常→统一返回”的需求,只是可扩展性和可读性不如自定义异常。

1、直接使用 JDK 或 Spring 自带异常+ 全局异常处理器

最简单的做法是 抛出通用异常(如 IllegalArgumentExceptionIllegalStateExceptionRuntimeException):

然后在全局异常处理器里捕获:

优点

  • 简单,直接用现成异常类;

  • 没有额外类的维护成本。

缺点

  • 错误码不统一;

  • 异常类型难以表达具体业务含义(IllegalArgumentException 既可以是“用户名存在”,也可能是“年龄无效”);

  • 项目大了难以管理。

2、使用 Spring MVC 自带异常

Spring 本身已经内置了一些异常类型,可在全局异常处理器里直接处理,例如:

异常类场景
MethodArgumentNotValidException@RequestBody 参数校验失败
BindException表单/Query 参数绑定失败
MissingServletRequestParameterException缺少请求参数
HttpRequestMethodNotSupportedException请求方法不支持(GET 调 POST 接口)
HttpMediaTypeNotSupportedExceptionContent-Type 不支持
HttpMessageNotReadableException请求体 JSON 解析错误
AccessDeniedExceptionSpring Security 鉴权失败

3、Controller 层直接 try...catch

优点

  • 局部处理,灵活度高;

  • 小项目或临时需求时可以快速实现。

缺点

  • 每个接口都要写重复代码;

  • 无法全局统一管理,维护成本高

8-2、自定义异常

1. 为什么要自定义异常

在实际开发中,系统异常类型很多,直接抛出 RuntimeException / Exception 太混乱:

  • 无法区分业务错误(如“用户名已存在”)和系统错误(如空指针、数据库连接失败)。

  • 无法返回明确的错误码和友好的提示信息。

  • 无法统一日志记录与前端响应格式。

通过 自定义异常,我们可以:

  • 封装 业务状态码错误信息、可选的附加数据。

  • 结合全局异常处理器,输出统一的 JSON 格式响应。

  • 提高可维护性和可读性。

  • 其中最常见的确是 自定义 code 和 message

2、编写规则

/*** 业务异常:用于表示可预期的业务逻辑错误*/
public class BusinessException extends RuntimeException {private int code; // 错误码public BusinessException(int code, String message) {super(message);this.code = code;}public int getCode() {return code;}
}
  • 继承 RuntimeException
    (Spring 事务回滚默认只对 RuntimeException 生效)。

  • 增加 错误码字段,便于前端识别。

示例:

全局异常处理器中处理自定义异常:

使用:

测试:

http://www.dtcms.com/a/432197.html

相关文章:

  • 上的网站app免费大全个人站长适合做什么网站
  • 我们提供的网站建设易语言如何做代刷网站
  • 中断控制器介绍-软件篇(linux)
  • 怎么学做淘宝免费视频网站wordpress 主机安装
  • 社区类网站建设的例子四川建设厅官方网站四库一平台
  • 【PID学习】PID算法改进
  • 引流推广app网站改版对seo影响
  • 南京代做网站h5编辑器有哪些软件
  • 在Zotero中配置PDF2zh插件并使用详细教程
  • 做图有什么网站河池市民政局门户网站建设
  • 10大免费软件下载网站wordpress模板克隆
  • MASM数据段完全指南:从基础定义到高级内存操作
  • 提供视频下载的网站做学术用的网站
  • 官方网站建设实训心得网站seo优化主要有哪些手段
  • 17.仅使用 CSS 实现的导航标签页,带滑动菜单指示器
  • 【LeetCode 每日一题】1470. 重新排列数组——(解法一)构造数组
  • 商城网站合同设计公司网站需要考虑什么
  • 银川建设网站简约的网页设计
  • 【人工智能通识专栏】第三十二讲:本地化部署模型
  • 网站怎么加关键词深圳网站建设公司小江
  • 网站开发人员介绍做网站首页看不到图片
  • ORB_SLAM2原理及代码解析:MapPoint::AddObservation() 函数
  • 炉石做任务抽奖网站装宽带一年大概需要多少钱
  • 个人网站建立 学生哈尔滨学校网站建设
  • 网站用什么软件做败sp个人网站app
  • 适合建设网站的国外服务器晋城市网站建设
  • 西安做商铺的网站网站设计公司费用
  • 专业做胶粘剂招聘网站华为云网站定制
  • 企业建站系统费用全站搜索
  • 做装饰网站公司wordpress plugins