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

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

大家好,我是小悟。

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

假如你的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步:进阶玩法——嵌套校验集合校验

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

// 测试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(垃圾进,垃圾出)——输入决定输出质量
  • 安全第一:很多安全漏洞都源于不可信的输入
  • 用户体验:早发现错误,早提示用户,别让用户猜谜


  1. 注解校验的优点:
  • 声明式:像贴标签一样简单,告别一堆if-else
  • 集中管理:规则在实体类上一目了然
  • 易于维护:改注解就能改规则,不用翻业务代码
  • 丰富内置:Spring提供了几十种注解,总有一款适合你


  1. 最佳实践建议:
  • 在DTO层做校验,保持业务层纯洁
  • 错误消息要友好,说人话,别甩技术术语
  • 区分必填和非必填字段,别要求用户填宇宙
  • 复杂逻辑用自定义校验器,别硬塞到一个注解里
  • 记得处理异常,给前端统一的错误格式


  1. 总结: 参数校验就像给你的代码请了个:
  • 门卫大爷:不合格的一律不让进
  • 语文老师:检查格式对不对,内容全不全
  • 健身教练:严格要求,不容马虎
  • 相声演员:出错时还能用幽默的方式告诉你


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

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

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

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

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

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

相关文章
|
14天前
|
人工智能 数据可视化 安全
王炸组合!阿里云 OpenClaw X 飞书 CLI,开启 Agent 基建狂潮!(附带免费使用6个月服务器)
本文详解如何用阿里云Lighthouse一键部署OpenClaw,结合飞书CLI等工具,让AI真正“动手”——自动群发、生成科研日报、整理知识库。核心理念:未来软件应为AI而生,CLI即AI的“手脚”,实现高效、安全、可控的智能自动化。
34787 40
王炸组合!阿里云 OpenClaw X 飞书 CLI,开启 Agent 基建狂潮!(附带免费使用6个月服务器)
|
9天前
|
人工智能 自然语言处理 安全
Claude Code 全攻略:命令大全 + 实战工作流(建议收藏)
本文介绍了Claude Code终端AI助手的使用指南,主要内容包括:1)常用命令如版本查看、项目启动和更新;2)三种工作模式切换及界面说明;3)核心功能指令速查表,包含初始化、压缩对话、清除历史等操作;4)详细解析了/init、/help、/clear、/compact、/memory等关键命令的使用场景和语法。文章通过丰富的界面截图和场景示例,帮助开发者快速掌握如何通过命令行和交互界面高效使用Claude Code进行项目开发,特别强调了CLAUDE.md文件作为项目知识库的核心作用。
9424 29
Claude Code 全攻略:命令大全 + 实战工作流(建议收藏)
|
4天前
|
人工智能 JavaScript Ubuntu
低成本搭建AIP自动化写作系统:Hermes保姆级使用教程,长文和逐步实操贴图
我带着怀疑的态度,深度使用了几天,聚焦微信公众号AIP自动化写作场景,写出来的几篇文章,几乎没有什么修改,至少合乎我本人的意愿,而且排版风格,也越来越完善,同样是起码过得了我自己这一关。 这个其实OpenClaw早可以实现了,但是目前我觉得最大的区别是,Hermes会自主总结提炼,并更新你的写作技能。 相信就冲这一点,就值得一试。 这篇帖子主要就Hermes部署使用,作一个非常详细的介绍,几乎一步一贴图。 关于Hermes,无论你赞成哪种声音,我希望都是你自己动手行动过,发自内心的选择!
1898 20
|
26天前
|
人工智能 JSON 机器人
让龙虾成为你的“公众号分身” | 阿里云服务器玩Openclaw
本文带你零成本玩转OpenClaw:学生认证白嫖6个月阿里云服务器,手把手配置飞书机器人、接入免费/高性价比AI模型(NVIDIA/通义),并打造微信公众号“全自动分身”——实时抓热榜、AI选题拆解、一键发布草稿,5分钟完成热点→文章全流程!
45672 155
让龙虾成为你的“公众号分身” | 阿里云服务器玩Openclaw
|
1天前
|
人工智能 自然语言处理 安全
|
8天前
|
机器学习/深度学习 存储 人工智能
还在手写Skill?hermes-agent 让 Agent 自己进化能力
Hermes-agent 是 GitHub 23k+ Star 的开源项目,突破传统 Agent 依赖人工编写Aegnt Skill 的瓶颈,首创“自我进化”机制:通过失败→反思→自动生成技能→持续优化的闭环,让 Agent 在实践中自主构建、更新技能库,持续自我改进。
1592 5
|
16天前
|
人工智能 JSON 监控
Claude Code 源码泄露:一份价值亿元的 AI 工程公开课
我以为顶级 AI 产品的护城河是模型。读完这 51.2 万行泄露的源码,我发现自己错了。
5730 26
|
6天前
|
IDE Java 编译器
【全网最详细】JDK17下载安装图文教程 | Java17编程环境搭建步骤详解
JDK 17是Java官方长期支持(LTS)版本,提供编译、调试、运行Java程序的完整工具链。具备高稳定性、强安全性及现代语言特性(如密封类、模式匹配),广泛用于企业开发、教学入门与生产环境,是学习和实践Java的首选基础工具。(239字)
1075 15
下一篇
开通oss服务