一、注解的概念
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;
}
View Code
本质上注解会被编译为继承了(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(不赞成使用)——用于标记已过时方法
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER, TYPE})
public @interface Deprecated {
}
@Deprecated 是一个标记注解,可标注于除注解类型声明之外的所有元素,保留时长为运行时VM。
用于告知编译器,某一程序元素(比如方法,成员变量)不建议使用时,应该使用这个注解。Java在javadoc中推荐使用该注解,一般应该提供为什么该方法不推荐使用以及相应替代方法。
5.3 @SuppressWarnings(抑制警告)——抑制编译器警告
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
@Retention(RetentionPolicy.SOURCE)
public @interface SuppressWarnings {
String[] value();
}
@SuppressWarnings有一个类型为String[]的成员,不是标记注解,这个成员的值为被禁止的警告名,可标注于除注解类型声明和包名之外的所有元素,仅保留在java源文件中。
用于告知编译器忽略特定的警告信息。
(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
@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
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Retention {
RetentionPolicy value();
}
描述该注解的生命周期,表示在什么编译级别上保存该注解的信息。
当注解类型声明中没有@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
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Documented {
}
是一个标记注解,表示拥有该注解的元素可通过javadoc此类的工具进行文档化,即在生成javadoc文档的时候将该Annotation也写入到文档中。
例如,上面源码@Retention的定义中有一行 @Documented,意思是指当前注解的元素会被javadoc工具进行文档化,那么在查看Java API文档时可查看当该注解元素。
6.4 @Inherited
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Inherited {
}
是一个标记注解,表示该注解类型被自动继承。如果一个使用了@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() | 返回此元素上存在的所有注解 |
getDeclaredAnnotation(Class) | 返回该程序元素上存在的、指定类型的注解,如果该类型注解不存在,则返回null;与此接口中的其他方法不同,该方法将忽略继承的注解; | |
getDeclaredAnnotations() | 返回直接存在于此元素上的所有注释。与此接口中的其他方法不同,该方法将忽略继承的注解; | |
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注解解析2.
Java 注解深入理解4.
Java注解(Annotation)5.
Java annotation源码解读6.
聊聊 Java 注解(上)7.
Java Annotation认知(包括框架图、详细介绍、示例说明)