news 2026/4/23 13:12:15

SpringBoot注解参数校验,给代码穿上“防弹衣”

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
SpringBoot注解参数校验,给代码穿上“防弹衣”

大家好,我是小悟。

一、参数校验:程序员的“防杠精神器”

假如你的API像个热情的饭店服务员,用户说“随便来点吃的”,你就真给他上了盘空气——这可不妙!参数校验就像是那个会耐心问“要辣的还是不辣的?要牛肉还是鸡肉?”的细心服务员,确保不闹出“我要咖啡你却给我上了杯洗脚水”的尴尬。

SpringBoot的注解校验就像给你的方法参数请了个私人保镖,专门拦截那些不靠谱的输入。没有它?用户传个null过来,你的程序可能就会表演“当场崩溃”的绝活。

二、详细步骤:给代码戴上“紧箍咒”

第1步:先来点“开胃菜”——添加依赖

<!-- pom.xml里加入这个,就像泡面加卤蛋,标配! --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-validation</artifactId> </dependency>

第2步:创建个“相亲简历”DTO类

import javax.validation.constraints.*; import java.util.Date; import java.util.List; /** * 用户注册DTO - 比相亲网站的个人资料要求还严格 */ 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位,包含字母和数字,别再用123456了!") private String password; @Min(value = 18, message = "未满18岁?小朋友先去写作业") @Max(value = 120, message = "超过120岁?您是老神仙吧") private Integer age; @NotNull(message = "手机号必须填,不然外卖到了找谁?") private String phone; @AssertTrue(message = "必须接受协议,虽然可能没人看") private Boolean acceptedAgreement; @Future(message = "预约时间必须是未来,时光机还没发明呢") private Date appointmentTime; @Size(min = 1, max = 3, message = "最多选3个爱好,您是想成为全能超人吗?") private List<String> hobbies; // 此处省略getter和setter,但它们确实存在,我发誓! // 用Lombok的@Data也行,但今天咱们保持纯洁的Java关系 // 自定义校验注解示例 @ValidGender private String gender; }

第3步:自定义校验注解——打造专属“安检仪”

import javax.validation.Constraint; import javax.validation.Payload; import java.lang.annotation.*; /** * 性别校验注解 - 咱们思想很开放,但数据要规范 */ @Target({ElementType.FIELD}) @Retention(RetentionPolicy.RUNTIME) @Constraint(validatedBy = GenderValidator.class) public @interface ValidGender { String message() default "性别必须是男、女或保密,您这是来自火星吗?"; Class<?>[] groups() default {}; Class<? extends Payload>[] payload() default {}; } /** * 性别校验器 - 严肃的判官 */ 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 // 这个注解让方法参数校验生效,就像给方法吃了“严格丸” public class UserController { /** * 注册用户 - 参数校验比丈母娘挑女婿还严格 */ @PostMapping("/register") public Result register(@RequestBody @Valid UserRegisterDTO userDTO) { // 如果参数校验失败,根本走不到这里 // 就像考试不及格,进不了下一轮面试 return Result.success("注册成功,恭喜通过严格审查!"); } /** * 方法参数校验 - 连路径变量都不放过 */ @GetMapping("/{id}") public Result getUser( @PathVariable @Min(value = 1, message = "ID必须大于0,您这是要找空气用户吗?") Long id, @RequestParam @NotBlank(message = "令牌不能为空,您这是想蒙混过关?") String token) { return Result.success("找到了用户ID: " + id); } /** * 分组校验 - 根据不同场景使用不同规则 * 就像上班穿正装,在家穿睡衣,场合要分清 */ @PostMapping("/update") public Result updateUser(@RequestBody @Validated(UserUpdateGroup.class) UserUpdateDTO dto) { return Result.success("更新成功"); } } // 分组接口定义 interface UserUpdateGroup {} interface UserCreateGroup {}

第5步:全局异常处理——优雅的“救火队员”

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; /** * 全局异常处理器 - 专业收拾校验失败的烂摊子 */ @RestControllerAdvice public class GlobalExceptionHandler { /** * 处理参数校验异常 - 把技术语言翻译成人话 */ @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, "参数校验失败", errors) .setMessage("您提交的数据有点小问题,请检查后再试哦~"); } /** * 处理ConstraintViolationException - 方法参数校验失败 */ @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, "参数不合法", errors); } } /** * 统一返回结果 - 给前端一个标准的“成绩单” */ @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步:进阶玩法——嵌套校验和集合校验

