Java注解
什么是注解👻
注解是放在Java源码的类、方法、字段、参数前的一种特殊“注释。
附加在代码中的一些元信息,用于一些工具在编译、运行时进行解析和使用,起到说明、配置的功能。
注解不会也不能影响代码的实际逻辑,仅仅起到辅助性的作用。
注释会被编译器直接忽略,注解则可以被编译器打包进入class文件,因此,注解是一种用作标注的“元数据”
注解有什么用?💀
从JVM的角度看,注解本身对代码逻辑没有任何影响,如何使用注解完全由工具决定
1、生成文档。这是最常见的,也是java 最早提供的注解。常用的有 @param @return 等
2、跟踪代码依赖性,实现替代配置文件功能。
3、在编译时进行格式检查。如 @override 放在方法前,如果你这个方法并不是覆盖了超类方法,则编译时就能检查出。
注解的原理是什么?👻
注解本质是一个继承了 Annotation 的特殊接口,其具体实现类是 Java运行时生成的动态代理类。
通过反射获取注解时,返回的是Java运行时生成的动态代理对象 $Proxy1。
通过代理对象 调用 自定义注解的方法,会最终调用 AnnotationInvocationHandler 的 invoke() 方法。
该方法会从memberValues 这个 Map 中索引出对应的值。
注解有哪几类?😺
**一、编译器使用的注解—— RetentionPolicy.SOURCE **
-
不会被编译进入
.class文件中 -
@Override:让编译器检查该方法是否正确地实现了覆写; -
@SuppressWarnings:告诉编译器忽略此处代码产生的警告。
二、由工具处理.class文件使用的注解—— RetentionPolicy.CLASS
- 会被编译进入
.class文件,但加载结束后并不会存在于内存中
三、在程序运行期能够读取的注解—— RetentionPolicy.RUNTIME
- 在加载后一直存在于JVM中
定义注解😺
如何定义注解
Java语言使用 @interface 语法来定义注解(Annotation)
public @interface Report {
int type() default 0;
String level() default "info";
String value() default "";
}
/*
可以用default设定一个默认值(强烈推荐)。最常用的参数应当命名为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;
}
总结:
- 用
@interface定义注解 - 添加参数、默认值。
- 参数成员只能用public 或默认(default) 这两个访问权修饰
- 参数类型只能用基本类型
- 所有基本类型;
- String;
- 枚举类型;
- 基本类型、String、Class以及枚举的数组
- 用元注解配置注解
- 必须设置
@Target和@Retention,@Retention一般设置为RUNTIME - 一般情况下,不必写
@Inherited和@Repeatable
- 必须设置
元注解
修饰注解的注解:元注解(meta annotation)
@Target
使用 @Target 可以定义 Annotation 能够被应用于源码的哪些位置:
- 类或接口:
ElementType.TYPE - 成员变量、对象、属性(包括enum实例):
ElementType.FIELD - 方法:
ElementType.METHOD - 构造方法:
ElementType.CONSTRUCTOR - 方法参数:
ElementType.PARAMETER - 局部变量:
ElementType.LOCAL_VARIABLE - 包:
ElementType.PACKAGE
//定义注解@Report可用在方法上,我们必须添加一个@Target(ElementType.METHOD)
@Target(ElementType.METHOD)
public @interface Report {
int type() default 0;
String level() default "info";
String value() default "";
}
//定义注解@Report可用在方法或字段上,可以把@Target注解参数变为数组{ ElementType.METHOD, ElementType.FIELD }
@Target({
ElementType.METHOD,
ElementType.FIELD
})
public @interface Report {
.....
}
@Target 定义的 value 是 ElementType[] 数组,只有一个元素时,可以省略数组的写法
@Retention
元注解@Retention定义了Annotation的生命周期:
- 仅编译期:
RetentionPolicy.SOURCE,不会被编译到 .class 文件中 - 仅class文件:
RetentionPolicy.CLASS,加载结束后不会存在内存中 - 运行期:
RetentionPolicy.RUNTIME,加载后一直存在内存中
@Retention(RetentionPolicy.RUNTIME)
public @interface Report {
int type() default 0;
String level() default "info";
String value() default "";
}
注意:
-
如果
@Retention不存在,则该Annotation默认为CLASS -
通常我们自定义的
Annotation都是RUNTIME,所以,务必要加上@Retention(RetentionPolicy.RUNTIME)这个元注解,便于在运行期间读取该Annotation
@Repeatable
@Repeatable元注解可以定义 Annotation 是否可重复
@Repeatable(Reports.class) //Reports的类,注解是可重复的
@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 {
}
@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 {
}
@Documented
一个简单的 Annotations 标记注解,表示是否将注解信息添加在java 文档中
如何处理注解👻
Java的注解本身对代码逻辑没有任何影响。根据@Retention的配置:
SOURCE类型的注解在编译期就被丢掉了;主要由编译器使用,只使用不编写CLASS类型的注解仅保存在class文件中,它们不会被加载进JVM;由底层工具库使用,涉及到class的加载,一般很少用到RUNTIME类型的注解会被加载进JVM,并且在运行期可以被程序读取。不但要使用,还经常需要编写
注解定义后也是一种
class,所有的注解都继承自java.lang.annotation.Annotation读取注解需要反射API。
-
判断某个注解是否存在于
Class、Field、Method或Constructor:Class.isAnnotationPresent(Class)Field.isAnnotationPresent(Class)Method.isAnnotationPresent(Class)Constructor.isAnnotationPresent(Class)
-
使用反射API读取Annotation:
-
第一种方法:先判断
Annotation是否存在,如果存在,就直接读取第二种方法:直接读取
Annotation,如果Annotation不存在,将返回null -
Class.getAnnotation(Class) -
Field.getAnnotation(Class) -
Method.getAnnotation(Class) -
Constructor.getAnnotation(Class)
-
-
读取方法参数的
Annotation
一次获取方法参数的所有注解就必须用一个二维数组来表示
public void hello(@NotNull @Range(max=5) String name, @NotNull String prefix) {
}
// 获取Method实例:
Method m = ...
// 获取所有参数的Annotation:
Annotation[][] annos = m.getParameterAnnotations();
// 第一个参数(索引为0)的所有Annotation:
Annotation[] annosOfName = annos[0];
for (Annotation anno : annosOfName) {
if (anno instanceof Range) { // @Range注解
Range r = (Range) anno;
}
if (anno instanceof NotNull) { // @NotNull注解
NotNull n = (NotNull) anno;
}
}
如何使用注解💀
注解如何使用,完全由程序自己决定
JUnit是一个测试框架,它会自动运行所有标记为@Test的方法
例子:
/*
这是一个@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;
}
//检查逻辑完全是我们自己编写的,JVM不会自动给注解添加任何额外的逻辑
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());
}
}
}
}
}
参考资料
[1] http://www.cnblogs.com/peida/archive/2013/04/26/3038503.html
[2] http://www.cnblogs.com/whoislcj/p/5671622.html
[3] http://blog.csdn.net/lylwo317/article/details/52163304
[4] 注解Annotation实现原理与自定义注解例子
Java注解详解:定义、使用与处理
本文详细介绍了Java注解的定义、作用、原理及分类,包括@Retention和@Target元注解的解释。注解用于提供元数据,不改变代码逻辑,但在编译或运行时可被工具处理。讲解了如何定义和处理注解,以及注解的运行时读取。同时,展示了注解在实际应用中的例子,如用于验证字段长度。
470

被折叠的 条评论
为什么被折叠?



