SpringBoot中注解參數(shù)校驗(yàn)的實(shí)戰(zhàn)指南
一、參數(shù)校驗(yàn):程序員的“防杠精神器”
假如你的API像個熱情的飯店服務(wù)員,用戶說“隨便來點(diǎn)吃的”,你就真給他上了盤空氣——這可不妙!參數(shù)校驗(yàn)就像是那個會耐心問“要辣的還是不辣的?要牛肉還是雞肉?”的細(xì)心服務(wù)員,確保不鬧出“我要咖啡你卻給我上了杯洗腳水”的尷尬。
SpringBoot的注解校驗(yàn)就像給你的方法參數(shù)請了個私人保鏢,專門攔截那些不靠譜的輸入。沒有它?用戶傳個null過來,你的程序可能就會表演“當(dāng)場崩潰”的絕活。
二、詳細(xì)步驟:給代碼戴上“緊箍咒”
第1步:先來點(diǎn)“開胃菜”——添加依賴
<!-- pom.xml里加入這個,就像泡面加鹵蛋,標(biāo)配! -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
第2步:創(chuàng)建個“相親簡歷”DTO類
import javax.validation.constraints.*;
import java.util.Date;
import java.util.List;
/**
* 用戶注冊DTO - 比相親網(wǎng)站的個人資料要求還嚴(yán)格
*/
public class UserRegisterDTO {
@NotBlank(message = "用戶名不能為空,難道您是無名氏?")
@Size(min = 2, max = 20, message = "用戶名長度在2-20之間,太短沒存在感,太長記不住")
private String username;
@Email(message = "郵箱格式不對,這可不是在寫情書,隨便寫寫就行")
private String email;
@Pattern(regexp = "^(?=.*[A-Za-z])(?=.*\\d)[A-Za-z\\d]{8,}$",
message = "密碼至少8位,包含字母和數(shù)字,別再用123456了!")
private String password;
@Min(value = 18, message = "未滿18歲?小朋友先去寫作業(yè)")
@Max(value = 120, message = "超過120歲?您是老神仙吧")
private Integer age;
@NotNull(message = "手機(jī)號必須填,不然外賣到了找誰?")
private String phone;
@AssertTrue(message = "必須接受協(xié)議,雖然可能沒人看")
private Boolean acceptedAgreement;
@Future(message = "預(yù)約時間必須是未來,時光機(jī)還沒發(fā)明呢")
private Date appointmentTime;
@Size(min = 1, max = 3, message = "最多選3個愛好,您是想成為全能超人嗎?")
private List<String> hobbies;
// 此處省略getter和setter,但它們確實(shí)存在,我發(fā)誓!
// 用Lombok的@Data也行,但今天咱們保持純潔的Java關(guān)系
// 自定義校驗(yàn)注解示例
@ValidGender
private String gender;
}
第3步:自定義校驗(yàn)注解——打造專屬“安檢儀”
import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.*;
/**
* 性別校驗(yàn)注解 - 咱們思想很開放,但數(shù)據(jù)要規(guī)范
*/
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = GenderValidator.class)
public @interface ValidGender {
String message() default "性別必須是男、女或保密,您這是來自火星嗎?";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
/**
* 性別校驗(yàn)器 - 嚴(yán)肅的判官
*/
public class GenderValidator implements ConstraintValidator<ValidGender, String> {
private static final Set<String> VALID_GENDERS =
new HashSet<>(Arrays.asList("男", "女", "保密"));
@Override
public boolean isValid(String value, ConstraintValidatorContext context) {
if (value == null) {
return true; // 用@NotNull管非空,咱們只管格式
}
return VALID_GENDERS.contains(value);
}
}
第4步:控制器里使用——給API裝上“安檢門”
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
import javax.validation.constraints.NotBlank;
@RestController
@RequestMapping("/api/users")
@Validated // 這個注解讓方法參數(shù)校驗(yàn)生效,就像給方法吃了“嚴(yán)格丸”
public class UserController {
/**
* 注冊用戶 - 參數(shù)校驗(yàn)比丈母娘挑女婿還嚴(yán)格
*/
@PostMapping("/register")
public Result register(@RequestBody @Valid UserRegisterDTO userDTO) {
// 如果參數(shù)校驗(yàn)失敗,根本走不到這里
// 就像考試不及格,進(jìn)不了下一輪面試
return Result.success("注冊成功,恭喜通過嚴(yán)格審查!");
}
/**
* 方法參數(shù)校驗(yàn) - 連路徑變量都不放過
*/
@GetMapping("/{id}")
public Result getUser(
@PathVariable @Min(value = 1, message = "ID必須大于0,您這是要找空氣用戶嗎?") Long id,
@RequestParam @NotBlank(message = "令牌不能為空,您這是想蒙混過關(guān)?") String token) {
return Result.success("找到了用戶ID: " + id);
}
/**
* 分組校驗(yàn) - 根據(jù)不同場景使用不同規(guī)則
* 就像上班穿正裝,在家穿睡衣,場合要分清
*/
@PostMapping("/update")
public Result updateUser(@RequestBody @Validated(UserUpdateGroup.class) UserUpdateDTO dto) {
return Result.success("更新成功");
}
}
// 分組接口定義
interface UserUpdateGroup {}
interface UserCreateGroup {}
第5步:全局異常處理——優(yōu)雅的“救火隊(duì)員”
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.validation.FieldError;
/**
* 全局異常處理器 - 專業(yè)收拾校驗(yàn)失敗的爛攤子
*/
@RestControllerAdvice
public class GlobalExceptionHandler {
/**
* 處理參數(shù)校驗(yàn)異常 - 把技術(shù)語言翻譯成人話
*/
@ExceptionHandler(MethodArgumentNotValidException.class)
public Result handleValidationException(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 Result.error(400, "參數(shù)校驗(yàn)失敗", errors)
.setMessage("您提交的數(shù)據(jù)有點(diǎn)小問題,請檢查后再試哦~");
}
/**
* 處理ConstraintViolationException - 方法參數(shù)校驗(yàn)失敗
*/
@ExceptionHandler(ConstraintViolationException.class)
public Result handleConstraintViolationException(ConstraintViolationException ex) {
List<String> errors = ex.getConstraintViolations().stream()
.map(violation -> violation.getMessage())
.collect(Collectors.toList());
return Result.error(400, "參數(shù)不合法", errors);
}
}
/**
* 統(tǒng)一返回結(jié)果 - 給前端一個標(biāo)準(zhǔn)的“成績單”
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Result<T> {
private Integer code;
private String message;
private T data;
private Long timestamp = System.currentTimeMillis();
public static <T> Result<T> success(T data) {
return new Result<>(200, "成功", data);
}
public static <T> Result<T> error(Integer code, String message, T data) {
return new Result<>(code, message, data);
}
public Result<T> setMessage(String message) {
this.message = message;
return this;
}
}
第6步:進(jìn)階玩法——嵌套校驗(yàn)和集合校驗(yàn)
/**
* 訂單DTO - 俄羅斯套娃式的校驗(yàn)
*/
public class OrderDTO {
@NotNull(message = "訂單信息不能為空")
@Valid // 這個注解讓嵌套校驗(yàn)生效,就像班主任檢查每個學(xué)生的作業(yè)
private UserDTO user;
@Valid // 集合也要逐個校驗(yàn),一個都別想逃
private List<@Valid OrderItemDTO> items;
@Valid
private AddressDTO address;
}
/**
* 地址DTO - 精確到門牌號
*/
public class AddressDTO {
@NotBlank(message = "省份不能空,您這是要寄到外太空?")
private String province;
@NotBlank(message = "城市不能空")
private String city;
@Size(min = 5, max = 100, message = "詳細(xì)地址5-100字,說清楚點(diǎn),快遞員會感謝您")
private String detail;
}
三、測試一下:看看“保鏢”工作認(rèn)不認(rèn)真
// 測試Controller - 專門搗亂看系統(tǒng)反應(yīng)
@SpringBootTest
@AutoConfigureMockMvc
class UserControllerTest {
@Autowired
private MockMvc mockMvc;
@Test
void testRegisterWithInvalidData() throws Exception {
String invalidUserJson = """
{
"username": "A", // 太短了!
"email": "not-an-email", // 這不是郵箱
"password": "123", // 太弱了
"age": 10, // 未成年!
"phone": null, // 空值
"acceptedAgreement": false, // 不同意協(xié)議
"appointmentTime": "2020-01-01", // 過去的時間
"hobbies": ["吃飯", "睡覺", "打豆豆", "刷手機(jī)", "發(fā)呆"] // 愛好太多
}
""";
mockMvc.perform(MockMvcRequestBuilders.post("/api/users/register")
.contentType(MediaType.APPLICATION_JSON)
.content(invalidUserJson))
.andExpect(status().isBadRequest()) // 應(yīng)該返回400
.andExpect(jsonPath("$.code").value(400))
.andExpect(jsonPath("$.data").exists()) // 錯誤詳情
.andDo(print()); // 打印響應(yīng),看看“保鏢”怎么懟你
}
}
四、性能優(yōu)化小貼士
/**
* 校驗(yàn)配置 - 讓校驗(yàn)既嚴(yán)格又高效
*/
@Configuration
public class ValidationConfig {
@Bean
public Validator validator() {
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
Validator validator = factory.getValidator();
// 可以在這里配置一些自定義設(shè)置
// 比如緩存校驗(yàn)器,避免重復(fù)創(chuàng)建
return validator;
}
/**
* 快速失敗模式 - 發(fā)現(xiàn)一個錯誤就立即返回
* 就像考試發(fā)現(xiàn)第一題錯了就交卷(不建議真人嘗試)
*/
@Bean
public Validator fastFailValidator() {
return Validation.byDefaultProvider()
.configure()
.addProperty("hibernate.validator.fail_fast", "true")
.buildValidatorFactory()
.getValidator();
}
}
總結(jié):參數(shù)校驗(yàn)的“人生哲理”
為什么需要參數(shù)校驗(yàn)?
- 防止GIGO(垃圾進(jìn),垃圾出)——輸入決定輸出質(zhì)量
- 安全第一:很多安全漏洞都源于不可信的輸入
- 用戶體驗(yàn):早發(fā)現(xiàn)錯誤,早提示用戶,別讓用戶猜謎
注解校驗(yàn)的優(yōu)點(diǎn):
- 聲明式:像貼標(biāo)簽一樣簡單,告別一堆if-else
- 集中管理:規(guī)則在實(shí)體類上一目了然
- 易于維護(hù):改注解就能改規(guī)則,不用翻業(yè)務(wù)代碼
- 豐富內(nèi)置:Spring提供了幾十種注解,總有一款適合你
最佳實(shí)踐建議:
- 在DTO層做校驗(yàn),保持業(yè)務(wù)層純潔
- 錯誤消息要友好,說人話,別甩技術(shù)術(shù)語
- 區(qū)分必填和非必填字段,別要求用戶填宇宙
- 復(fù)雜邏輯用自定義校驗(yàn)器,別硬塞到一個注解里
- 記得處理異常,給前端統(tǒng)一的錯誤格式
總結(jié): 參數(shù)校驗(yàn)就像給你的代碼請了個:
- 門衛(wèi)大爺:不合格的一律不讓進(jìn)
- 語文老師:檢查格式對不對,內(nèi)容全不全
- 健身教練:嚴(yán)格要求,不容馬虎
- 相聲演員:出錯時還能用幽默的方式告訴你
嚴(yán)謹(jǐn)?shù)某绦騿T對待輸入就像貓奴對待貓主子,既要有愛,也要有規(guī)矩! 你的API會因?yàn)榱己玫膮?shù)校驗(yàn)而變得更加健壯、安全、用戶友好。
以上就是SpringBoot中注解參數(shù)校驗(yàn)的實(shí)戰(zhàn)指南的詳細(xì)內(nèi)容,更多關(guān)于SpringBoot注解參數(shù)校驗(yàn)的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Java實(shí)現(xiàn)常見的排序算法代碼實(shí)例
這篇文章主要介紹了Java實(shí)現(xiàn)常見的排序算法代碼實(shí)例,按照思路實(shí)現(xiàn)了以下幾個排序算法(冒泡排序、直接插入排序、直接選擇排序、快速排序),方便日后用到,特此記錄一下,需要的朋友可以參考下2023-11-11
Spring?Cloud?Gateway?WebFlux?模式架構(gòu)場景分析
本文詳細(xì)介紹了Spring Cloud Gateway WebFlux模式的架構(gòu),包括入口類、關(guān)鍵類關(guān)系、核心組件和關(guān)鍵流程描述,Spring Cloud Gateway通過響應(yīng)式編程模型和非阻塞I/O,提供了高性能的API網(wǎng)關(guān)解決方案,感興趣的朋友跟隨小編一起看看吧2025-12-12
Spring Security 密碼驗(yàn)證動態(tài)加鹽的驗(yàn)證處理方法
小編最近在改造項(xiàng)目,需要將gateway整合security在一起進(jìn)行認(rèn)證和鑒權(quán),今天小編給大家分享Spring Security 密碼驗(yàn)證動態(tài)加鹽的驗(yàn)證處理方法,感興趣的朋友一起看看吧2021-06-06
java后臺本地文件轉(zhuǎn)為MultipartFile類型的實(shí)現(xiàn)方式
在Java后臺將本地文件轉(zhuǎn)換為MultipartFile類型,可以通過使用FileItemFactory創(chuàng)建FileItem,然后使用CommonsMultipartFile類構(gòu)造一個MultipartFile對象,將本地文件流轉(zhuǎn)換為MultipartFile,getMultipartFiles()和getMultipartFiles()方法2025-02-02
maven創(chuàng)建spark項(xiàng)目的pom.xml文件配置demo
這篇文章主要為大家介紹了maven創(chuàng)建spark項(xiàng)目的pom.xml文件配置demo,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-05-05
Servlet3.0實(shí)現(xiàn)文件上傳的方法
本篇文章主要介紹了Servlet實(shí)現(xiàn)文件上傳的方法,所謂文件上傳就是將本地的文件發(fā)送到服務(wù)器中保存。有興趣的可以了解一下。2017-03-03
使用Java servlet實(shí)現(xiàn)自動登錄退出功能
這篇文章主要介紹了使用Java servlet實(shí)現(xiàn)自動登錄退出功能,,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),具有一定的參考借鑒價值,需要的朋友可以參考下2019-11-11

