大庆市建设局网站济南网站建设分销商城
2026/4/10 11:12:53 网站建设 项目流程
大庆市建设局网站,济南网站建设分销商城,深圳建设发展集团有限公司,wordpress内页无法打开一、注解校验概述 1.1 为什么需要注解校验#xff1f; 在实际开发中#xff0c;我们经常需要对输入数据进行校验#xff1a; java // 传统方式#xff1a;代码冗长、难以维护 public void createUser(String username, String email, Integer age) { if (username nul…一、注解校验概述1.1 为什么需要注解校验在实际开发中我们经常需要对输入数据进行校验java// 传统方式代码冗长、难以维护 public void createUser(String username, String email, Integer age) { if (username null || username.length() 3 || username.length() 20) { throw new IllegalArgumentException(用户名长度必须在3-20之间); } if (email null || !email.matches(^[A-Za-z0-9_.-](.)$)) { throw new IllegalArgumentException(邮箱格式不正确); } if (age null || age 18 || age 120) { throw new IllegalArgumentException(年龄必须在18-120之间); } //... } // ✅ 注解校验简洁、声明式、可复用 public void createUser(Valid UserDTO userDTO) { //... }注解校验的优势✅声明式通过注解声明校验规则代码更简洁✅可复用校验逻辑可以复用避免重复代码✅易维护校验规则集中管理易于维护✅标准化遵循JSR-303/JSR-380标准✅国际化支持国际化错误消息1.2 常用校验注解Jakarta Bean Validation提供的注解注解说明示例NotNull值不能为nullNotNull String nameNotEmpty集合、字符串、数组不能为空NotEmpty ListString itemsNotBlank字符串不能为空白去除首尾空格后长度0NotBlank String contentSize(min, max)大小必须在指定范围内Size(min3, max20) String nameMin(value)数值必须大于等于指定值Min(18) Integer ageMax(value)数值必须小于等于指定值Max(120) Integer ageEmail必须是有效的邮箱格式Email String emailPattern(regexp)必须匹配指定的正则表达式Pattern(regexp^1[3-9]\\d{9}$) String phonePast日期必须是过去的时间Past Date birthDateFuture日期必须是未来的时间Future Date appointmentDateAssertTrue布尔值必须是trueAssertTrue Boolean agreedNegative数值必须是负数Negative Integer balancePositive数值必须是正数Positive Integer amount二、Valid vs Validated2.1 核心区别这两个注解虽然功能相似但有关键区别特性ValidValidated来源Jakarta Bean Validation (JSR-380)Spring Framework位置方法、字段、构造器参数方法、类型、参数嵌套校验✅ 支持✅ 支持分组校验❌ 不支持✅ 支持校验组序列❌ 不支持✅ 支持Spring集成需要配置原生支持2.2 Valid的使用基本用法javaData public class UserDTO { NotNull(message 用户ID不能为空) private Long id; NotBlank(message 用户名不能为空) Size(min 3, max 20, message 用户名长度必须在3-20之间) private String username; Email(message 邮箱格式不正确) NotBlank(message 邮箱不能为空) private String email; Min(value 18, message 年龄必须大于等于18岁) Max(value 120, message 年龄必须小于等于120岁) private Integer age; }在Controller中使用javaRestController RequestMapping(/api/users) public class UserController { // 使用Valid触发校验 PostMapping public ResponseEntityString createUser(Valid RequestBody UserDTO userDTO) { // 如果校验失败会自动抛出MethodArgumentNotValidException return ResponseEntity.ok(用户创建成功); } }嵌套校验javaData public class OrderDTO { NotNull(message 订单ID不能为空) private Long orderId; Valid // 关键必须使用Valid才能触发嵌套对象的校验 NotNull(message 用户信息不能为空) private UserDTO user; Valid NotEmpty(message 订单项不能为空) private ListOrderItemDTO items; } Data public class OrderItemDTO { NotNull(message 商品ID不能为空) private Long productId; Min(value 1, message 数量必须大于0) private Integer quantity; }2.3 Validated的使用基本用法javaService Validated // 类级别添加Validated启用方法参数校验 public class UserService { // 简单参数校验 public void updateUser( NotNull(message 用户ID不能为空) Long id, NotBlank(message 用户名不能为空) String username) { // 业务逻辑... } // 对象校验 public void createUser(Valid UserDTO userDTO) { // 业务逻辑... } }分组校验Validated独有javapublic interface CreateGroup {} public interface UpdateGroup {} Data public class UserDTO { Null(groups CreateGroup.class, message 创建时ID必须为空) NotNull(groups UpdateGroup.class, message 更新时ID不能为空) private Long id; NotBlank(groups {CreateGroup.class, UpdateGroup.class}) private String username; } RestController RequestMapping(/api/users) public class UserController { PostMapping public ResponseEntityString create( Validated(CreateGroup.class) RequestBody UserDTO userDTO) { return ResponseEntity.ok(创建成功); } PutMapping public ResponseEntityString update( Validated(UpdateGroup.class) RequestBody UserDTO userDTO) { return ResponseEntity.ok(更新成功); } }2.4 选择建议选择决策树less是否需要分组校验 ├─ 是 → 使用 Validated └─ 否 → 是否在Controller中 ├─ 是 → 两者都可以推荐 Valid └─ 否 → 使用 Validated最佳实践Controller层使用 Valid简洁、够用Service层使用 Validated支持方法参数校验需要分组必须使用 Validated嵌套对象在嵌套对象字段上添加 Valid三、校验组Validation Groups3.1 为什么需要校验组不同场景下同一对象的校验规则可能不同java体验AI代码助手代码解读复制代码// 场景1新增用户 // - id为空由数据库生成 // - username必填 // - password必填 // 场景2更新用户 // - id必填根据id更新 // - username可选 // - password可选不修改则不传3.2 定义校验组java/** * 校验组定义 */ public interface ValidationGroups { // 新增操作 interface Create {} // 更新操作 interface Update {} // 删除操作 interface Delete {} // 默认组不指定group时使用 interface Default {} }3.3 在实体类中使用分组javaData public class UserDTO { // 创建时ID必须为空更新时ID不能为空 Null(groups ValidationGroups.Create.class, message 创建用户时ID必须为空) NotNull(groups {ValidationGroups.Update.class, ValidationGroups.Delete.class}, message 更新/删除用户时ID不能为空) private Long id; NotBlank(groups {ValidationGroups.Create.class, ValidationGroups.Update.class}, message 用户名不能为空) Size(min 3, max 20, groups {ValidationGroups.Create.class, ValidationGroups.Update.class}, message 用户名长度必须在3-20之间) private String username; Email(groups ValidationGroups.Create.class, message 邮箱格式不正确) NotBlank(groups ValidationGroups.Create.class, message 邮箱不能为空) private String email; // 创建时密码必填更新时可选 NotBlank(groups ValidationGroups.Create.class, message 密码不能为空) Size(min 6, max 20, groups ValidationGroups.Create.class, message 密码长度必须在6-20之间) private String password; NotNull(groups ValidationGroups.Create.class, message 年龄不能为空) Min(value 18, groups ValidationGroups.Create.class, message 年龄必须大于等于18岁) private Integer age; }3.4 使用校验组javaRestController RequestMapping(/api/users) public class UserController { PostMapping public ResponseEntity? create( Validated(ValidationGroups.Create.class) RequestBody UserDTO userDTO) { // 只校验Create组中定义的规则 return ResponseEntity.ok(创建成功); } PutMapping(/{id}) public ResponseEntity? update( PathVariable Long id, Validated(ValidationGroups.Update.class) RequestBody UserDTO userDTO) { // 只校验Update组中定义的规则 return ResponseEntity.ok(更新成功); } DeleteMapping(/{id}) public ResponseEntity? delete( PathVariable Long id, Validated(ValidationGroups.Delete.class) RequestBody UserDTO userDTO) { // 只校验Delete组中定义的规则 return ResponseEntity.ok(删除成功); } }3.5 组序列Group Sequence控制校验组的执行顺序默认按照定义的顺序依次校验javaGroupSequence({CreateGroup.class, UpdateGroup.class, Default.class}) public interface OrderedGroup { } RestController public class UserController { PostMapping public ResponseEntity? create( Validated(OrderedGroup.class) RequestBody UserDTO userDTO) { return ResponseEntity.ok(创建成功); } }注意一旦某个组校验失败后续组不会再执行。四、自定义校验注解4.1 自定义注解的应用场景当内置注解无法满足需求时可以创建自定义校验注解手机号校验PhoneNumber身份证号校验IdCard枚举值校验EnumValue字段互斥FieldMatch密码强度StrongPassword4.2 实现手机号校验注解第一步定义注解javaTarget({ElementType.FIELD, ElementType.PARAMETER}) Retention(RetentionPolicy.RUNTIME) Constraint(validatedBy PhoneNumberValidator.class) Documented public interface PhoneNumber { // 必须的三个属性 String message() default 手机号格式不正确; Class?[] groups() default {}; Class? extends Payload[] payload() default {}; // 自定义属性是否支持国际化号码 boolean international() default false; // 自定义属性支持的国家代码 String[] countryCodes() default {86}; }第二步实现校验器javapublic class PhoneNumberValidator implements ConstraintValidatorPhoneNumber, String { private boolean international; private String[] countryCodes; // 中国大陆手机号正则 private static final String CHINA_PHONE_PATTERN ^1[3-9]\\d{9}$; Override public void initialize(PhoneNumber constraintAnnotation) { this.international constraintAnnotation.international(); this.countryCodes constraintAnnotation.countryCodes(); } Override public boolean isValid(String value, ConstraintValidatorContext context) { // null值由NotNull处理 if (value null) { return true; } // 国际号码校验 if (international) { return validateInternational(value); } // 中国手机号校验 return value.matches(CHINA_PHONE_PATTERN); } private boolean validateInternational(String phone) { // 简单的国际号码校验逻辑 for (String code : countryCodes) { if (phone.startsWith(code)) { String number phone.substring(code.length()); return number.matches(^\\d{6,15}$); } } return false; } }第三步使用注解javaData public class UserDTO { PhoneNumber(message 手机号格式不正确) private String mobile; PhoneNumber(international true, countryCodes {86, 1, 44}, message 国际手机号格式不正确) private String internationalPhone; }4.3 实现密码强度校验注解定义javaTarget({ElementType.FIELD, ElementType.PARAMETER}) Retention(RetentionPolicy.RUNTIME) Constraint(validatedBy StrongPasswordValidator.class) Documented public interface StrongPassword { String message() default 密码强度不足; Class?[] groups() default {}; Class? extends Payload[] payload() default {}; // 最小长度 int minLength() default 8; // 是否需要大写字母 boolean requireUppercase() default true; // 是否需要小写字母 boolean requireLowercase() default true; // 是否需要数字 boolean requireDigit() default true; // 是否需要特殊字符 boolean requireSpecialChar() default true; }校验器实现javapublic class StrongPasswordValidator implements ConstraintValidatorStrongPassword, String { private int minLength; private boolean requireUppercase; private boolean requireLowercase; private boolean requireDigit; private boolean requireSpecialChar; private static final Pattern UPPERCASE_PATTERN Pattern.compile([A-Z]); private static final Pattern LOWERCASE_PATTERN Pattern.compile([a-z]); private static final Pattern DIGIT_PATTERN Pattern.compile(\\d); private static final Pattern SPECIAL_CHAR_PATTERN Pattern.compile([!#$%^*()_\\-\\[\\]{};:\\\\\|,.\\/?]); Override public void initialize(StrongPassword constraintAnnotation) { this.minLength constraintAnnotation.minLength(); this.requireUppercase constraintAnnotation.requireUppercase(); this.requireLowercase constraintAnnotation.requireLowercase(); this.requireDigit constraintAnnotation.requireDigit(); this.requireSpecialChar constraintAnnotation.requireSpecialChar(); } Override public boolean isValid(String password, ConstraintValidatorContext context) { if (password null) { return true; } if (password.length() minLength) { return false; } if (requireUppercase !UPPERCASE_PATTERN.matcher(password).find()) { return false; } if (requireLowercase !LOWERCASE_PATTERN.matcher(password).find()) { return false; } if (requireDigit !DIGIT_PATTERN.matcher(password).find()) { return false; } if (requireSpecialChar !SPECIAL_CHAR_PATTERN.matcher(password).find()) { return false; } return true; } }4.4 跨字段校验实现密码和确认密码必须一致的校验注解定义javaTarget({ElementType.TYPE}) Retention(RetentionPolicy.RUNTIME) Constraint(validatedBy FieldMatchValidator.class) Documented public interface FieldMatch { String message() default 字段值不匹配; Class?[] groups() default {}; Class? extends Payload[] payload() default {}; // 第一个字段名 String first(); // 第二个字段名 String second(); }校验器实现javapublic class FieldMatchValidator implements ConstraintValidatorFieldMatch, Object { private String firstFieldName; private String secondFieldName; Override public void initialize(FieldMatch constraintAnnotation) { this.firstFieldName constraintAnnotation.first(); this.secondFieldName constraintAnnotation.second(); } Override public boolean isValid(Object value, ConstraintValidatorContext context) { if (value null) { return true; } try { Field firstField value.getClass().getDeclaredField(firstFieldName); firstField.setAccessible(true); Object firstValue firstField.get(value); Field secondField value.getClass().getDeclaredField(secondFieldName); secondField.setAccessible(true); Object secondValue secondField.get(value); return Objects.equals(firstValue, secondValue); } catch (Exception e) { return false; } } }使用示例javaData FieldMatch(first password, second confirmPassword, message 两次输入的密码不一致) public class RegisterRequest { private String username; private String password; private String confirmPassword; }五、生产环境实战5.1 统一异常处理在生产环境中需要统一处理校验异常javaRestControllerAdvice public class GlobalExceptionHandler { /** * 处理 Valid 触发的校验异常 */ ExceptionHandler(MethodArgumentNotValidException.class) public ResponseEntityErrorResponse handleValidationException( MethodArgumentNotValidException ex) { ListString errors ex.getBindingResult() .getFieldErrors() .stream() .map(error - error.getField() : error.getDefaultMessage()) .collect(Collectors.toList()); ErrorResponse response ErrorResponse.builder() .code(400) .message(参数校验失败) .errors(errors) .timestamp(LocalDateTime.now()) .build(); return ResponseEntity.badRequest().body(response); } /** * 处理 Validated 触发的校验异常 */ ExceptionHandler(ConstraintViolationException.class) public ResponseEntityErrorResponse handleConstraintViolation( ConstraintViolationException ex) { ListString errors ex.getConstraintViolations() .stream() .map(violation - violation.getPropertyPath() : violation.getMessage()) .collect(Collectors.toList()); ErrorResponse response ErrorResponse.builder() .code(400) .message(参数校验失败) .errors(errors) .timestamp(LocalDateTime.now()) .build(); return ResponseEntity.badRequest().body(response); } /** * 处理请求参数绑定异常 */ ExceptionHandler(BindException.class) public ResponseEntityErrorResponse handleBindException(BindException ex) { ListString errors ex.getBindingResult() .getFieldErrors() .stream() .map(error - error.getField() : error.getDefaultMessage()) .collect(Collectors.toList()); ErrorResponse response ErrorResponse.builder() .code(400) .message(参数绑定失败) .errors(errors) .timestamp(LocalDateTime.now()) .build(); return ResponseEntity.badRequest().body(response); } } Data Builder class ErrorResponse { private Integer code; private String message; private ListString errors; private LocalDateTime timestamp; }5.2 快速失败机制默认情况下Bean Validation会校验所有约束并返回所有错误。如果需要在第一个错误时就停止javaConfiguration public class ValidationConfig { Bean public Validator validator() { ValidatorFactory factory Validation.byDefaultProvider() .configure() .failFast(true) // 启用快速失败 .buildValidatorFactory(); return factory.getValidator(); } Bean public MethodValidationPostProcessor methodValidationPostProcessor() { MethodValidationPostProcessor processor new MethodValidationPostProcessor(); processor.setValidator(validator()); return processor; } }5.4 手动触发校验在Service层手动触发校验javaService RequiredArgsConstructor public class UserService { private final Validator validator; public void createUser(UserDTO userDTO) { // 手动校验 SetConstraintViolationUserDTO violations validator.validate(userDTO, Default.class); if (!violations.isEmpty()) { throw new ConstraintViolationException(violations); } // 业务逻辑... } }六、最佳实践6.1 设计原则单一职责每个注解只负责一个校验规则组合使用多个简单注解组合成复杂规则错误消息清晰提供具体、可操作的错误提示分组管理使用校验组区分不同场景自定义注解复杂业务逻辑创建自定义注解6.2 性能优化避免过度校验只校验必要的数据校验顺序将简单的校验放在前面缓存ValidatorValidator实例可以复用异步校验对于复杂校验考虑异步处理七、总结本文系统地介绍了Java注解校验的核心概念和实践Valid vs Validated理解两者的区别和适用场景校验组使用分组管理不同场景的校验规则自定义注解创建符合业务需求的校验注解生产实践异常处理、国际化、性能优化

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询