一、注解的概念
Annotation(注解)是插入代码中的元数据(元数据从metadata一词译来,就是“描述数据的数据”的意思),在JDK5.0及以后版本引入。它可以在编译期使用预编译工具进行处理, 也可以在运行期使用 Java 反射机制进行处理,用于创建文档,跟踪代码中的依赖性,甚至执行基本编译时检查。因为本质上,Annotion是一种特殊的接口,程序可以通过反射来获取指定程序元素的Annotion对象,然后通过Annotion对象来获取注解里面的元数据。
二、注解的本质
2.1 通过示例看清本质
注解本质上是一种继承自接口`java.lang.annotation.Annotation`的特殊接口。
一个自定义注解的示例:
@Documented @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface PersonAnno { String name() default ""; int age() default 0; }
本质上注解会被编译为继承了(Annotation接口)的接口,反编译上面的PersonAnno.class可以看到代码如下:
2.2 注解源码
java.lang.annotation.Annotation类源码如下:
/** * The common interface extended by all annotation types. Note that an * interface that manually extends this one does <i>not</i> define * an annotation type. Also note that this interface does not itself * define an annotation type. * * @author Josh Bloch * @since 1.5 */ /** * 首先声明英语不是很好,大致意思是:这是一个基础接口,所有的注解类型都继承与它。但是需要注意的是 * (1)不需要手动指明一个注解类型是继承与它的(意思是自动继承) * (2)它本身不是注解类型 */ public interface Annotation { /** * 这三个方法就不用多说了吧! */ boolean equals(Object obj); int hashCode(); String toString(); /** * Returns the annotation type of this annotation. */ /** * 返回注解的class */ Class<? extends Annotation> annotationType(); }
2.3 注解本质的总结
(1)注解实质上会被编译器编译为接口,并且继承java.lang.annotation.Annotation接口。
(2)注解的成员变量会被编译器编译为同名的抽象方法。
(3)根据Java的class文件规范,class文件中会在程序元素的属性位置记录注解信息。例 如,RuntimeVisibleAnnotations属性位置,记录修饰该类的注解有哪些;flags属性位置,记录该类是不是注解;在方法的 AnnotationDefault属性位置,记录注解的成员变量默认值是多少。
三、注解的作用
Annotation的作用大致可分为三类:
(1)编写文档:通过代码里标识的元数据生成文档;
(2)代码分析:通过代码里标识的元数据对代码进行分析;
(3)编译检查:通过代码里标识的元数据让编译器能实现基本的编译检查;
综上所述可知,Annotation主要用于提升软件的质量和提高软件的生产效率。
四、注解的分类
4.1 注解分类
4.1.1 根据成员个数分类
(1)标记注解:没有定义成员的Annotation类型,自身代表某类信息,如:@Override
(2)单成员注解:只定义了一个成员,比如@SuppressWarnings 定义了一个成员String[] value,使用value={…}大括号来声明数组值,一般也可以省略“value=”
(3)多成员注解:定义了多个成员,使用时以name=value对分别提供数据
4.1.2 根据注解使用的功能和用途分类
(1)Java内置注解:Java自带的注解类型
@Override:用于修饰此方法覆盖了父类的方法;
@Deprecated:用于修饰已经过时的方法;
@SuppressWarnnings:用于通知java编译器禁止特定的编译警告;
(2)元注解:注解的注解,负责注解其他注解
@Target:用于描述注解可以修饰的类型
@Retention:用于声明注解的生命周期,即注解在什么范围内有效。
@Documented:是一个标记注解,表明含有该注解类型的元素(带有注释的)会通过javadoc或类似工具进行文档化。
@Inherited:是一个标记注解,表示该注解类型能被自动继承。
@Repeatable :规定注解是否可以重复,重复型的注解还需要指明注解容器,用来存储可重复性注解,同样也是 Java 8 之后才支持
(3)自定义注解:用户根据自己的需求自定义的注解类型
使用@interface自定义注解,自动继承了java.lang.annotation.Annotation接口
五、Java内置注解
5.1 @Override(覆写) ——限定重写父类方法
(1)源码:
@Target(ElementType.METHOD) @Retention(RetentionPolicy.SOURCE) public @interface Override { }
(2)分析:
@Override 是一个标记注解,标注于方法上,仅保留在java源文件中。
(3)用途:
用于告知编译器,我们需要覆写超类的当前方法。如果某个方法带有该注解但并没有覆写超类相应的方法,则编译器会生成一条错误信息。
5.2 @Deprecated(不赞成使用)——用于标记已过时方法
(1)源码:
@Documented @Retention(RetentionPolicy.RUNTIME) @Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER, TYPE}) public @interface Deprecated { }
(2)分析:
@Deprecated 是一个标记注解,可标注于除注解类型声明之外的所有元素,保留时长为运行时VM。
(3)用途:
用于告知编译器,某一程序元素(比如方法,成员变量)不建议使用时,应该使用这个注解。Java在javadoc中推荐使用该注解,一般应该提供为什么该方法不推荐使用以及相应替代方法。
5.3 @SuppressWarnings(抑制警告)——抑制编译器警告
(1)源码:
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE}) @Retention(RetentionPolicy.SOURCE) public @interface SuppressWarnings { String[] value(); }
(2)分析:
@SuppressWarnings有一个类型为String[]的成员,不是标记注解,这个成员的值为被禁止的警告名,可标注于除注解类型声明和包名之外的所有元素,仅保留在java源文件中。
(3)用途:
用于告知编译器忽略特定的警告信息。
(4)使用示例:
public class SuppressWarningTest { @SuppressWarnings("unchecked") public void addItems2(String item){ @SuppressWarnings("unused") List list = new ArrayList(); List items = new ArrayList(); items.add(item); } @SuppressWarnings({"unchecked","unused"}) public void addItems1(String item){ List list = new ArrayList(); list.add(item); } @SuppressWarnings("all") public void addItems(String item){ List list = new ArrayList(); list.add(item); } }
(5)常见参数值
该注解有方法value(),可支持多个字符串参数,例如:
@SupressWarning(value={"uncheck","deprecation"})
前面讲的@Override,@Deprecated都是无需参数的,而压制警告是需要带有参数的,可用参数如下:
参数 | 含义 |
---|---|
deprecation | 使用了过时的类或方法时的警告 |
unchecked | 执行了未检查的转换时的警告 |
fallthrough | 当Switch程序块进入进入下一个case而没有Break时的警告 |
path | 在类路径、源文件路径等有不存在路径时的警告 |
serial | 当可序列化的类缺少serialVersionUID定义时的警告 |
finally | 任意finally子句不能正常完成时的警告 |
all | 以上所有情况的警告 |
更多关键字 |
六、元注解
元注解的作用就是负责注解其他注解。
6.1 @Target
(1)源码:
@Documented @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.ANNOTATION_TYPE) public @interface Target { ElementType[] value(); }
(2)作用:
用于描述注解可以修饰的程序元素。当注解类型声明中没有@Target元注解,则默认为可适用所有的程序元素。
如果存在指定的@Target元注解,则编译器强制实施相应的使用限制。程序元素(ElementType)是枚举类型,共定义8种程序元素,如下表:
ElementType | 含义 |
---|---|
ANNOTATION_TYPE | 注解类型声明 |
CONSTRUCTOR | 构造方法声明 |
FIELD | 字段声明(包括枚举常量) |
LOCAL_VARIABLE | 局部变量声明 |
METHOD | 方法声明 |
PACKAGE | 包声明 |
PARAMETER | 参数声明 |
TYPE | 类、接口(包括注解类型)或枚举声明 |
例如,上面源码@Target的定义中有一行 @Target(ElementType.ANNOTATION_TYPE) ,意思是指当前注解的元素类型是注解类型。
6.2 @Retention
(1)源码:
@Documented @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.ANNOTATION_TYPE) public @interface Retention { RetentionPolicy value(); }
(2)作用:
描述该注解的生命周期,表示在什么编译级别上保存该注解的信息。
当注解类型声明中没有@Retention元注解,则默认保留策略为RetentionPolicy.CLASS。
保留策略(RetentionPolicy)是枚举类型,共定义3种保留策略,如下表:
RetentionPolicy | 含义 |
---|---|
SOURCE | 仅存在Java源文件,经过编译器后便丢弃相应的注解 |
CLASS | 存在Java源文件,以及经编译器后生成的Class字节码文件,但在运行时VM不再保留注释 |
RUNTIME | 存在源文件、编译生成的Class字节码文件,以及保留在运行时VM中,可通过反射性地读取注解 |
例如,上面源码@Retention的定义中有一行 @Retention(RetentionPolicy.RUNTIME),意思是指当前注解的保留策略为RUNTIME,即存在Java源文件,也存在经过编译器编译后的生成的Class字节码文件,同时在运行时虚拟机(VM)中也保留该注解,可通过反射机制获取当前注解内容。
6.3 @Documented
(1)源码:
@Documented @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.ANNOTATION_TYPE) public @interface Documented { }
(2)作用:
是一个标记注解,表示拥有该注解的元素可通过javadoc此类的工具进行文档化,即在生成javadoc文档的时候将该Annotation也写入到文档中。
例如,上面源码@Retention的定义中有一行 @Documented,意思是指当前注解的元素会被javadoc工具进行文档化,那么在查看Java API文档时可查看当该注解元素。
6.4 @Inherited
(1)源码:
@Documented @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.ANNOTATION_TYPE) public @interface Inherited { }
(2)作用:
是一个标记注解,表示该注解类型被自动继承。如果一个使用了@Inherited修饰的annotation类型被用于一个class,则这个annotation将被用于该class的子类。
当@Inherited annotation类型标注的annotation的Retention是RetentionPolicy.RUNTIME,则反射API增强了这种继承性。如果我们使用java.lang.reflect去查询一个@Inherited annotation类型的annotation时,反射代码检查将展开工作:检查class和其父类,直到发现指定的annotation类型被发现,或者到达类继承结构的顶层。
七、自定义注解
7.1 自定义注解的规则
自定义注解示例:
/** *自定义注解MyAnnotation */ @Target(ElementType.TYPE) //目标对象是类型 @Retention(RetentionPolicy.RUNTIME) //保存至运行时 @Documented //生成javadoc文档时,该注解内容一起生成文档 @Inherited //该注解被子类继承 public @interface MyAnnotation { public String value() default ""; //当只有一个元素时,建议元素名定义为value(),这样使用时赋值可以省略"value=" String name() default "devin"; //String int age() default 18; //int boolean isStudent() default true; //boolean String[] alias(); //数组 enum Color {GREEN, BLUE, RED,} //枚举类型 Color favoriteColor() default Color.GREEN; //枚举值 }
自定义注解规则:
(1)定义注解:使用@interface来声明一个注解,同时将自动继承java.lang.annotation.Annotation接口,由编译程序自动完成其他细节。在定义注解时,不能继承其他的注解或接口。
(2)配置注解参数(key):注解的每一个方法实际上是声明了一个配置参数。注解方法不带参数,方法的名称就是参数的名称,返回值类型就是参数的类型(返回值类型只能是基本类型、Class、String、enum)。
(3)注解参数的默认值(value):可以通过default来声明参数的默认值。
(4)注解参数的可支持数据类型:基本类型、String、Enums、Annotation以及前面这些类型的数组类型。
(5)注解参数的访问权限:只能用public或默认(default)这两个访问权修饰。
(6)如果只有一个参数成员,建议参数名称设为value()。
(7)注解元素必须有确定的值,要么在定义注解的默认值中指定,要么在使用注解时指定,非基本类型的注解元素的值不可为null。因此, 使用空字符串或负数作为默认值是一种常用的做法。
7.2 使用自定义注解
@MyAnnotation( value = "info", name = "myname", age = 99, isStudent = false, alias = {"name1", "name2"}, favoriteColor = MyAnnotation.Color.RED ) public class MyClass { //使用MyAnnotation注解,该类生成的javadoc文档包含注解信息如下: /* @MyAnnotation(value = "info", name = "myname", age = 99, isStudent = false, alias = {"name1","name2"}, favoriteColor = Color.RED) public class MyClass extends Object */ } public class MySubClass extends MyClass{ //子类MySubClass继承了父类MyClass的注解 } 七、解
八、 解析注解信息
8.1 AnnotatedElement 接口
Java使用Annotation接口来代表程序元素前面的注解,该接口是所有Annotation类型的父接口。
通过反射技术来解析自定义注解,关于反射类位于包java.lang.reflect,其中有一个接口 AnnotatedElement,该接口代表程序中可以接受注解的程序元素。
AnnotatedElement接口是所有程序元素(Field、Method、Package、Class和Constructor)的父接口,所以程序通过反射获取了某个类的AnnotatedElement对象之后,程序就可以调用该对象的如下七个方法来访问Annotation信息:
返回值 | 方法 | 解释 |
---|---|---|
T | getAnnotation(Class annotationClass) | 当存在该元素的指定类型注解,则返回相应注释,否则返回null |
Annotation[] | getAnnotations() | 返回此元素上存在的所有注解 |
Annotation[] | getDeclaredAnnotation(Class) | 返回该程序元素上存在的、指定类型的注解,如果该类型注解不存在,则返回null;与此接口中的其他方法不同,该方法将忽略继承的注解; |
Annotation[] | getDeclaredAnnotations() | 返回直接存在于此元素上的所有注释。与此接口中的其他方法不同,该方法将忽略继承的注解; |
Annotation[] | getAnnotationsByType(Class) | 返回直接存在于此元素上指定注解类型的所有注解; |
boolean | isAnnotationPresent (Class<?extends Annotation> annotationClass) | 判断该程序元素上是否包含指定类型的注解,存在则返回true,否则返回false; |
8.1.2 isAnnotationPresent(Class<?extends Annotation>)源码
public boolean isAnnotationPresent( Class<? extends Annotation> annotationClass) { if (annotationClass == null) throw new NullPointerException(); return getAnnotation(annotationClass) != null; }
其实是调用了 getAnnotation(Class )
8.1.3 getAnnotation(Class )源码
public <A extends Annotation> A getAnnotation(Class<A> annotationClass) { if (annotationClass == null) throw new NullPointerException(); initAnnotationsIfNecessary();//初始化 return (A) annotations.get(annotationClass); }
initAnnotationsIfNecessary() 源码
//看了下这个方法。基本上就是扫描后放入到annotations中,完成初始话! private synchronized void initAnnotationsIfNecessary() { clearCachesOnClassRedefinition();//看了下这个方法,好像是清理缓存用的。 if (annotations != null) return; declaredAnnotations = AnnotationParser.parseAnnotations( getRawAnnotations(), getConstantPool(), this);//这个方法没法找到啊 Class<?> superClass = getSuperclass(); if (superClass == null) { annotations = declaredAnnotations; } else { annotations = new HashMap<Class, Annotation>(); superClass.initAnnotationsIfNecessary(); for (Map.Entry<Class, Annotation> e : superClass.annotations.entrySet()) { Class annotationClass = e.getKey(); if (AnnotationType.getInstance(annotationClass).isInherited()) annotations.put(annotationClass, e.getValue()); } annotations.putAll(declaredAnnotations); } }
8.2 解析注解示例
(1)FruitName注解
package com.ray.annotation; import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.Target; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.ElementType; /** * 水果名称注解 */ @Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface FruitName { String value() default " "; }
(2)FruitColor注解
package com.ray.annotation; import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.Target; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.ElementType; /** * 水果颜色注解 */ @Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface FruitColor { /** * 颜色枚举 */ public enum Color{BLUE, RED, GREEN}; /** * 颜色属性 * @return */ Color fruitColor() default Color.GREEN; }
(3)FruitProvider注解
package com.ray.annotation; import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.Target; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.ElementType; /** * 水果供应商注解 */ @Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface FruitProvider { /** * 供应商编号 * @return */ public int id() default -1; /** * 供应商名称 * @return */ public String name() default " "; /** * 供应商地址 * @return */ public String address() default " "; }
(4)实体类——Apple类
package com.ray.annotation; /***********注解使用***************/ public class Apple { @FruitName("Apple") private String appleName; @FruitColor(fruitColor = FruitColor.Color.RED) private String appleColor; @FruitProvider(id = 1, name = "陕西红富士集团", address = "陕西红富士大厦") private String appleProvider; public String getAppleProvider() { return appleProvider; } public void setAppleProvider(String appleProvider) { this.appleProvider = appleProvider; } public String getAppleName() { return appleName; } public void setAppleName(String appleName) { this.appleName = appleName; } public String getAppleColor() { return appleColor; } public void setAppleColor(String appleColor) { this.appleColor = appleColor; } public void displayName(){ System.out.println(getAppleName()); } }
(5)注解解析类——AnnotationParser类
package com.ray.annotation; import java.lang.reflect.Field; public class AnnotationParser { public static void main(String[] args) throws SecurityException, ClassNotFoundException { //1.获取苹果类的属性 String clazz = "com.ray.annotation.Apple"; //Field[] fields = AnnotationParser.class.getClassLoader().loadClass(clazz).getDeclaredFields(); Field[] fields = Class.forName(clazz).getDeclaredFields(); //2.解析注解信息 for (Field field : fields) { //System.out.println(field.getName().toString()); //2.1当field上标注了FruitName注解时 if (field.isAnnotationPresent(FruitName.class)){ FruitName fruitName = field.getAnnotation(FruitName.class); System.out.println("水果的名称:" + fruitName.value()); //2.2当field上标注了FruitColor注解时 }else if (field.isAnnotationPresent(FruitColor.class)){ FruitColor fruitColor = field.getAnnotation(FruitColor.class); System.out.println("水果的颜色:"+fruitColor.fruitColor()); }else if (field.isAnnotationPresent(FruitProvider.class)){ FruitProvider fruitProvider = field.getAnnotation(FruitProvider.class); System.out.println("水果供应商编号:" + fruitProvider.id() + " 名称:" + fruitProvider.name() + " 地址:" + fruitProvider.address()); } } } }
参考文章:
1.java注解解析