Spring Boot 接口参数校验从入门到精通
Spring Boot æ¥å£åæ°æ ¡éªä»å ¥é¨å°ç²¾é
ææ 2026-08-19 2 é 读7åé忥壿¶ï¼ä½ æ¯ä¸æ¯è¿å¨ç¨ä¸å if 夿忰æ¯å¦ä¸ºç©ºãæ ¼å¼å¯¹ä¸å¯¹ï¼è¿ç¯æç« å¸¦ä½ ç¨æ´ä¼é
çæ¹å¼æå®ä¸åã
示ä¾
å°å¼ åå ¥èæ¶ï¼è´è´£å¼åä¸ä¸ªç¨æ·æ³¨åæ¥å£ãä»é常认çï¼ç¼åäºå¦ä¸ä»£ç ï¼
@PostMapping("/register")
public String register(User user) {
// æå¨æ ¡éªæ¯ä¸ªå段
if (user.getUsername() == null || user.getUsername().isEmpty()) {
return "ç¨æ·åä¸è½ä¸ºç©º";
}
if (user.getPassword() == null || user.getPassword().length() < 6) {
return "å¯ç é¿åº¦ä¸è½å°äº6ä½";
}
if (user.getEmail() == null || !user.getEmail().contains("@")) {
return "é®ç®±æ ¼å¼ä¸æ£ç¡®";
}
if (user.getAge() == null || user.getAge() < 0 || user.getAge() > 150) {
return "å¹´é¾ä¸åæ³";
}
// ... ç»§ç»ä¸å¡é»è¾
}
è¿æ ·åæä»ä¹é®é¢ï¼
| é®é¢ | 说æ |
|---|---|
| 代ç èè¿ | æ ¡éªä»£ç æ¯ä¸å¡é»è¾è¿å¤ |
| éå¤å³å¨ | æ¯ä¸ªæ¥å£é½è¦åä¸éç±»ä¼¼çæ ¡éª |
| é¾ä»¥ç»´æ¤ | æ°å¢åæ®µè¦æ¹å¤å¤ä»£ç |
| ä¸ç»ä¸ | ä¸å人åçæ ¡éªæ ¼å¼äºè±å «é¨ |
ä¸ä¸åæ³æ¯ï¼ä½¿ç¨ Java ç Bean Validationï¼åå« JSR-303ï¼è§èï¼éè¿æ³¨è§£ä¼é å°å®æåæ°æ ¡éªã
äºåéå¿«éå ¥é¨
ç¬¬ä¸æ¥ï¼å¼å ¥ä¾èµ
Spring Boot 2.3+ éè¦æå¨å¼å ¥æ ¡éªä¾èµï¼èçæ¬èªå¸¦ï¼ï¼
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
ç¬¬äºæ¥ï¼å¨å®ä½ç±»ä¸å 注解
import javax.validation.constraints.*;
public class UserRegisterDTO {
@NotBlank(message = "ç¨æ·åä¸è½ä¸ºç©º")
private String username;
@NotBlank(message = "å¯ç ä¸è½ä¸ºç©º")
@Size(min = 6, max = 20, message = "å¯ç é¿åº¦å¿
é¡»å¨6-20ä½ä¹é´")
private String password;
@NotBlank(message = "é®ç®±ä¸è½ä¸ºç©º")
@Email(message = "é®ç®±æ ¼å¼ä¸æ£ç¡®")
private String email;
@NotNull(message = "å¹´é¾ä¸è½ä¸ºç©º")
@Min(value = 1, message = "年龿å°ä¸º1å²")
@Max(value = 150, message = "å¹´é¾æå¤§ä¸º150å²")
private Integer age;
// getter / setter çç¥
}
ç¬¬ä¸æ¥ï¼å¨Controllerä¸ä½¿ç¨ @Valid
@RestController
@RequestMapping("/api/user")
public class UserController {
@PostMapping("/register")
public Result register(@Valid @RequestBody UserRegisterDTO dto) {
// å¦ææ ¡éªä¸éè¿ï¼æ ¹æ¬ä¸ä¼æ§è¡å°è¿é
// è¿éåªç®¡åä¸å¡é»è¾
userService.register(dto);
return Result.success("注åæå");
}
}
å°±è¿æ ·ç®å䏿¥ï¼ææ if æ ¡éªé½ä¸éè¦åäºï¼ å½åæ°ä¸ç¬¦åè§åæ¶ï¼Spring Boot ä¼èªå¨æåºå¼å¸¸ï¼å¹¶è¿å400é误ã
å¸¸ç¨æ ¡éªæ³¨è§£å¤§å ¨ï¼æ°äººå¿ çï¼
ç©ºå¼æ ¡éª
| 注解 | éç¨ç±»å | 说æ |
|---|---|---|
@NotNull | ä»»æç±»å | ä¸è½ä¸º null |
@NotBlank | String | ä¸è½ä¸º nullã空å符串ãçº¯ç©ºæ ¼ |
@NotEmpty | StringãCollectionãMapãæ°ç» | ä¸è½ä¸º null æç©º |
使ç¨å»ºè®®
-
åç¬¦ä¸²åæ®µä¼å ç¨
@NotBlank -
éå/Map åæ®µç¨
@NotEmpty -
å è£ ç±»åï¼å¦
IntegerãLongï¼ç¨@NotNull
æ°å¼æ ¡éª
| 注解 | éç¨ç±»å | 说æ |
|---|---|---|
@Min(value) | æ°å¼ç±»å | æå°å¼ï¼å«ï¼ |
@Max(value) | æ°å¼ç±»å | æå¤§å¼ï¼å«ï¼ |
@DecimalMin(value) | æ°å¼ç±»å | æå°å¼ï¼æ¯æå°æ°ï¼ |
@DecimalMax(value) | æ°å¼ç±»å | æå¤§å¼ï¼æ¯æå°æ°ï¼ |
@Digits(integer, fraction) | æ°å¼ç±»å | æ´æ°ä½æ°åå°æ°ä½æ°éå¶ |
@Positive | æ°å¼ç±»å | æ£æ°ï¼>0ï¼ |
@PositiveOrZero | æ°å¼ç±»å | æ£æ°æ0 |
@Negative | æ°å¼ç±»å | è´æ° |
@NegativeOrZero | æ°å¼ç±»å | è´æ°æ0 |
示ä¾
@Min(value = 1, message = "æ°éè³å°ä¸º1")
@Max(value = 999, message = "æ°éä¸è½è¶
è¿999")
private Integer quantity;
@DecimalMin(value = "0.01", message = "éé¢è³å°ä¸º0.01")
@DecimalMax(value = "999999.99", message = "éé¢ä¸è½è¶
è¿999999.99")
private BigDecimal amount;
åç¬¦ä¸²æ ¡éª
| 注解 | 说æ |
|---|---|
@Size(min, max) | å符串é¿åº¦èå´ |
@Email | é®ç®±æ ¼å¼ |
@Pattern(regexp) | æ£å表达å¼å¹é |
@URL | URLæ ¼å¼ |
示ä¾
@Past(message = "çæ¥å¿
é¡»æ¯è¿å»çæ¶é´")
private LocalDate birthday;
@Future(message = "æææå¿
é¡»æäºå½åæ¶é´")
private LocalDateTime expireTime;
@AssertTrue(message = "å¿
é¡»åæç¨æ·åè®®")
private Boolean agreeProtocol;
åç»æ ¡éªï¼åä¸ä¸ªå¯¹è±¡ï¼ä¸ååºæ¯ä¸åè§å
åä¸ä¸ª DTO å¯è½å¨ä¸åæ¥å£ä¸ä½¿ç¨ï¼æ ¡éªè§åä¸ä¸æ ·ãæ¯å¦ï¼æ°å¢ç¨æ·æ¶å¯ç å¿ å¡«ï¼æ´æ°ç¨æ·æ¶å¯ç å¯éã
ç¬¬ä¸æ¥ï¼å®ä¹åç»æ¥å£ï¼åªæ¯ä¸¤ä¸ªç©ºæ¥å£ï¼
public interface CreateGroup {} // æ°å¢åç»
public interface UpdateGroup {} // æ´æ°åç»
ç¬¬äºæ¥ï¼å¨æ³¨è§£ä¸æå®åç»
public class UserDTO {
@NotNull(message = "IDä¸è½ä¸ºç©º", groups = UpdateGroup.class)
private Long id;
@NotBlank(message = "ç¨æ·åä¸è½ä¸ºç©º", groups = {CreateGroup.class, UpdateGroup.class})
private String username;
@NotBlank(message = "å¯ç ä¸è½ä¸ºç©º", groups = CreateGroup.class) // æ°å¢æ¶å¿
å¡«
@Size(min = 6, max = 20, message = "å¯ç é¿åº¦6-20ä½")
private String password;
@Email(message = "é®ç®±æ ¼å¼ä¸æ£ç¡®")
private String email; // 没æå®åç»ï¼é»è®¤å¨ææåç»é½çæ
}
ç¬¬ä¸æ¥ï¼å¨ Controller 䏿å®ä½¿ç¨çåç»
@RestController
@RequestMapping("/api/user")
public class UserController {
@PostMapping("/create") // æ°å¢æ¶ä½¿ç¨ CreateGroup
public Result create(@Validated(CreateGroup.class) @RequestBody UserDTO dto) {
// æ¤æ¶ä¼æ ¡éªï¼idï¼ä¸æ ¡éªï¼å 为没æå¨CreateGroup䏿 è®°ï¼ãusernameï¼æ ¡éªï¼ãpasswordï¼æ ¡éªï¼
userService.create(dto);
return Result.success();
}
@PutMapping("/update") // æ´æ°æ¶ä½¿ç¨ UpdateGroup
public Result update(@Validated(UpdateGroup.class) @RequestBody UserDTO dto) {
// æ¤æ¶ä¼æ ¡éªï¼idï¼æ ¡éªï¼ãusernameï¼æ ¡éªï¼ãpasswordï¼ä¸æ ¡éªï¼å 为没æå¨UpdateGroup䏿 è®°ï¼
userService.update(dto);
return Result.success();
}
}
注æï¼ åç»æ ¡éªæ¶è¦ç¨ @Validated è䏿¯ @Validï¼@Validated æè½æå®åç»ã
é«çº§æå·§ï¼èªå®ä¹æ ¡éªæ³¨è§£
ç¬¬ä¸æ¥ï¼å®ä¹æ³¨è§£
import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.*;
@Documented
@Constraint(validatedBy = GenderValidator.class) // æå®æ ¡éªå¨
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Gender {
String message() default "æ§å«åªè½æ¯ MALE æ FEMALE";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
ç¬¬äºæ¥ï¼å®ç°æ ¡éªå¨
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
public class GenderValidator implements ConstraintValidator<Gender, String> {
@Override
public boolean isValid(String value, ConstraintValidatorContext context) {
if (value == null) {
return true; // å
许为空ï¼ç± @NotNull æ§å¶æ¯å¦å¿
å¡«
}
return "MALE".equals(value) || "FEMALE".equals(value);
}
}
ç¬¬ä¸æ¥ï¼ä½¿ç¨
public class UserDTO {
@Gender(message = "æ§å«åªè½å¡« MALE æ FEMALE")
private String gender;
}
å ¨å±ç»ä¸å¤çæ ¡éªå¼å¸¸
é»è®¤æ åµä¸ï¼æ ¡éªå¤±è´¥ä¼è¿å400é误åé»è®¤çæ¥éä¿¡æ¯ã为äºè®©å端æ¶å°ç»ä¸æ ¼å¼çååºï¼éè¦å ¨å±å¼å¸¸å¤çã
import org.springframework.http.HttpStatus;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import java.util.HashMap;
import java.util.Map;
@RestControllerAdvice
public class GlobalExceptionHandler {
/**
* å¤ç @Valid æ ¡éªå¤±è´¥å¼å¸¸
*/
@ExceptionHandler(MethodArgumentNotValidException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
public Result handleValidationException(MethodArgumentNotValidException e) {
// æ¶éææåæ®µçæ ¡éªå¤±è´¥ä¿¡æ¯
Map<String, String> errors = new HashMap<>();
e.getBindingResult().getAllErrors().forEach(error -> {
String fieldName = ((FieldError) error).getField();
String errorMessage = error.getDefaultMessage();
errors.put(fieldName, errorMessage);
});
return Result.error(400, "åæ°æ ¡éªå¤±è´¥", errors);
}
}
è¿åç»åç«¯çæ ¼å¼
{
"code": 400,
"message": "åæ°æ ¡éªå¤±è´¥",
"data": {
"username": "ç¨æ·åä¸è½ä¸ºç©º",
"password": "å¯ç é¿åº¦å¿
é¡»å¨6-20ä½ä¹é´"
}
}
常è§é®é¢ä¸é¿åæå
åä¸ï¼@Valid ä¸çæ
åå ï¼ æ²¡æå¼å
¥ spring-boot-starter-validation ä¾èµï¼Spring Boot 2.3+ éè¦æå¨å¼å
¥ï¼ã
è§£å³æ¹æ¡ï¼ æ£æ¥ pom.xml æ¯å¦æè¯¥ä¾èµã
åäºï¼å¯¹ List éåæ ¡éªæ æ
éè¯¯åæ³ï¼
@PostMapping("/batch")
public Result batch(@Valid @RequestBody List<UserDTO> userList) { // List 䏿¯æ @Valid
// ...
}
æ£ç¡®åæ³ï¼ ç¨å è£ ç±»
@Data
public class UserListDTO {
@Valid
private List<UserDTO> userList;
}
@PostMapping("/batch")
public Result batch(@Valid @RequestBody UserListDTO dto) {
// ...
}
åä¸ï¼åµå¥å¯¹è±¡æ ¡éªå¤±æ
éè¯¯åæ³ï¼
public class OrderDTO {
@NotNull
private Long userId;
// 没æå @Validï¼Address å
é¨çæ ¡éªä¸çæ
private AddressDTO address;
}
æ£ç¡®åæ³ï¼
public class OrderDTO {
@NotNull
private Long userId;
@Valid // å¿
é¡»å @Valid æè½è§¦ååµå¥æ ¡éª
private AddressDTO address;
}
ååï¼æ´æ°ç±»åç @NotNull æ æ³æ ¡éª 0
@NotNull åªæ ¡éªæ¯å¦ä¸º nullï¼ä¸æ ¡éªå¼ç大å°ãå¦æè¦æé¤ 0ï¼éè¦é
å @Min(1) 使ç¨ã
åäºï¼æ¥å¿è®°å½æ¶æ³é²ææä¿¡æ¯
æ ¡éªå¤±è´¥æ¶å¦æç´æ¥ææ´ä¸ªDTO对象æå°å°æ¥å¿ï¼å¯è½æ³é²å¯ç çä¿¡æ¯ã
éè¯¯åæ³ï¼
logger.error("æ ¡éªå¤±è´¥ï¼åæ°ï¼{}", dto); // å¯è½å
å«å¯ç
æ£ç¡®åæ³ï¼
logger.error("æ ¡éªå¤±è´¥ï¼ç¨æ·ï¼{}ï¼å段ï¼{}", dto.getUsername(), errors);
宿´é¡¹ç®å ç»æ
ææ ¡éªç¸å ³ä»£ç æ¾å¨åºæçä½ç½®ï¼
æ£æ¥æ¸ å
为äºå¸®å©ä½ å¿«éæ£æ¥èªå·±çé¡¹ç®æ¯å¦å·²æ£ç¡®ä½¿ç¨æ ¡éªåè½ï¼è¿éæä¾ä¸ä¸ªæ£æ¥æ¸ åè¡¨æ ¼ï¼
| æ£æ¥é¡¹ | 说æ |
|---|---|
项ç®ä¸å·²å¼å
¥ spring-boot-starter-validation ä¾èµ | Spring Boot 2.3+ éè¦æå¨å¼å ¥ |
æææ¥å£åæ°é½ç¨ DTO æ¥æ¶ï¼é
å @Valid æ @Validated æ ¡éª | é¿å
å¨ Controller ä¸å大é if 夿 |
| åä¸ä¸ª DTO å¨ä¸åæ¥å£æä¸åæ ¡éªè§åæ¶ï¼ä½¿ç¨äºåç»æ ¡éª | éè¿ @Validated(Group.class) æå®åç» |
| å ç½®æ³¨è§£æ æ³æ»¡è¶³æ¶ï¼åäºèªå·±çèªå®ä¹æ³¨è§£ | å®ç° ConstraintValidator æ¥å£ |
å
¨å±å¼å¸¸å¤çå¨ç»ä¸å¤ç MethodArgumentNotValidException | è¿åç»ä¸æ ¼å¼çé误ååº |
åµå¥å¯¹è±¡æ ¡éªç¨äº @Valid | ç¡®ä¿åµå¥å¯¹è±¡å é¨çæ³¨è§£çæ |
| List éåæ ¡éªç¨äºå è£ ç±» | ç´æ¥å¯¹ List<T> ä½¿ç¨ @Valid æ æ |
| 没æå¨æ¥å¿ä¸è®°å½å¯ç çææä¿¡æ¯ | é¿å æ³é²ç¨æ·éç§ |
æå
仿å¨å ifÂ æ ¡éªå°ä½¿ç¨æ³¨è§£æ ¡éªï¼ä»£ç éåå° 80% 以ä¸ï¼å¯è¯»æ§åç»´æ¤æ§å´å¤§å¹
æåãè¿å°±æ¯ç¨å¥½å·¥å
·çä»·å¼ââææ¶é´è±å¨çæ£çä¸å¡é»è¾ä¸ï¼è䏿¯éå¤çæ ¡éªå³å¨ä¸ã
Aitishiku.com