一、注解的概念

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注解解析

2.Java 注解深入理解

4.Java注解(Annotation)

5.Java annotation源码解读

6.聊聊 Java 注解(上)

7.Java Annotation认知(包括框架图、详细介绍、示例说明)

Java_注解_01_注解(Annotation)详解的更多相关文章

  1. Java注解(Annotation)详解

    转: Java注解(Annotation)详解 幻海流心 2018.05.23 15:20 字数 1775 阅读 380评论 0喜欢 1 Java注解(Annotation)详解 1.Annotati ...

  2. Android注解支持Support Annotations详解

    ###注解支持(Support Annotations)Android support library从19.1版本开始引入了一个新的注解库,它包含很多有用的元注解,你能用它们修饰你的代码,帮助你发现 ...

  3. Hibernate注解----关联映射注解以及课程总结详解----图片版本

    上一篇,记录了Hibernate注解----类级别注解以及属性注解详解 ,我们这一节主要讲解的是Hibernate注解----关联映射注解以及课程总结详解. 本节的主要内容: 第3章 关联映射注解 3 ...

  4. Java Annotation详解 理解和使用Annotation

    系统中用到了java注解: 查了一下如何使用注解,到底注解是什么: (1)创建方法:MsgTrace Java Class==> 在Create New Class中: name:输入MsgTr ...

  5. Java Annotation详解(二): 反射和Annotation

    前面一篇文<Java Annotation详解(一): 理解和使用Annotation>中,我们或许会觉得,Annotation注释其实并没有多大的作用,除了几个内建的Annotation ...

  6. 注解Annotation 详解(转)

    要深入学习注解,我们就必须能定义自己的注解,并使用注解,在定义自己的注解之前,我们就必须要了解Java为我们提供的元注解和相关定义注解的语法. 元注解: 元注解的作用就是负责注解其他注解.Java5. ...

  7. Java注解Annotation详解

    从JDK5开始,Java增加了Annotation(注解),Annotation是代码里的特殊标记,这些标记可以在编译.类加载.运行时被读取,并执行相应的处理.通过使用Annotation,开发人员可 ...

  8. Java自定义注解Annotation详解

    注解相当于一种标记,在程序中加了注解就等于为程序打上了某种标记,没加,则等于没有某种标记,以后,javac编译器,开发工具和其他程序可以用反射来了解你的类及各种元素上有无何种标记,看你有什么标记,就去 ...

  9. 一对一关联查询注解@OneToOne的实例详解

    表的关联查询比较复杂,应用的场景很多,本文根据自己的经验解释@OneToOne注解中的属性在项目中的应用.本打算一篇博客把增删改查写在一起,但是在改的时候遇到了一些问题,感觉挺有意思,所以写下第二篇专 ...

随机推荐

  1. 【Shell Basic】Shell脚本编写规范

    shell脚本需要有较高的实用性.可维护.可阅读.方便他人阅读,因而需要建立一定的规范来操作 dream361@master:~$ cat test2.sh #!/bin/bash 所使用的bash程 ...

  2. 《Java从入门到放弃》JavaSE篇:综合练习——单身狗租赁系统(数组版)

    因为现在只学习了基本语法,所以在综合练习之前,先补充关于方法概念. 方法的作用:把一系列的代码放在一起,然后再取个别名.之后通过这个别名的调用,就相当于执行了这一系列的代码. 方法的语法:([]中的内 ...

  3. Servlet中Response对象应用2(输出随机验证码图片)

    预期结果如图: 可用于登陆界面的验证 需要使用random类和绘画相关的几个类.以及imageio的内容. import java.awt.*; import java.awt.image.Buffe ...

  4. 我的前端故事----我为什么用GraphQL

    背景 今年我在做一个有关商户的app,这是一个包含商户从入网到审核.从驳回提交到入网维护的完整的生命周期线下推广人员使用的客户端软件,但故事并没有这么简单... 疑问 随着app的逐渐完善,遇到的问题 ...

  5. MySql数据库导入导出

    1.导出整个数据库     mysqldump -u 用户名 -p 数据库名 > 存放位置     比如:     mysqldump -u root -p project > c:/a. ...

  6. Redis订阅和发布模式和Redis事务

    -------------------Redis订阅和发布模式------------------- 1.概念     Redis 发布订阅(pub/sub)是一种消息通信模式:     发送者(pu ...

  7. 将位图导入为ArcGIS面要素

    本文根据笔者经验,介绍一种从位图图像导入ArcGIS称为要素的方法.这种方法适用于从现有出版物图片中获取地理信息的情况. 首先要说明的是,从位图导入要素是非常非常不精确的方式,如果有其它数据来源,那么 ...

  8. Akka(23): Stream:自定义流构件功能-Custom defined stream processing stages

    从总体上看:akka-stream是由数据源头Source,流通节点Flow和数据流终点Sink三个框架性的流构件(stream components)组成的.这其中:Source和Sink是stre ...

  9. spring mvc:属性无法自动注入

    在使用spring mvc 3开发一个项目模块时,遇到这样一个奇怪的问题: 前端页面发送的请求中,所有参数都无法自动注入到指定的@ModelAttribute对象中,经过检查,参数名称与接受对象的属性 ...

  10. 详解session

    详见:http://blog.yemou.net/article/query/info/tytfjhfascvhzxcytp30 一.术语session 在我的经验里,session这个词被滥用的程度 ...