本文目录导读:

我来为您详细介绍Java注解校验的实操落地案例,包含完整示例和最佳实践。
注解校验核心组件
自定义注解示例
import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.*;
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Constraint(validatedBy = {PhoneValidator.class})
public @interface ValidPhone {
String message() default "手机号格式不正确";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
// 支持自定义正则
String pattern() default "^1[3-9]\\d{9}$";
}
自定义校验器
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
public class PhoneValidator implements ConstraintValidator<ValidPhone, String> {
private String pattern;
@Override
public void initialize(ValidPhone annotation) {
this.pattern = annotation.pattern();
}
@Override
public boolean isValid(String phone, ConstraintValidatorContext context) {
if (phone == null || phone.trim().isEmpty()) {
return false;
}
return phone.matches(pattern);
}
}
实际项目落地案例
实体类使用注解
import javax.validation.constraints.*;
import java.math.BigDecimal;
import java.time.LocalDateTime;
public class UserRequest {
@NotBlank(message = "用户名不能为空")
@Size(min = 2, max = 20, message = "用户名长度2-20个字符")
private String username;
@NotBlank(message = "密码不能为空")
@Pattern(regexp = "^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d).{8,20}$",
message = "密码需包含大小写字母和数字,8-20位")
private String password;
@NotNull(message = "年龄不能为空")
@Min(value = 18, message = "年龄必须大于18岁")
@Max(value = 120, message = "年龄不能超过120岁")
private Integer age;
@NotBlank(message = "邮箱不能为空")
@Email(message = "邮箱格式不正确")
private String email;
@ValidPhone(message = "手机号格式不正确")
private String phone;
@Future(message = "过期时间必须是未来时间")
private LocalDateTime expireTime;
@DecimalMin(value = "0.01", message = "金额必须大于0")
@DecimalMax(value = "999999.99", message = "金额不能超过999999.99")
private BigDecimal amount;
// getters and setters
}
Spring Boot集成配置
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.validation.beanvalidation.MethodValidationPostProcessor;
import javax.validation.Validator;
import org.springframework.boot.validation.MessageInterpolatorFactory;
import org.springframework.context.support.ResourceBundleMessageSource;
@Configuration
public class ValidationConfig {
@Bean
public MethodValidationPostProcessor methodValidationPostProcessor() {
MethodValidationPostProcessor processor = new MethodValidationPostProcessor();
processor.setValidator(validator());
return processor;
}
@Bean
public Validator validator() {
ValidatorFactory validatorFactory = Validation.byDefaultProvider()
.configure()
.messageInterpolator(new MessageInterpolatorFactory().getObject())
.buildValidatorFactory();
return validatorFactory.getValidator();
}
}
Controller层使用
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
import javax.validation.constraints.*;
import java.util.List;
@RestController
@RequestMapping("/api/users")
@Validated // 类级别启用校验
public class UserController {
// 方式1:直接校验实体
@PostMapping("/register")
public Result register(@Valid @RequestBody UserRequest request) {
// 业务逻辑
return Result.success();
}
// 方式2:校验路径参数和请求参数
@GetMapping("/{id}")
public Result getUser(
@PathVariable
@Min(value = 1, message = "用户ID必须大于0")
Long id,
@RequestParam(defaultValue = "1")
@Min(value = 1)
Integer page,
@Size(max = 10, message = "每页数量不能超过10")
@RequestParam(defaultValue = "10")
Integer size) {
return Result.success();
}
// 方式3:分组校验
@PostMapping("/update")
public Result updateUser(
@Validated(UpdateGroup.class)
@RequestBody UserRequest request) {
return Result.success();
}
// 方式4:集合参数校验
@PostMapping("/batch")
public Result batchCreate(
@Valid
@Size(min = 1, max = 100, message = "批量操作数量1-100")
@RequestBody List<@Valid UserRequest> users) {
return Result.success();
}
}
分组校验定义
public interface CreateGroup {
// 创建时的校验组
}
public interface UpdateGroup {
// 更新时的校验组
}
// 使用分组
public class UserRequest {
@Null(groups = CreateGroup.class, message = "创建时ID不能指定")
@NotNull(groups = UpdateGroup.class, message = "更新时ID不能为空")
private Long id;
@NotBlank(groups = {CreateGroup.class, UpdateGroup.class})
private String username;
// 更新时不需要校验密码
@NotBlank(groups = CreateGroup.class)
private String password;
}
全局异常处理
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import java.util.stream.Collectors;
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(MethodArgumentNotValidException.class)
public Result handleValidationExceptions(MethodArgumentNotValidException ex) {
String errors = ex.getBindingResult()
.getFieldErrors()
.stream()
.map(error -> error.getField() + ": " + error.getDefaultMessage())
.collect(Collectors.joining(", "));
return Result.error(400, "参数校验失败: " + errors);
}
@ExceptionHandler(ConstraintViolationException.class)
public Result handleConstraintViolation(ConstraintViolationException ex) {
String errors = ex.getConstraintViolations()
.stream()
.map(ConstraintViolation::getMessage)
.collect(Collectors.joining(", "));
return Result.error(400, "参数校验失败: " + errors);
}
@ExceptionHandler(BindException.class)
public Result handleBindException(BindException ex) {
String errors = ex.getFieldErrors()
.stream()
.map(error -> error.getField() + ": " + error.getDefaultMessage())
.collect(Collectors.joining(", "));
return Result.error(400, "参数绑定失败: " + errors);
}
}
国际化消息配置
// 1. 配置消息源
@Bean
public ResourceBundleMessageSource messageSource() {
ResourceBundleMessageSource source = new ResourceBundleMessageSource();
source.setBasenames("messages/validation");
source.setDefaultEncoding("UTF-8");
return source;
}
// 2. 创建消息文件 resources/messages/validation.properties
# 中文消息
user.username.notblank=用户名不能为空
user.password.pattern=密码格式不正确
# 3. 在注解中使用
@NotBlank(message = "{user.username.notblank}")
private String username;
最佳实践建议
校验层级划分
// 前端校验 - 使用JavaScript/Vue等
// Controller层校验 - 注解校验
// Service层校验 - 业务逻辑校验
// DAO层校验 - 数据库约束
@Service
public class UserService {
// 业务逻辑校验示例
public void createUser(@Valid UserRequest request) {
// 注解校验参数
// 业务校验
if (userRepository.existsByUsername(request.getUsername())) {
throw new BusinessException("用户名已存在");
}
// 复杂业务规则校验
validateBusinessRules(request);
}
private void validateBusinessRules(UserRequest request) {
// 自定义业务规则校验
}
}
自定义校验注解高级示例
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = PasswordMatchesValidator.class)
public @interface PasswordMatches {
String message() default "两次密码输入不一致";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
// 跨字段校验器
public class PasswordMatchesValidator implements ConstraintValidator<PasswordMatches, Object> {
@Override
public boolean isValid(Object obj, ConstraintValidatorContext context) {
if (obj instanceof UserRequest) {
UserRequest user = (UserRequest) obj;
return user.getPassword().equals(user.getConfirmPassword());
}
return false;
}
}
性能优化技巧
// 1. 使用缓存校验配置
@Configuration
public class ValidationConfig {
@Bean
public Validator validator() {
// 复用ValidatorFactory
return Validation.buildDefaultValidatorFactory().getValidator();
}
}
// 2. 按需校验
@Service
public class UserService {
@Autowired
private Validator validator;
public void partialUpdate(UserRequest request) {
// 只校验非空字段
Set<ConstraintViolation<UserRequest>> violations =
validator.validate(request, UpdateGroup.class);
if (!violations.isEmpty()) {
// 处理校验失败
}
}
}
常见问题解决
嵌套对象校验
public class OrderRequest {
@Valid // 嵌套校验
private List<@Valid OrderItem> items;
@Valid
private Address address;
}
public class OrderItem {
@NotNull
@Positive
private Long productId;
@Min(1)
@Max(99)
private Integer quantity;
}
条件校验
// 使用@ScriptAssert进行复杂条件校验
@ScriptAssert(lang = "javascript",
script = "_this.password.equals(_this.confirmPassword)",
message = "密码不一致")
public class UserRequest {
private String password;
private String confirmPassword;
}
通过以上案例,您可以在项目中快速落地Java注解校验,提高代码质量和开发效率,建议根据实际业务需求选择合适的校验方式,并做好异常处理和日志记录。