在 controller
中我们首先对所有的请求进行日志记录,身份校验,参数校验之后直接把过滤后的数据丢给 logic
(serviceImpl) 。这样子一来 controller
只是充当了 路由 + 过滤器 的作用,如果之后修改 API ,前端的请求地址不需要修改,只要在 logic
(serviceImpl) 中修改或者重新一个方法就行了。
springboot 前端参数校验 1 2 3
在 Spring Boot 项目中,参数校验是一个非常重要的环节,主要使用 JSR-303/JSR-380 规范的实现(如 Hibernate Validator)来完成。在Spring Boot中,前端参数校验通常通过 JSR 380(Bean Validation 2.0) 实现,结合 Hibernate Validator(默认实现)和 Spring 的自动配置,可以实现对前端传递参数的校验。以下是如何在 Spring Boot 中实现前端参数校验的详细步骤:
1. 添加依赖
在 pom.xml
文件中添加 spring-boot-starter-validation
依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
在 Gradle 构建工具中添加 spring-boot-starter-validation
依赖:
implementation 'org.springframework.boot:spring-boot-starter-validation'
2. 创建 DTO 类并添加校验注解
使用 JSR 380 提供的注解对字段进行校验,例如 @NotNull
, @Size
, @Email
, @Pattern
等。
示例:用户数据校验
import javax.validation.constraints.*;
public class UserDTO {
@NotNull(message = "用户名不能为空")
@Size(min = 3, max = 20, message = "用户名长度必须在3到20个字符之间")
private String username;
@NotNull(message = "邮箱不能为空")
@Email(message = "邮箱格式不正确")
private String email;
@NotNull(message = "年龄不能为空")
@Min(value = 18, message = "年龄必须大于或等于18岁")
@Max(value = 100, message = "年龄必须小于或等于100岁")
private Integer age;
@Pattern(regexp = "^[0-9]+$", message = "电话号码只能包含数字")
private String phone;
// Getters and Setters
}
3. 在 Controller 中使用 @Valid
注解
在控制器中,使用 @Valid
注解对请求体进行校验。如果校验失败,Spring 会自动抛出 MethodArgumentNotValidException
。
示例:控制器方法
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
@RestController
@RequestMapping("/api/users")
public class UserController {
@PostMapping
public ResponseEntity<String> createUser(@Valid @RequestBody UserDTO userDTO) {
// 如果校验通过,执行逻辑
return ResponseEntity.ok("用户创建成功: " + userDTO.getUsername());
}
}
4. 全局异常处理
为了返回更友好的错误信息,可以通过 @ControllerAdvice
/ @RestControllerAdvice
和 @ExceptionHandler
捕获校验异常并自定义返回格式。
示例:全局异常处理器
使用@ControllerAdvice和@ExceptionHandler来处理MethodArgumentNotValidException以返回自定义错误消息:
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.*;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import java.util.HashMap;
import java.util.Map;
@ControllerAdvice
public class GlobalExceptionHandler {
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(MethodArgumentNotValidException.class)
public Map<String, String> handleValidationExceptions(MethodArgumentNotValidException ex) {
Map<String, String> errors = new HashMap<>();
ex.getBindingResult().getAllErrors().forEach((error) -> {
String fieldName = ((FieldError) error).getField();
String errorMessage = error.getDefaultMessage();
errors.put(fieldName, errorMessage);
});
return errors;
}
}
使用@RestControllerAdvice和@ExceptionHandler来处理MethodArgumentNotValidException和ConstraintViolationException以返回自定义错误消息:
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.validation.BindException;
import org.springframework.validation.FieldError;
import javax.validation.ConstraintViolationException;
import java.util.HashMap;
import java.util.Map;
@RestControllerAdvice // 等同于 @ControllerAdvice + @ResponseBody
public class GlobalExceptionHandler {
// 处理 @RequestBody 的 Bean (dto) 校验失败; @Valid
// 常用于 JSON (dto) 的输入校验
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<Map<String, String>> handleValidationExceptions(
MethodArgumentNotValidException ex) {
Map<String, String> errors = new HashMap<>();
ex.getBindingResult().getAllErrors().forEach((error) -> {
String fieldName = ((FieldError) error).getField();
String errorMessage = error.getDefaultMessage();
errors.put(fieldName, errorMessage);
});
return ResponseEntity.badRequest().body(errors);
}
// controller 方法参数或方法返回值验证失败时抛出; @Validated, 参数校验
// @PathVariable, @RequestParam 或返回值校验
@ExceptionHandler(ConstraintViolationException.class)
public ResponseEntity<Map<String, String>> handleConstraintViolation(
ConstraintViolationException ex) {
Map<String, String> errors = new HashMap<>();
ex.getConstraintViolations().forEach(violation -> {
String fieldName = violation.getPropertyPath().toString();
String errorMessage = violation.getMessage();
errors.put(fieldName, errorMessage);
});
return ResponseEntity.badRequest().body(errors);
}
}
5. 配置校验规则(可选)
如果需要自定义校验规则,可以实现 ConstraintValidator
接口。
示例:自定义校验注解
创建注解
import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.*;
@Documented
@Constraint(validatedBy = PhoneValidator.class)
@Target({ElementType.METHOD, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface ValidPhone {
String message() default "电话号码格式不正确";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
创建校验逻辑
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
public class PhoneValidator implements ConstraintValidator<ValidPhone, String> {
@Override
public boolean isValid(String value, ConstraintValidatorContext context) {
// 校验逻辑:电话号码只能包含数字,且长度为10-15
return value != null && value.matches("^[0-9]{10,15}$");
}
}
使用自定义注解
public class UserDTO {
@ValidPhone
private String phone;
// 其他字段
}
6. 示例请求与响应
请求示例
// 使用 axios 发送请求
async function createUser(userData) {
try {
const response = await axios.post('/api/users', userData);
console.log('创建成功:', response.data);
} catch (error) {
if (error.response && error.response.data) {
// 处理验证错误
const validationErrors = error.response.data;
Object.keys(validationErrors).forEach(field => {
console.error(`${field}: ${validationErrors[field]}`);
});
}
}
}
正确请求
{
"username": "JohnDoe",
"email": "john.doe@example.com",
"age": 25,
"phone": "1234567890"
}
错误请求
{
"username": "JD",
"email": "invalid-email",
"age": 15,
"phone": "abc123"
}
错误响应示例
{
"username": "用户名长度必须在3到20个字符之间",
"email": "邮箱格式不正确",
"age": "年龄必须大于或等于18岁",
"phone": "电话号码格式不正确"
}
常用校验注解
注解 | 功能说明 |
---|---|
@NotNull | 验证字段不能为空 |
@NotEmpty | 验证字段不能为空且长度大于0(字符串), 不能为空(字符串长度不为0或集合大小不为0) |
@NotBlank | 验证字段不能为空且必须包含非空白字符, 不能为空(去除首尾空格后长度不为0) |
@Size | 验证字符串、集合等的长度或大小范围 |
@Min / @Max | 验证数字的最小值和最大值 |
@Email | 验证邮箱格式 |
@Pattern | 使用正则表达式验证字段 |
@Digits | 验证数字的整数位数和小数位数 |
@Past / @Future | 验证日期必须是过去或未来 |
@Positive | 正数 |
@Negative | 负数 |
总结
通过 Spring Boot 的 spring-boot-starter-validation
和 Hibernate Validator,可以轻松实现前端参数校验。结合注解、全局异常处理器和自定义校验规则,可以满足大多数场景的需求,确保数据的完整性和正确性。
以上内容提供了一个完整的 Spring Boot 参数校验方案。主要包括:
- 依赖配置:添加必要的验证依赖
- 实体类验证:使用注解定义验证规则
- 控制器验证:使用
@Valid
或@Validated
开启验证 - 异常处理:统一处理验证异常
- 常用注解说明:列举了常用的验证注解
- 前端调用示例:展示了如何处理验证结果
一些重要说明:
@Valid
和@Validated
的区别:@Valid
是 JSR-303 规范的标准注解@Validated
是 Spring 提供的注解,支持分组校验
- 嵌套验证:
- 对象中包含其他对象时,需要在字段上添加
@Valid
注解
- 对象中包含其他对象时,需要在字段上添加
- 分组验证:
- 可以通过定义接口来实现不同场景下的验证规则
- 自定义验证:
- 可以通过
@Constraint
注解创建自定义验证规则
- 可以通过
@Valid @Validated
在Spring Boot中,@Valid
和 @Validated
是两种用于触发参数校验的注解,它们都可以用来验证前端传递的参数,但它们有一些细微的区别和适用场景。以下是对两者的详细对比和使用说明:
1. @Valid
@Valid
是 JSR 380(Bean Validation 2.0) 规范的一部分,由 Java 提供,通常用于触发 Hibernate Validator 的校验逻辑。
特点
- 来源:
javax.validation.Valid
。 - 作用范围:
- 用于校验 Java Bean(如 DTO 对象)。
- 可以嵌套校验(即校验对象中的嵌套对象)。
- 不支持分组验证
- 可以用在方法参数和字段
- 分组校验:不支持分组校验(需要用
@Validated
)。 - 常用场景:用于校验简单的请求参数或嵌套对象。
示例
校验简单对象
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
public class UserDTO {
@NotNull(message = "用户名不能为空")
@Size(min = 3, max = 20, message = "用户名长度必须在3到20个字符之间")
private String username;
@NotNull(message = "邮箱不能为空")
private String email;
// Getters and Setters
}
在控制器中使用 @Valid
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
@RestController
@RequestMapping("/users")
public class UserController {
@PostMapping
public String createUser(@Valid @RequestBody UserDTO userDTO) {
return "用户创建成功: " + userDTO.getUsername();
}
}
2. @Validated
@Validated
是 Spring 提供的注解,功能更加强大,除了支持基本的校验功能外,还支持 分组校验。
特点
- 来源:
org.springframework.validation.annotation.Validated
。 - 作用范围:
- 用于校验 Java Bean(如 DTO 对象)。
- 支持分组校验。
- 可以用在类型、方法和方法参数
- 不支持嵌套验证(除非结合@Valid)
- 分组校验:支持分组校验(通过
groups
属性指定校验分组)。 - 常用场景:用于需要分组校验的复杂场景。
示例
分组校验
定义校验分组
public interface CreateGroup {}
public interface UpdateGroup {}
在 DTO 中指定分组
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
public class UserDTO {
@NotNull(groups = CreateGroup.class, message = "用户名不能为空")
@Size(min = 3, max = 20, groups = {CreateGroup.class, UpdateGroup.class}, message = "用户名长度必须在3到20个字符之间")
private String username;
@NotNull(groups = UpdateGroup.class, message = "邮箱不能为空")
private String email;
// Getters and Setters
}
在控制器中使用 @Validated
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/users")
public class UserController {
// 创建用户时校验 CreateGroup 分组
@PostMapping
public String createUser(@Validated(CreateGroup.class) @RequestBody UserDTO userDTO) {
return "用户创建成功: " + userDTO.getUsername();
}
// 更新用户时校验 UpdateGroup 分组
@PutMapping
public String updateUser(@Validated(UpdateGroup.class) @RequestBody UserDTO userDTO) {
return "用户更新成功: " + userDTO.getUsername();
}
}
3. @Valid 和 @Validated 的区别
特性 | @Valid | @Validated |
---|---|---|
来源 | javax.validation.Valid | org.springframework.validation.annotation.Validated |
分组校验 | 不支持 | 支持 |
嵌套校验 | 支持 | 支持 |
适用场景 | 简单校验 | 复杂校验(需要分组时) |
校验触发位置 | 由 Bean Validation 规范触发 | 由 Spring 的校验机制触发 |
4. 嵌套校验
无论是 @Valid
还是 @Validated
,都支持嵌套校验。只需在嵌套对象的字段上添加 @Valid
注解即可。
示例
import javax.validation.Valid;
import javax.validation.constraints.NotNull;
public class UserDTO {
@NotNull(message = "用户名不能为空")
private String username;
@NotNull(message = "地址不能为空")
private String address;
// getters and setters
}
public class OrderDTO {
@NotNull(message = "订单ID不能为空")
private String orderId;
@Valid // 必须加@Valid才能验证嵌套对象 UserDTO
@NotNull(message = "用户信息不能为空")
private UserDTO user;
// Getters and Setters
}
在控制器中:
@PostMapping("/orders")
public String createOrder(@Valid @RequestBody OrderDTO orderDTO) {
return "订单创建成功";
}
方法级别验证
@Service
@Validated // 类级别需要添加@Validated
public class UserService {
public void createUser(@NotNull(message = "用户名不能为空") String username,
@Min(value = 0, message = "年龄不能为负数") int age) {
// 处理逻辑
}
@Validated(Create.class) // 方法级别的分组验证
public void saveUser(@Valid UserDTO userDTO) {
// 处理逻辑
}
}
5. 总结
-
@Valid
:- 用于简单校验。
- 支持嵌套校验。
- 不支持分组校验。
- 更加通用,适用于大多数场景。
-
@Validated
:- 支持分组校验。
- 适用于复杂校验场景(如不同操作需要不同的校验规则)。
- 是 Spring 提供的扩展功能。
在实际开发中,如果不需要分组校验,@Valid
足够满足需求;如果需要分组校验,则必须使用 @Validated
。
6. 建议
- Controller层验证:
- 使用
@Valid
进行基础的请求体验证 - 如果需要分组验证,使用
@Validated
- 使用
- Service层验证:
- 使用
@Validated
在类级别开启验证 - 使用参数级别的约束注解进行验证
- 使用
- 嵌套验证:
- 需要验证嵌套对象时,确保使用
@Valid
- 需要验证嵌套对象时,确保使用
- 分组验证:
- 当同一个DTO在不同场景下有不同的验证规则时,使用
@Validated
配合验证分组
- 当同一个DTO在不同场景下有不同的验证规则时,使用
- 异常处理:
- 确保同时处理
MethodArgumentNotValidException
和ConstraintViolationException
- 确保同时处理
通过合理使用这两个注解,可以实现更灵活和强大的参数验证功能。