Java 自定义注解
·
写作时间:2025-08-19
本章介绍实现自定义注解校验 springboot 项目接收前端传入参数。
💡 什么是注解:
Java 注解(Annotation)是一种特殊的标记性代码,它不直接影响程序的执行逻辑,但可以为代码提供元数据(描述数据的数据),供编译器、工具或运行时环境使用。
主要作用
- 编译期检查与约束
- 简化代码与替代配置文件
- 运行时动态处理,如生成动态代码
- 作为特定标记,用于告诉编译器一些信息
元注解
java内置的注解,标明该注解的使用范围、生命周期、生效范围等。
@Retention指定被修饰的注解的生命周期(即注解保留到哪个阶段)
- 取值:
-
SOURCE: 注解仅在源代码阶段存在,编译时会被丢弃(不会进入 class 文件)。CLASS: 注解在编译时保留到 class 文件中,但 JVM 运行时不会加载(默认值)。RUNTIME: 注解在运行时仍然存在,可通过反射获取。
@Retention(RetentionPolicy.RUNTIME) // 注解在运行时可见
public @interface MyAnnotation {}
@Targe:指定被修饰的注解可以应用在哪些程序元素上(如类、方法、字段等)。
- 取值:
-
TYPE:类、接口、枚举、注解。METHOD:方法。FIELD:字段(成员变量、枚举常量)。PARAMETER:方法参数。CONSTRUCTOR:构造方法。LOCAL_VARIABLE:局部变量。ANNOTATION_TYPE:注解类型(只能修饰其他注解)。PACKAGE:包。TYPE_PARAMETER:类型参数(如泛型中的<T>)。TYPE_USE:任何类型的使用场景(如声明变量、强制类型转换等)。
@Target({ElementType.METHOD, ElementType.FIELD}) // 注解可用于方法和字段
public @interface MyAnnotation {}
@Documented:指定被修饰的注解会被 Javadoc 工具提取到文档中(默认情况下,注解不会出现在文档中)。
@Documented // 该注解会出现在 Javadoc 文档中
public @interface MyAnnotation {}
@Inherited:指定被修饰的注解具有继承性。即如果父类使用了该注解,子类若未显式标注其他注解,则会继承父类的该注解(仅对类注解有效,对方法、字段等无效)。
@Inherited // 允许子类继承该注解
public @interface MyAnnotation {}
@MyAnnotation
class Parent {}
class Child extends Parent {} // Child 会继承 Parent 的 @MyAnnotation 注解
Repeatablle: 指定被修饰的注解是可重复的,即同一个程序元素上可以多次使用该注解(Java 8 新增)。
// 定义容器注解(存储重复的 @MyAnnotation)
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotations {
MyAnnotation[] value(); // 数组类型,存储重复的注解
}
// 标记 @MyAnnotation 为可重复(关联容器注解)
@Repeatable(MyAnnotations.class)
public @interface MyAnnotation {
String value();
}
// 重复使用注解
@MyAnnotation("value1")
@MyAnnotation("value2")
class MyClass {}
@Native:标记注解的成员变量是一个与 native 方法相关的常量(Java 8 新增)。通常用于生成 JNI 头文件时识别常量。
public @interface MyAnnotation {
@Native int value() default 0; // 标记为与 native 方法相关的常量
}
在springboot中自定义注解
- 创建注解
// 参数校验注解: 用户传入参数中 手机号码 和 邮箱 必须有一个不为空
// 注解作用于类(因为要校验多个字段)
@Target({ElementType.TYPE})
// 注解保留到运行时
@Retention(RetentionPolicy.RUNTIME)
// 标记为约束注解,指定校验逻辑的实现类
@Constraint(validatedBy = OneOfNotNullValidator.class)
public @interface OneOfNotNull {
// 错误提示信息
String message() default "phone和email必须至少填写一个";
// 分组校验(默认空)
Class<?>[] groups() default {};
// 负载信息(默认空)
Class<? extends Payload>[] payload() default {};
// 必须填写的字段名称(支持多个)
String[] fields();
}
- 注解逻辑实现
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import java.lang.reflect.Field;
// 泛型:第一个参数是自定义注解,第二个参数是要校验的类(RegisterDTO)
public class OneOfNotNullValidator implements ConstraintValidator<OneOfNotNull, Object> {
// 存储需要校验的字段名称(如 {"phone", "email"})
private String[] fields;
@Override
public void initialize(OneOfNotNull constraintAnnotation) {
// 初始化时获取注解中指定的字段名称
this.fields = constraintAnnotation.fields();
}
@Override
public boolean isValid(Object value, ConstraintValidatorContext context) {
// value 是被校验的对象(如 RegisterDTO 实例)
if (value == null) {
return false; // 对象为null时直接校验失败
}
try {
// 遍历所有需要校验的字段
for (String fieldName : fields) {
// 通过反射获取字段
Field field = value.getClass().getDeclaredField(fieldName);
field.setAccessible(true); // 允许访问私有字段
// 获取字段值
Object fieldValue = field.get(value);
// 如果有一个字段不为空,校验通过
if (fieldValue != null && !fieldValue.toString().trim().isEmpty()) {
return true;
}
}
} catch (NoSuchFieldException | IllegalAccessException e) {
// 反射异常(如字段不存在),视为校验失败
return false;
}
// 所有字段都为空,校验失败
return false;
}
}
- 注解使用
@Data
@OneOfNotNull(fields = {"phone", "email"}, message = "手机号和邮箱必须存在一个不为空")
public class RegisterDTO {
/**
* 手机号码
*/
@Pattern(regexp = "^1[3-9]\\d{9}$", message = "手机号码格式不正确")
private String phone;
/**
* 用户邮箱
*/
@Email(message = "邮箱格式不正确")
private String email;
}
- 未知代码解释
- 注解
@Constraint的作用:标记自定义校验注解的元注解,用于告诉 Bean Validation 框架:这个注解是一个校验注解,并且指定了该注解的校验逻辑由哪个类实现。 - Payload 接口作用:这是一个标记接口(空接口),用于在校验注解中传递额外的元数据(如校验的严重程度、业务标识等),本身没有任何方法。
// 1. 对校验约束进行分类或标记(例如区分 “警告级”“错误级” 校验)。
// 2. 校验失败时,可通过 ConstraintViolation.getConstraintDescriptor().getPayload()
// 获取 payload 信息,用于后续处理(如日志分级、错误页面跳转等)。
// 使用示例:
// 定义一个表示“严重错误”的 payload 类
public class Severity implements Payload {
public static class Error implements Severity {} // 错误级
public static class Warning implements Severity {} // 警告级
}
// 在自定义注解中使用
public @interface Phone {
// 允许指定 payload(默认空)
Class<? extends Payload>[] payload() default {};
}
// 使用注解时指定 payload
public class User {
@Phone(payload = Severity.Error.class) // 标记为错误级校验
private String phone;
}
initialize方法:initialize方法是ConstraintValidator接口中的方法,用于初始化校验器,在校验器实例创建后、执行校验逻辑前调用。
// 1. 获取自定义注解中的属性值(如正则表达式、错误消息等),并保存到校验器中,供后续校验使用。
// 2. 执行一些初始化操作(如编译正则表达式、加载配置等)。
// 使用示例:
public class PhoneValidator implements ConstraintValidator<Phone, String> {
private String regexp; // 存储注解中的正则表达式
@Override
public void initialize(Phone constraintAnnotation) {
// 从注解中获取 regexp 属性值并初始化
this.regexp = constraintAnnotation.regexp();
}
@Override
public boolean isValid(String value, ConstraintValidatorContext context) {
// 使用 initialize 中获取的 regexp 进行校验
return value.matches(regexp);
}
}
// 这里 initialize() 从 @Phone 注解中获取正则表达式,供 isValid() 方法使用。
isvalid方法:isvalid方法是ConstraintValidator接口中的核心方法,用于 执行实际的校验逻辑,返回true表示校验通过,false表示校验失败。
// 1. value:被校验的字段值(如手机号字符串、实体对象等)。
// 2. context:校验上下文,可用于修改错误消息、禁用默认消息等。
// 使用示例:
@Override
public boolean isValid(String phone, ConstraintValidatorContext context) {
// 校验逻辑:如果手机号为空,或不匹配正则,则返回 false
if (phone == null || phone.isEmpty()) {
return false;
}
return phone.matches(regexp); // 使用 initialize 中初始化的 regexp
}
更多推荐


所有评论(0)