Java注解校验案例怎么实操落地

wen java案例 30

本文目录导读:

Java注解校验案例怎么实操落地

  1. 注解校验核心组件
  2. 实际项目落地案例
  3. 最佳实践建议
  4. 常见问题解决

我来为您详细介绍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注解校验,提高代码质量和开发效率,建议根据实际业务需求选择合适的校验方式,并做好异常处理和日志记录。

抱歉,评论功能暂时关闭!