从 JVM 的角度看,注解本身对代码逻辑没有任何影响,如何使用注解完全由工具决定。
Java 的注解分为三类
- 由编译器使用的注解,例如
@Override:让编译器检查该方法是否正确的实现了覆写@SuppressWarnings:告诉编译器忽略此处代码产生的警告
- 由工具处理
.class文件使用的注解,比如有些工具在加载class的时候,对class做动态修改实现一些特殊的功能。使用频率不高 在程序运行期能够读取的注解,它们在加载后一直存在于 JVM 中。这一种是最常用到的注解
定义注解
Java 语言使用
@interface语法来定义注解,它的格式如下:public @interface Report {int type() default 0;String level() default "info";String value() default "";}
注解的参数类似无参数方法,可以用
default设定一个默认值(强烈推荐)。最常用的参数应当命名为value。定义注解时,如果参数名称是value,且只有一个参数,那么使用时可以忽略参数名称。通常情况下,核心参数使用value命名。public class Hello {@Check(min=0, max=100, value=55)public int n;@Check(value=99)public int p;@Check(99) // @Check(value=99)public int x;@Checkpublic int y;}
@Check就是一个注解。第一个@Check(min=0, max=100, value=55)明确定义了三个参数,第二个@Check(value=99)只定义了一个value参数,它实际上和@Check(99)是完全一样的。最后一个@Check表示所有参数都使用默认值。元注解
用于修饰其他注解的注解被称为元注解。Java 标准库已经定义了很多元注解,通常不会自己编写元注解
@Target
@Target最常用的元注解。使用@Target定义Annotation能够被应用于源码的哪些位置:
- 类或接口:
ElementType.TYPE - 字段:
ElementType.FIELD - 方法:
ElementType.METHOD - 构造方法:
ElementType.CONSTRUCTOR - 方法参数:
ElementType.PARAMETER定义注解可用在多处位置是,可以把
@Target注解参数变为数组。例如:{ ElementType.METHOD,ElementType.FIELD }表示注解可用在方法或字段上
@Retention
@Retention 定义 Annotation 的生命周期
- 仅编译期:
RetentionPolicy.SOURCE - 仅class文件:
RetentionPolicy.CLASS - 运行期:
RetentionPolicy.RUNTIME如果 @Retention 不存在,则该 Annotation 默认为 CLASS。实际开发过程中,定义的 Annotation 生命周期都是 RUNTIME,所以务必要加上
@Retention(RetentionPolicy.RUNTIME)这个元注解
@Repeatable
@Repeatable 定义 Annotation 是否可重复声明。这个注解用的比较少
@Inherited
@Inherited 定义子类是否可继承父类定义的 Annotation。@Inherited 仅针对 @Target(ElementType.TYPE) 类型的 Annotation 有效,并且仅针对 class 的继承,对 interface 的继承无效
@Inherited@Target(ElementType.TYPE)public @interface Report {int type() default 0;String level() default "info";String value() default "";}
在使用的时候,如果一个类用到了 @Report:
@Report(type=1)public class Person {}
则它的子类默认也定义了该注解:
public class Student extends Person {}
处理注解
Java 的注解本身对代码逻辑没有影响。其生命周期根据 @Retention 的配置决定。因为注解定义后也是一种 class,所有的注解都继承自 java.lang.annotation.Annotation,所以读取注解需要用到反射 API
Java 反射 API 提供的用于读取 Annotation 的方法包括:
- 判断某个注解是否存在于
Class、Field、Method或Constructor:Class.isAnnotationPresent(Class)Field.isAnnotationPresent(Class)Method.isAnnotationPresent(Class)Constructor.isAnnotationPresent(Class)
- 读取注解
Annotation,如果Annotation不存在,将返回null
- 定义注解
@Range,针对数字型校验最大和最小值、字符串型校验最大和最小长度 ```java import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target;
@Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) public @interface Range { int min() default 0; int max() default 255; }
2. 定义用于接收校验结果的输出模型类 `ValidationResult` 和 `ValidationError`1. `isValid` 属性表示 `JavaBean` 是否通过校验1. `errors` 属性存储校验失败的字段和失败原因```javaimport lombok.Getter;import lombok.Setter;import lombok.ToString;import java.util.List;@ToStringpublic class ValidationResult {@Getter@Setterprivate boolean isValid;@Getter@Setterprivate List<ValidationError> errors;}import lombok.Getter;import lombok.Setter;import lombok.ToString;import java.lang.reflect.Field;@ToStringpublic class ValidationError {@Getter@Setterprivate Field field;@Getter@Setterprivate String message;}
定义字段校验工具类
FieldValidation,工作流程大致分为- 读取 JavaBean 实例类型
- 读取 JavaBean 中字段
遍历字段是否声明 @Range 注解,如果定义则根据规则校验字段内容是否合法
public abstract class FieldValidation {/*** 根据特性校验字段值** @param instance 对象示例* @return boolean*/public static ValidationResult check(Object instance) throws IllegalAccessException {final ValidationResult result = new ValidationResult();final ArrayList<ValidationError> errors = CollUtil.newArrayList();final Class<?> instanceClass = instance.getClass();final Field[] declaredFields = instanceClass.getDeclaredFields();if (ArrayUtil.isEmpty(declaredFields)) {result.setValid(true);return result;}for (Field field : declaredFields) {if (!field.canAccess(instance)) {field.setAccessible(true);}if (!field.isAnnotationPresent(Range.class)) {continue;}final Range range = field.getAnnotation(Range.class);final int min = range.min();final int max = range.max();if (field.getType() == int.class) {final var value = (int) field.get(instance);if (value < min) {final ValidationError error = new ValidationError();error.setField(field);error.setMessage(String.format("value = %s,can't less than %s", value, min));errors.add(error);}if (value > max) {final ValidationError error = new ValidationError();error.setField(field);error.setMessage(String.format("value = %s,can't greater than %s", value, max));errors.add(error);}continue;}if (field.getType() == String.class) {final var value = (String) field.get(instance);if (StrUtil.isEmpty(value)) {final ValidationError error = new ValidationError();error.setField(field);error.setMessage("value can't be null");errors.add(error);continue;}final int length = value.length();if (length < min) {final ValidationError error = new ValidationError();error.setField(field);error.setMessage(String.format("length can't greater than %s,but %s", max, length));errors.add(error);}if (length > max) {final ValidationError error = new ValidationError();error.setField(field);error.setMessage(String.format("length can't greater than %s,but %s", max, length));errors.add(error);}}}result.setErrors(errors);result.setValid(errors.isEmpty());return result;}}
使用
@Range示例 DemoBean ```java import lombok.Setter;
public class DemoBean {
@Setter@Range(min = 1, max = 200)private int age;@Setter()@Range(min = 1, max = 3)private String name;
}
5. 调用示例```java@Slf4jpublic class Main {public static void main(String[] args) {final DemoBean demoBean = new DemoBean();demoBean.setAge(8989);demoBean.setName("xcxcxcxc");ValidationResult result = new ValidationResult();try {result = FieldValidation.check(demoBean);} catch (IllegalAccessException e) {e.printStackTrace();}log.info("Validation return {}",result.toString());}}// 打印结果22:17:29.127 [main] INFO com.dev.Main - Validation return ValidationResult(isValid=false, errors=[ValidationError(field=private int com.dev.annotation.DemoBean.age, message=value = 8989,can't greater than 200), ValidationError(field=private java.lang.String com.dev.annotation.DemoBean.name, message=length can't greater than 3,but 8)])
