Java注解(Annotation
),就是放在Java源码的类、方法、字段、参数前的一种特殊“注释”。及注解可以放在类上、方法上、属性上、方法参数上。
@Resource("hello")
public class Hello {
@Inject
int n;
@PostConstruct
public void hello(@Param String name) {
System.out.println(name);
}
@Override
public String toString() {
return "Hello";
}
}
注解有时可以被编译器打包进入class文件,因此,注解是一种用作标注的“元数据”。
1. 注解分类
Java的注解可以分为三类:
- 第一类是由编译器使用的注解,例如:
@Override
:让编译器检查该方法是否正确地实现了覆写;
@SuppressWarnings
:告诉编译器忽略此处代码产生的警告。
这类注解不会被编译进入.class文件,它们在编译后就被编译器扔掉了。 - 第二类是由工具处理.class文件使用的注解,比如有些工具会在加载class的时候,对class做动态修改,实现一些特殊的功能。这类注解会被编译进入.class文件,但加载结束后并不会存在于内存中。这类注解只被一些底层库使用,一般我们不必自己处理。
- 第三类是在程序运行期能够读取的注解,它们在加载后一直存在于JVM中,这也是最常用的注解。例如,一个配置了
@PostConstruct
的方法会在调用构造方法后自动被调用(这是Java代码读取该注解实现的功能,JVM并不会识别该注解)。
2. 注解的配置参数
注解中可以配置参数,参数若不写,会取默认值,因此注解的配置参数可以是以下类型
- 所有基本类型;
- String;
- 枚举类型;
- 基本类型、String、Class以及枚举的数组。
这些类型都有自己的默认值。
大部分注解都有一个名为value
的配置参数,可以不写,也可以加上:
@ApiOperation(value = "测试")
@PostMapping("/test")
public void test() {
}
如上,@ApiOperation
带上了value
,@PostMapping
中要表示的也是value='/test'
,只不过只写了值。
如果参数名称是value,且只有一个参数,那么可以省略参数名称。
3. 元注解
有一些注解可以修饰其他注解,这些注解就称为元注解(meta annotation)。Java标准库已经定义了一些元注解,我们只需要使用元注解,通常不需要自己去编写元注解。
3.1 @Target
最常用的元注解是@Target
。使用@Target
可以定义Annotation
能够被应用于源码的哪些位置:
- 类或接口:
ElementType.TYPE
; - 字段:
ElementType.FIELD
; - 方法:
ElementType.METHOD
; - 构造方法:
ElementType.CONSTRUCTOR
; - 方法参数:
ElementType.PARAMETER
。
@Target
定义的value是ElementType[]数组,只有一个元素时,可以省略数组的写法。
如下注解,@Target
使用
@Target(ElementType.METHOD)
public @interface Report {
int type() default 0;
String level() default "info";
String value() default "";
}
@Target({ ElementType.METHOD, ElementType.FIELD})
public @interface Report {
}
3.2 @Retention
另一个重要的元注解@Retention
定义了Annotation
的生命周期:
- 仅编译期:
RetentionPolicy.SOURCE
; - 仅class文件:
RetentionPolicy.CLASS
; - 运行期:
RetentionPolicy.RUNTIME
。
如果@Retention
不存在,则该注解(Annotation
)默认为CLASS
。因为通常我们自定义的Annotation
都是RUNTIME
,所以,务必要加上@Retention(RetentionPolicy.RUNTIME)
这个元注解:
@Target({ ElementType.METHOD, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Report {
}
3.3 @Repeatable
使用@Repeatable
这个元注解可以定义Annotation
是否可重复。这个注解应用不是特别广泛。
@Repeatable(Reports.class)
@Target(ElementType.TYPE)
public @interface Report {
int type() default 0;
String level() default "info";
String value() default "";
}
@Target(ElementType.TYPE)
public @interface Reports {
Report[] value();
}
经过@Repeatable
修饰后,在某个类型声明处,就可以添加多个@Report
注解:
@Report(type=1, level="debug")
@Report(type=2, level="warning")
public class Hello {
}
3.4 @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(type=1)
public class Person {
}
public class Student extends Person {
}
如上Student
类也加了该注解。
4. 自定义注解
Java中使用@interface语法来定义注解(Annotation
)。注解的参数类似无参数方法,可以用default
设定一个默认值(强烈推荐)。最常用的参数应当命名为value
。
- 第一步,用
@interface
定义注解:
public @interface Report {
}
- 第二步,添加参数、默认值:
public @interface Report {
int type() default 0;
String level() default "info";
String value() default "";
}
把最常用的参数定义为value()
,推荐所有参数都尽量设置默认值。
- 第三步,用元注解配置注解:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Report {
int type() default 0;
String level() default "info";
String value() default "";
}
其中,必须设置@Target
和@Retention
,@Retention
一般设置为RUNTIME
,因为我们自定义的注解通常要求在运行期读取。一般情况下,不必写@Inherited
和@Repeatable
。
- 第四步,让注解生效
注解本身对代码逻辑没有任何影响。要想注解起作用,就需要有特定的逻辑。
我们可以自定义AOP,在AOP中拦截注解,然后处理逻辑。AOP中拦截注解参见下面的第5点内容。
我们来看一个@Range注解,我们希望用它来定义一个String字段的规则:字段长度满足@Range的参数定义:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Range {
int min() default 0;
int max() default 255;
}
在某个JavaBean中,我们可以使用该注解:
public class Person {
@Range(min=1, max=20)
public String name;
@Range(max=10)
public String city;
}
但是,定义了注解,本身对程序逻辑没有任何影响。我们必须自己编写代码来使用注解。这里,我们编写一个Person实例的检查方法,它可以检查Person实例的String字段长度是否满足@Range的定义:
void check(Person person) throws IllegalArgumentException, ReflectiveOperationException {
// 遍历所有Field:
for (Field field : person.getClass().getFields()) {
// 获取Field定义的@Range:
Range range = field.getAnnotation(Range.class);
// 如果@Range存在:
if (range != null) {
// 获取Field的值:
Object value = field.get(person);
// 如果值是String:
if (value instanceof String) {
String s = (String) value;
// 判断值是否满足@Range的min/max:
if (s.length() < range.min() || s.length() > range.max()) {
throw new IllegalArgumentException("Invalid field: " + field.getName());
}
}
}
}
}
这样一来,我们通过@Range
注解,配合check()
方法,就可以完成Person实例的检查。注意检查逻辑完全是我们自己编写的,JVM不会自动给注解添加任何额外的逻辑。
5. 注解内容获取
注解的生命周期由@Retention
的配置:
-
SOURCE
类型的注解主要由编译器使用,在编译期就被丢掉了,因此我们一般只使用,不编写 -
CLASS
类型的注解主要由底层工具库使用,仅保存在class文件中,它们不会被加载进JVM,涉及到class的加载,一般我们很少用到 -
RUNTIME
类型的注解不但要使用,还经常需要编写,会被加载进JVM,并且在运行期可以被程序读取
因此,我们只讨论如何读取RUNTIME
类型的注解。
因为注解定义后也是一种class,所有的注解都继承自java.lang.annotation.Annotation
,因此,读取注解,需要使用反射API。
5.1 判断某个注解是否存在于Class、Field、Method或Constructor:
Class.isAnnotationPresent(Class)
Field.isAnnotationPresent(Class)
Method.isAnnotationPresent(Class)
Constructor.isAnnotationPresent(Class)
5.2 使用反射API读取注解的内容:
Class.getAnnotation(Class)
Field.getAnnotation(Class)
Method.getAnnotation(Class)
Constructor.getAnnotation(Class)
5.3 读取swagger
注解
在AOP中,以读取swagger
注解为例:
// 1. 读取某个类上@Api注解信息
Class<?> targetClass = joinPoint.getTarget().getClass();
if (targetClass.isAnnotationPresent(Api.class)) { // 判断是否存在@Api注解
Api swaggerApi = targetClass.getAnnotation(Api.class);
System.out.println(swaggerApi.tags()[0]);
}
// 获取@ApiOperation注解信息
Signature sig = joinPoint.getSignature();
MethodSignature msig = (MethodSignature)sig;
Method md = targetClass.getMethod(msig.getName(), msig.getParameterTypes());
if (md.isAnnotationPresent(ApiOperation.class)) { // 判断是否存在@ApiOperation注解
ApiOperation swaggerApiOperation = md.getAnnotation(ApiOperation.class);
System.out.println(swaggerApiOperation.value());
}
文章参考:https://www.liaoxuefeng.com/wiki/1252599548343744/1265102803921888
网友评论