本文目录导读:

我来给你一个完整的自定义校验注解案例,包括从注解定义到使用的完整流程。
创建自定义注解
import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.*;
/**
* 手机号校验注解
*/
@Documented
@Constraint(validatedBy = PhoneValidator.class)
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface Phone {
// 默认错误消息
String message() default "手机号格式不正确";
// 分组
Class<?>[] groups() default {};
// 负载
Class<? extends Payload>[] payload() default {};
}
实现校验器
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import java.util.regex.Pattern;
/**
* 手机号校验器
*/
public class PhoneValidator implements ConstraintValidator<Phone, String> {
// 简单的手机号正则
private static final Pattern PHONE_PATTERN = Pattern.compile(
"^1[3-9]\\d{9}$"
);
@Override
public void initialize(Phone constraintAnnotation) {
// 初始化方法,可以获取注解参数
}
@Override
public boolean isValid(String phone, ConstraintValidatorContext context) {
if (phone == null || phone.isEmpty()) {
// 是否允许为空由 @NotNull 等注解控制
return true;
}
return PHONE_PATTERN.matcher(phone).matches();
}
}
更多复杂示例
1 带参数的校验注解
/**
* 金额范围校验注解
*/
@Documented
@Constraint(validatedBy = MoneyRangeValidator.class)
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface MoneyRange {
double min() default 0;
double max() default Double.MAX_VALUE;
String message() default "金额超出范围";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
2 复杂校验器实现
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import java.math.BigDecimal;
/**
* 金额范围校验器
*/
public class MoneyRangeValidator implements ConstraintValidator<MoneyRange, BigDecimal> {
private BigDecimal min;
private BigDecimal max;
@Override
public void initialize(MoneyRange constraintAnnotation) {
this.min = BigDecimal.valueOf(constraintAnnotation.min());
this.max = BigDecimal.valueOf(constraintAnnotation.max());
}
@Override
public boolean isValid(BigDecimal value, ConstraintValidatorContext context) {
if (value == null) {
return true;
}
boolean isValid = value.compareTo(min) >= 0 && value.compareTo(max) <= 0;
if (!isValid) {
// 自定义错误消息
context.disableDefaultConstraintViolation();
context.buildConstraintViolationWithTemplate(
String.format("金额必须在 %.2f 到 %.2f 之间", min, max)
).addConstraintViolation();
}
return isValid;
}
}
3 对象级联校验注解
/**
* 密码强度校验注解
*/
@Documented
@Constraint(validatedBy = PasswordValidator.class)
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface Password {
int minLength() default 8;
int maxLength() default 20;
boolean requireUpperCase() default true;
boolean requireLowerCase() default true;
boolean requireDigit() default true;
String message() default "密码强度不足";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
/**
* 密码强度校验器
*/
public class PasswordValidator implements ConstraintValidator<Password, String> {
private Password password;
@Override
public void initialize(Password constraintAnnotation) {
this.password = constraintAnnotation;
}
@Override
public boolean isValid(String value, ConstraintValidatorContext context) {
if (value == null) {
return true;
}
// 长度检查
if (value.length() < password.minLength() || value.length() > password.maxLength()) {
return false;
}
// 字符类型检查
if (password.requireUpperCase() && !value.matches(".*[A-Z].*")) {
return false;
}
if (password.requireLowerCase() && !value.matches(".*[a-z].*")) {
return false;
}
if (password.requireDigit() && !value.matches(".*\\d.*")) {
return false;
}
return true;
}
}
使用案例
1 在实体类中使用
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
public class UserDTO {
@NotBlank(message = "用户名不能为空")
private String username;
@Phone(message = "手机号格式不正确")
private String phone;
@Password(message = "密码至少8位,需包含大写字母、小写字母和数字")
private String password;
@MoneyRange(min = 100, max = 10000, message = "账户余额必须在100-10000之间")
private BigDecimal balance;
// getter/setter 省略
}
2 Spring Boot Controller中使用
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
@RestController
@RequestMapping("/api/users")
@Validated
public class UserController {
/**
* 创建用户
*/
@PostMapping
public Result createUser(@Valid @RequestBody UserDTO userDTO) {
// 参数已经通过校验
return Result.success(userService.createUser(userDTO));
}
/**
* 查询用户(参数校验)
*/
@GetMapping("/{phone}")
public Result getUserByPhone(@Phone(message = "手机号格式错误") @PathVariable String phone) {
return Result.success(userService.getUserByPhone(phone));
}
/**
* 查询用户(RequestParam校验)
*/
@GetMapping("/query")
public Result queryUsers(
@Phone(message = "手机号格式错误")
@RequestParam(required = false) String phone,
@MoneyRange(min = 0, max = 5000, message = "余额范围错误")
@RequestParam(required = false) BigDecimal balance) {
return Result.success(userService.queryUsers(phone, balance));
}
}
3 Service层使用
import org.springframework.validation.annotation.Validated;
import javax.validation.Valid;
@Service
@Validated
public class UserServiceImpl implements UserService {
@Override
public User createUser(@Valid @RequestBody UserDTO userDTO) {
// 校验通过后执行业务逻辑
return userMapper.insert(userDTO);
}
/**
* 更新用户信息
*/
@Override
public User updateUser(@Valid UserDTO userDTO) {
// 可以指定分组进行部分校验
return userMapper.update(userDTO);
}
}
全局异常处理
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.BindException;
import org.springframework.validation.FieldError;
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.HashMap;
import java.util.Map;
import java.util.stream.Collectors;
@RestControllerAdvice
public class GlobalExceptionHandler {
/**
* 处理请求体校验异常
*/
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<Map<String, Object>> handleMethodArgumentNotValid(
MethodArgumentNotValidException ex) {
Map<String, String> errors = ex.getBindingResult()
.getFieldErrors()
.stream()
.collect(Collectors.toMap(
FieldError::getField,
FieldError::getDefaultMessage,
(existing, replacement) -> existing
));
Map<String, Object> response = new HashMap<>();
response.put("code", 400);
response.put("message", "请求参数校验失败");
response.put("errors", errors);
return ResponseEntity.badRequest().body(response);
}
/**
* 处理参数校验异常(@RequestParam等)
*/
@ExceptionHandler(ConstraintViolationException.class)
public ResponseEntity<Map<String, Object>> handleConstraintViolation(
ConstraintViolationException ex) {
String message = ex.getConstraintViolations()
.stream()
.map(ConstraintViolation::getMessage)
.collect(Collectors.joining("; "));
Map<String, Object> response = new HashMap<>();
response.put("code", 400);
response.put("message", message);
return ResponseEntity.badRequest().body(response);
}
/**
* 处理表单绑定异常
*/
@ExceptionHandler(BindException.class)
public ResponseEntity<Map<String, Object>> handleBindException(BindException ex) {
Map<String, String> errors = ex.getBindingResult()
.getFieldErrors()
.stream()
.collect(Collectors.toMap(
FieldError::getField,
FieldError::getDefaultMessage
));
Map<String, Object> response = new HashMap<>();
response.put("code", 400);
response.put("message", "参数绑定失败");
response.put("errors", errors);
return ResponseEntity.badRequest().body(response);
}
}
测试用例
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import javax.validation.ConstraintViolation;
import javax.validation.Validator;
import java.math.BigDecimal;
import java.util.Set;
@RunWith(SpringRunner.class)
@SpringBootTest
public class ValidationTest {
@Autowired
private Validator validator;
@Test
public void testPhoneValidation() {
UserDTO user = new UserDTO();
user.setPhone("13800138000"); // 有效手机号
Set<ConstraintViolation<UserDTO>> violations = validator.validate(user);
assert violation.isEmpty();
}
@Test
public void testInvalidPhone() {
UserDTO user = new UserDTO();
user.setPhone("12345"); // 无效手机号
Set<ConstraintViolation<UserDTO>> violations = validator.validate(user);
assert !violations.isEmpty();
assert violations.iterator().next().getMessage().equals("手机号格式不正确");
}
@Test
public void testPasswordValidation() {
UserDTO user = new UserDTO();
user.setPassword("Password123"); // 符合要求
Set<ConstraintViolation<UserDTO>> violations = validator.validate(user);
assert violation.isEmpty();
}
@Test
public void testMoneyRange() {
UserDTO user = new UserDTO();
user.setBalance(new BigDecimal("150.00")); // 在100-10000之间
Set<ConstraintViolation<UserDTO>> violations = validator.validate(user);
assert violation.isEmpty();
user.setBalance(new BigDecimal("50.00")); // 超出范围
violations = validator.validate(user);
assert !violations.isEmpty();
}
}
注意事项
// 1. 需要导入相关依赖
// Maven
/*
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
*/
// 2. 启动类需要添加@Validated注解
@SpringBootApplication
@Validated
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
// 3. 分组校验
public interface CreateGroup {}
public interface UpdateGroup {}
public class UserDTO {
@Null(groups = CreateGroup.class, message = "创建时ID必须为空")
@NotNull(groups = UpdateGroup.class, message = "更新时ID不能为空")
private Long id;
@NotBlank(groups = CreateGroup.class, message = "创建时用户名不能为空")
private String username;
}
这个案例涵盖了自定义校验注解的完整实现,从简单的字段校验到复杂的业务校验,配置了全局异常处理,并包含测试用例,你可以根据实际业务需求进行修改和扩展。
版权声明:NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播