/** * 订单DTO - 俄罗斯套娃式的校验 */ public class OrderDTO { @NotNull(message = "订单信息不能为空") @Valid // 这个注解让嵌套校验生效,就像班主任检查每个学生的作业 private UserDTO user; @Valid // 集合也要逐个校验,一个都别想逃 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 = "详细地址5-100字,说清楚点,快递员会感谢您") private String detail; }

三、测试一下:看看“保镖”工作认不认真

// 测试Controller - 专门捣乱看系统反应 @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, // 不同意协议 "appointmentTime": "2020-01-01", // 过去的时间 "hobbies": ["吃饭", "睡觉", "打豆豆", "刷手机", "发呆"] // 爱好太多 } """; mockMvc.perform(MockMvcRequestBuilders.post("/api/users/register") .contentType(MediaType.APPLICATION_JSON) .content(invalidUserJson)) .andExpect(status().isBadRequest()) // 应该返回400 .andExpect(jsonPath("$.code").value(400)) .andExpect(jsonPath("$.data").exists()) // 错误详情 .andDo(print()); // 打印响应,看看“保镖”怎么怼你 } }

四、性能优化小贴士

/** * 校验配置 - 让校验既严格又高效 */ @Configuration public class ValidationConfig { @Bean public Validator validator() { ValidatorFactory factory = Validation.buildDefaultValidatorFactory(); Validator validator = factory.getValidator(); // 可以在这里配置一些自定义设置 // 比如缓存校验器,避免重复创建 return validator; } /** * 快速失败模式 - 发现一个错误就立即返回 * 就像考试发现第一题错了就交卷(不建议真人尝试) */ @Bean public Validator fastFailValidator() { return Validation.byDefaultProvider() .configure() .addProperty("hibernate.validator.fail_fast", "true") .buildValidatorFactory() .getValidator(); } }

总结:参数校验的“人生哲理”

  1. 为什么需要参数校验?
    • 防止GIGO(垃圾进,垃圾出)——输入决定输出质量
    • 安全第一:很多安全漏洞都源于不可信的输入
    • 用户体验:早发现错误,早提示用户,别让用户猜谜
  2. 注解校验的优点:
    • 声明式:像贴标签一样简单,告别一堆if-else
    • 集中管理:规则在实体类上一目了然
    • 易于维护:改注解就能改规则,不用翻业务代码
    • 丰富内置:Spring提供了几十种注解,总有一款适合你
  3. 最佳实践建议:
    • 在DTO层做校验,保持业务层纯洁
    • 错误消息要友好,说人话,别甩技术术语
    • 区分必填和非必填字段,别要求用户填宇宙
    • 复杂逻辑用自定义校验器,别硬塞到一个注解里
    • 记得处理异常,给前端统一的错误格式
  4. 总结:
    参数校验就像给你的代码请了个:
    • 门卫大爷:不合格的一律不让进
    • 语文老师:检查格式对不对,内容全不全
    • 健身教练:严格要求,不容马虎
    • 相声演员:出错时还能用幽默的方式告诉你

严谨的程序员对待输入就像猫奴对待猫主子,既要有爱,也要有规矩!你的API会因为良好的参数校验而变得更加健壮、安全、用户友好。

谢谢你看我的文章,既然看到这里了,如果觉得不错,随手点个赞、转发、在看三连吧,感谢感谢。那我们,下次再见。

您的一键三连,是我更新的最大动力,谢谢

山水有相逢,来日皆可期,谢谢阅读,我们再会

我手中的金箍棒,上能通天,下能探海

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/23 8:13:49

UVC深紫外净水技术:或将成为饮用水安全的新一代解决方案

随着社会发展与生活水平提升&#xff0c;人类获取饮用水的方式经历了从井水、自来水到各类包装水与净化水的多次变革。当前&#xff0c;水质安全已成为公众普遍关注的议题。家庭净水设备虽能有效去除水中多种杂质与化学污染物&#xff0c;但其出水仍可能面临微生物二次滋生的风…

作者头像 李华
网站建设 2026/4/23 11:35:57

每天一个网络知识:什么是MSTP?

在网络技术不断演进的今天&#xff0c;生成树协议&#xff08;Spanning Tree Protocol, STP&#xff09;作为防止二层环路的关键机制&#xff0c;早已成为网络工程师必须掌握的基础知识。然而&#xff0c;传统的STP存在资源利用率低、收敛速度慢等问题。为了解决这些问题&#…

作者头像 李华
网站建设 2026/4/23 11:19:32

PHP vs Python:如何选择?

选择学习PHP还是Python取决于具体需求、职业目标和技术场景。以下是两者的对比分析&#xff0c;供参考&#xff1a;适用领域PHP 主要专注于Web开发&#xff0c;尤其是服务器端脚本。历史悠久的语言&#xff0c;WordPress、Facebook早期版本等均基于PHP构建。适合快速开发动态网…

作者头像 李华
网站建设 2026/4/23 9:58:56

小程序毕设选题推荐:基于springboot的智慧小饭桌成人小饭桌微信小程序【附源码、mysql、文档、调试+代码讲解+全bao等】

博主介绍&#xff1a;✌️码农一枚 &#xff0c;专注于大学生项目实战开发、讲解和毕业&#x1f6a2;文撰写修改等。全栈领域优质创作者&#xff0c;博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java、小程序技术领域和毕业项目实战 ✌️技术范围&#xff1a;&am…

作者头像 李华
网站建设 2026/4/15 14:11:24

西门子罗宾康A5E01649374I/O板

西门子罗宾康A5E01649374 I/O板详解一、产品概述西门子罗宾康A5E01649374是一款高性能工业自动化I/O&#xff08;输入/输出&#xff09;接口板&#xff0c;专为工业控制系统的信号采集与执行器驱动设计。作为西门子Simatic系列的重要组成部分&#xff0c;该模块采用模块化设计&…

作者头像 李华
网站建设 2026/4/23 9:58:15

基于SpringBoot的校运会管理系统毕设源码

博主介绍&#xff1a;✌ 专注于Java,python,✌关注✌私信我✌具体的问题&#xff0c;我会尽力帮助你。一、研究目的本研究旨在开发一套基于SpringBoot框架的校运会管理系统&#xff0c;以实现校运会组织、管理、报名、成绩统计和查询等功能的自动化和智能化。具体研究目的如下&…

作者头像 李华