做网站费用需要分摊吗,网站的动画广告横幅怎么做的,做一元购网站 要多少钱,有哪些可以推广的平台一、基础篇#xff1a;引入依赖与校验流程1.1 引入 Validation 依赖在 Spring Boot 项目中#xff0c;启用参数校验通常只需要引入 spring-boot-starter-validation。需要注意 Spring Boot 版本带来的差异#xff1a;Spring Boot 2.3.x 之前#xff1a;spring-boot-starter…一、基础篇引入依赖与校验流程1.1 引入 Validation 依赖在 Spring Boot 项目中启用参数校验通常只需要引入spring-boot-starter-validation。需要注意 Spring Boot 版本带来的差异Spring Boot 2.3.x 之前spring-boot-starter-web会自动传递引入hibernate-validator无需额外配置。Spring Boot 2.3.x 之后为了精简依赖Web Starter 默认不再包含校验器需要手动添加xmldependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-validation/artifactId /dependency该 Starter 会自动引入hibernate-validatorJSR-380 的参考实现以及tomcat-embed-el表达式语言支持用于解析校验消息中的占位符。1.2 为什么需要后端校验前端校验如 JS 校验主要为了提升用户体验拦截明显不合法的输入。但攻击者可以绕过前端直接调用 API因此后端校验是保障数据安全的最后一道关卡。使用传统的if-else进行校验会导致代码臃肿、难以维护而基于注解的声明式校验能显著提升代码的可读性和可维护性 。二、基础篇注解驱动的声明式校验Spring Validation 的核心思想是通过注解描述校验规则由框架自动执行校验。2.1 常用校验注解速览校验注解主要位于javax.validation.constraints包下JSR 标准和org.hibernate.validator.constraints包下Hibernate 扩展。注解作用说明适用类型NotNull值不能为null任意类型NotEmpty字符串不为null且长度 0集合/数组/Map 不为null且 size 0CharSequence、Collection、Map、ArrayNotBlank字符串不为null且去除前后空格后长度 0仅限StringSize(min, max)字符串长度、集合/数组大小必须在指定范围内CharSequence、Collection、Map、ArrayMin(value)/Max(value)数字值必须 / 指定的值BigDecimal、BigInteger、byte、short、int、long及其包装类DecimalMin/DecimalMax与Min/Max类似但支持字符串形式的小数可设置是否包含边界同上Pattern(regexp)字符串必须匹配指定的正则表达式StringEmail字符串必须是格式良好的电子邮件地址StringPast/Future日期必须是过去 / 未来的时间Date、Calendar、Instant、LocalDate等时间类型Digits(integer, fraction)数字的整数部分和小数部分位数必须在指定范围内BigDecimal、BigInteger、String等Range(min, max)数字元素必须在合适的范围内BigDecimal、BigInteger、String注意NotNull、NotEmpty、NotBlank的区别需仔细区分Email允许值为null因此通常与NotNull或NotBlank联用 。2.2 基础使用场景场景一RequestBody 参数校验POST、PUT 请求对于接收 JSON 格式数据的接口通常在 DTOData Transfer Object类的字段上声明约束然后在 Controller 方法参数上使用Valid或Validated注解开启校验。java// 1. DTO 类定义校验规则 public class UserDTO { NotNull(message 用户ID不能为空) private Long id; NotBlank(message 用户名不能为空) Size(min 2, max 10, message 用户名长度必须在 {min} 到 {max} 之间) private String name; NotBlank(message 账号不能为空) Size(min 6, max 20, message 账号长度必须在6-20之间) private String account; Email(message 邮箱格式不正确) NotBlank(message 邮箱不能为空) private String email; // Getters and Setters ... } // 2. Controller 中使用 Valid 开启校验 RestController RequestMapping(/users) public class UserController { PostMapping public ResponseEntityString createUser(RequestBody Valid UserDTO userDTO) { // 只有校验通过才会进入方法体 System.out.println(用户数据校验通过处理业务逻辑...); return ResponseEntity.ok(用户创建成功); } }当请求的 JSON 数据不满足校验规则时会抛出MethodArgumentNotValidException异常并返回 400 状态码 。场景二RequestParam / PathVariable 参数校验GET 请求对于查询参数或路径变量需要在 Controller 类上标注Validated注解然后在参数前直接添加约束注解。javaValidated // 必须添加在类上才能触发方法参数的校验 RestController RequestMapping(/users) public class UserController { GetMapping(/{userId}) public ResponseEntityUserDTO getUserById( PathVariable Min(value 1, message 用户ID必须大于等于1) Long userId) { // 业务逻辑 return ResponseEntity.ok(new UserDTO()); } GetMapping(/search) public ResponseEntityListUserDTO searchUsers( RequestParam NotBlank(message 关键词不能为空) String keyword, RequestParam Min(value 1, message 页码最小为1) Max(value 100, message 页码最大为100) int page) { // 业务逻辑 return ResponseEntity.ok(new ArrayList()); } }若校验失败会抛出ConstraintViolationException异常 。三、进阶篇应对复杂业务场景基础校验只能应对单字段的静态规则但真实业务往往更复杂例如“新增时不校验ID修改时必须校验ID”、“一个对象内的字段相互关联”、“需要查询数据库校验唯一性”等。此时就需要更高级的特性。3.1 分组校验Group Validation同一个 DTO 在不同接口中可能有不同的校验规则。分组校验允许我们定义校验组空接口并在注解中指定groups属性然后在Validated注解中指定要激活的组 。java// 1. 定义分组接口仅作为标识 public class ValidationGroups { public interface Create {} // 新增场景 public interface Update {} // 修改场景 } // 2. DTO 中为字段指定分组 public class UserDTO { // 新增时不校验ID因为 ID 为空由数据库生成修改时必须校验 Null(groups ValidationGroups.Create.class, message 新增时ID必须为空) NotNull(groups ValidationGroups.Update.class, message 修改时ID不能为空) Min(value 1, groups ValidationGroups.Update.class, message ID必须为正整数) private Long id; NotBlank(message 用户名不能为空) // 不指定 groups默认属于 Default 组 Size(min 2, max 10, message 用户名长度需在2-10之间) private String name; // ... 其他字段 } // 3. Controller 中使用 Validated 指定分组 RestController RequestMapping(/users) public class UserController { PostMapping public ResponseEntityString createUser( RequestBody Validated(ValidationGroups.Create.class) UserDTO userDTO) { // 此时只会校验属于 Create 组和 Default 组的字段规则 return ResponseEntity.ok(创建成功); } PutMapping public ResponseEntityString updateUser( RequestBody Validated(ValidationGroups.Update.class) UserDTO userDTO) { // 此时只会校验属于 Update 组和 Default 组的字段规则 return ResponseEntity.ok(修改成功); } }注意如果注解上没有指定groups默认属于javax.validation.groups.Default组。当使用Validated(Create.class)时会校验Create 组 Default 组的规则。3.2 嵌套校验Nested Validation当 DTO 中包含另一个复杂对象或集合时需要在属性上额外添加Valid注解以触发嵌套对象的校验。javapublic class UserDTO { // ... 基础字段 NotNull(message 部门信息不能为空) Valid // 触发对 Department 对象的校验 private Department department; Valid // 触发对列表中每个 Job 对象的校验 private ListJob jobs; // Getters and Setters ... } public class Department { NotBlank(message 部门名称不能为空) private String deptName; NotNull(message 部门ID不能为空) private Long deptId; // ... } public class Job { NotBlank(message 职位名称不能为空) private String jobName; // ... }在 Controller 中只需校验外层的UserDTO框架会自动递归校验所有被Valid标记的属性及其子属性 。3.3 自定义校验注解Custom Validation当内置注解无法满足业务需求时如手机号格式校验、枚举值校验、唯一性校验等可以创建自定义注解。3.3.1 简单规则手机号格式校验java// 1. 定义注解 Target({ElementType.FIELD}) Retention(RetentionPolicy.RUNTIME) Constraint(validatedBy PhoneValidator.class) // 指定校验器 public interface Phone { String message() default 手机号格式不正确; Class?[] groups() default {}; Class? extends Payload[] payload() default {}; } // 2. 实现校验器 public class PhoneValidator implements ConstraintValidatorPhone, String { private static final Pattern PHONE_PATTERN Pattern.compile(^1[3-9]\\d{9}$); Override public boolean isValid(String value, ConstraintValidatorContext context) { if (value null) { return true; // 是否允许为空由 NotNull 等注解控制 } return PHONE_PATTERN.matcher(value).matches(); } } // 3. 使用 public class UserDTO { Phone(message 手机号格式错误) private String phone; }3.3.2 复杂规则带状态的校验跨字段或依赖数据库有时校验需要用到 Spring 容器中的 Bean如 Service、Repository此时校验器需要实现ConstraintValidator并可以通过Autowired注入依赖 。场景校验用户名是否唯一java// 1. 注解 Target(ElementType.FIELD) Retention(RetentionPolicy.RUNTIME) Constraint(validatedBy UniqueUserValidator.class) public interface UniqueUser { String message() default 用户名已存在; Class?[] groups() default {}; Class? extends Payload[] payload() default {}; } // 2. 校验器 Component // 将校验器注册为 Spring Bean以便注入依赖 public class UniqueUserValidator implements ConstraintValidatorUniqueUser, String { Autowired private UserRepository userRepository; // 假设这是操作数据库的 Bean Override public boolean isValid(String username, ConstraintValidatorContext context) { if (username null) { return true; } // 查询数据库判断是否存在 return userRepository.findByUsername(username) null; } } // 3. 使用 public class UserDTO { UniqueUser(message 用户名已被注册) private String username; }3.3.3 跨字段校验Class-Level 注解当校验规则涉及多个字段时例如密码和确认密码必须一致需要在类级别添加注解以便能同时获取多个字段的值 。java// 1. 类级别注解 Target(ElementType.TYPE) Retention(RetentionPolicy.RUNTIME) Constraint(validatedBy PasswordMatchesValidator.class) public interface PasswordMatches { String message() default 两次输入的密码不一致; Class?[] groups() default {}; Class? extends Payload[] payload() default {}; } // 2. 校验器 public class PasswordMatchesValidator implements ConstraintValidatorPasswordMatches, UserDTO { Override public boolean isValid(UserDTO userDTO, ConstraintValidatorContext context) { if (userDTO.getPassword() null || userDTO.getConfirmPassword() null) { return true; } return userDTO.getPassword().equals(userDTO.getConfirmPassword()); } } // 3. 在 DTO 上使用 PasswordMatches(message 密码和确认密码必须一致) public class UserDTO { NotBlank private String password; NotBlank private String confirmPassword; // ... getters and setters }3.4 编程式校验Programmatic Validation在某些 Service 层或工具类中可能需要手动触发校验。可以注入javax.validation.Validator对象进行手动校验。javaService public class UserService { Autowired private Validator validator; // 注入 Validator public void createUser(UserDTO userDTO) { SetConstraintViolationUserDTO violations validator.validate(userDTO); if (!violations.isEmpty()) { // 收集错误信息并抛出异常 StringBuilder sb new StringBuilder(); for (ConstraintViolationUserDTO violation : violations) { sb.append(violation.getPropertyPath()).append(: ).append(violation.getMessage()).append(; ); } throw new IllegalArgumentException(参数校验失败: sb.toString()); } // 继续执行业务 } }这种方式可以在任何需要校验的地方灵活使用而不仅限于 Controller 层 。四、高级篇优化与扩展4.1 全局异常处理统一返回格式默认情况下校验失败会抛出异常Spring 会返回包含异常信息的 400 响应。在实际项目中通常需要统一 API 的返回格式。可以使用ControllerAdvice全局捕获校验异常 。javaRestControllerAdvice public class GlobalExceptionHandler { // 处理 RequestBody 校验失败 ExceptionHandler(MethodArgumentNotValidException.class) public ResponseEntityMapString, Object handleMethodArgumentNotValidException(MethodArgumentNotValidException ex) { MapString, Object resp new HashMap(); resp.put(code, 400); resp.put(msg, 参数校验失败); MapString, String errors new HashMap(); ex.getBindingResult().getFieldErrors().forEach(error - errors.put(error.getField(), error.getDefaultMessage()) ); resp.put(errors, errors); return ResponseEntity.badRequest().body(resp); } // 处理 RequestParam/PathVariable 校验失败 ExceptionHandler(ConstraintViolationException.class) public ResponseEntityMapString, Object handleConstraintViolationException(ConstraintViolationException ex) { MapString, Object resp new HashMap(); resp.put(code, 400); resp.put(msg, 参数校验失败); MapString, String errors new HashMap(); ex.getConstraintViolations().forEach(violation - { String propertyPath violation.getPropertyPath().toString(); String message violation.getMessage(); errors.put(propertyPath.substring(propertyPath.lastIndexOf(.) 1), message); }); resp.put(errors, errors); return ResponseEntity.badRequest().body(resp); } // 处理其他异常... }4.2 快速失败模式Fail Fast默认情况下校验器会收集所有字段的违反约束信息后才抛出异常。如果希望一旦发现某个字段校验失败就立即抛出可以配置快速失败模式 。javaConfiguration public class ValidationConfig { Bean public Validator validator() { ValidatorFactory validatorFactory Validation.byProvider(HibernateValidator.class) .configure() .failFast(true) // 开启快速失败 .buildValidatorFactory(); return validatorFactory.getValidator(); } Bean public MethodValidationPostProcessor methodValidationPostProcessor() { MethodValidationPostProcessor processor new MethodValidationPostProcessor(); processor.setValidator(validator()); return processor; } }4.3 集合校验Validate Collection of DTOs当接口需要接收一个 DTO 列表如批量创建用户并希望对列表中的每个 DTO 进行校验时不能直接用ListUserDTO接收因为泛型擦除会导致校验失效。需要自定义一个包装类或者使用Valid注解在方法参数上但更常见的做法是使用自定义 List 包装类 。java// 1. 自定义包装类 public class ValidationListE implements ListE { Delegate // Lombok 注解自动生成 List 接口的实现 Valid // 标注 Valid告诉校验器要校验内部的元素 private ListE list new ArrayList(); // 必须重写 toString 等方法或使用 Delegate } // 2. Controller 中使用 PostMapping(/batch) public ResponseEntityString createUsers(RequestBody Valid ValidationListUserDTO userList) { // userList 中的每个 UserDTO 都会被校验 return ResponseEntity.ok(批量创建成功); }注Delegate是 Lombok 注解需要引入 Lombok 依赖。如果不使用 Lombok需要手动实现 List 接口的所有方法并转发给内部的 list。4.4 国际化错误信息i18n默认的错误信息是硬编码在注解的message属性中的。Spring Boot 支持使用MessageSource实现国际化使错误信息可以根据客户端语言环境动态切换 。创建消息文件在resources目录下创建ValidationMessages.properties默认、ValidationMessages_zh_CN.properties中文等。properties# ValidationMessages_zh_CN.properties user.name.required用户名不能为空 user.email.invalid邮箱地址格式不正确在注解中引用占位符javapublic class UserDTO { NotBlank(message {user.name.required}) private String name; Email(message {user.email.invalid}) private String email; }配置 MessageSource通常 Spring Boot 会自动配置但可以自定义javaBean public MessageSource messageSource() { ResourceBundleMessageSource messageSource new ResourceBundleMessageSource(); messageSource.setBasename(ValidationMessages); messageSource.setDefaultEncoding(UTF-8); return messageSource; }4.5 OASSwagger集成如果在项目中使用 Springfox 或 SpringDoc 生成 OpenAPI 文档代码中的校验注解会自动映射到生成的 OAS 文件中如minLength、maxLength、pattern等这有助于客户端了解 API 的输入约束提高 API 的安全性 。五、补充篇其他校验方式与对比5.1 传统 if / else 校验优点是灵活缺点是代码冗长、分散不易维护容易遗漏 。5.2 Spring 的 Assert 工具类Spring 提供了Assert工具类提供了一系列断言方法如notNull、hasText、isTrue等。它提供了一种半声明式的编程式校验代码比if-else简洁但通常用于方法内部的参数检查而非 Controller 层的数据绑定校验 。javapublic void updateUser(Long id, String name) { Assert.notNull(id, ID不能为空); Assert.hasText(name, 用户名不能为空); // ... }5.3Valid与Validated的区别这是面试中常见的问题总结如下表特性Valid(javax.validation)Validated(org.springframework)来源JSR-303/380 标准注解Spring 提供的注解对Valid的封装分组校验不支持支持通过value属性指定分组嵌套校验支持在嵌套属性上标注必须结合Valid使用自身不触发嵌套校验使用位置字段、方法参数、方法返回值类、方法参数、方法返回值功能触发校验触发校验 支持分组 支持 Spring 的 AOP 方法级校验简单来说在 Controller 层二者都可以用如果涉及分组校验必须用Validated。嵌套校验时外层的Validated不会递归触发内层对象的校验内层对象的字段上需要标注Valid。六、总结Spring Boot 中的参数校验方案已经发展得非常成熟和强大。通过本文的梳理我们可以看到一条从简单到复杂的清晰路径基础用法引入依赖使用NotNull、Size等注解对 DTO 字段或方法参数进行声明式校验配合Valid或Validated开启校验。应对复杂业务利用分组校验解决不同接口的差异化规则使用嵌套校验处理复杂的对象结构通过自定义注解和跨字段校验实现定制化逻辑。优化体验与性能通过全局异常处理统一错误格式配置快速失败模式优化响应速度结合国际化提供用户友好的提示。拓展使用场景在 Service 层使用编程式校验处理集合校验集成 Swagger 生成更准确的 API 文档。