从 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;
@Check
public 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` 属性存储校验失败的字段和失败原因
```java
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import java.util.List;
@ToString
public class ValidationResult {
@Getter
@Setter
private boolean isValid;
@Getter
@Setter
private List<ValidationError> errors;
}
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import java.lang.reflect.Field;
@ToString
public class ValidationError {
@Getter
@Setter
private Field field;
@Getter
@Setter
private 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
@Slf4j
public 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)])