注解的本质

「java.lang.annotation.Annotation」接口中有这么一句话,用来描述『注解』。

The common interface extended by all annotation types

所有的注解类型都继承自这个普通的接口(Annotation)

这句话有点抽象,但却说出了注解的本质。我们看一个 JDK 内置注解的定义:

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override { }

这是注解 @Override 的定义,其实它本质上就是:

public interface Override extends Annotation{

}

没错,注解的本质就是一个继承了 Annotation 接口的接口。有关这一点,你可以去反编译任意一个注解类,你会得到结果的。

一个注解准确意义上来说,只不过是一种特殊的注释而已,如果没有解析它的代码,它可能连注释都不如。

而解析一个类或者方法的注解往往有两种形式,一种是编译期直接的扫描,一种是运行期反射。反射的事情我们待会说,而编译器的扫描指的是编译器在对 java 代码编译字节码的过程中会检测到某个类或者方法被一些注解修饰,这时它就会对于这些注解进行某些处理。

典型的就是注解 @Override,一旦编译器检测到某个方法被修饰了 @Override 注解,编译器就会检查当前方法的方法签名是否真正重写了父类的某个方法,也就是比较父类中是否具有一个同样的方法签名。

这一种情况只适用于那些编译器已经熟知的注解类,比如 JDK 内置的几个注解,而你自定义的注解,编译器是不知道你这个注解的作用的,当然也不知道该如何处理,往往只是会根据该注解的作用范围来选择是否编译进字节码文件,仅此而已。

元注解

『元注解』是用于修饰注解的注解,通常用在注解的定义上,例如:

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override { }

这是我们 @Override 注解的定义,你可以看到其中的 @Target,@Retention 两个注解就是我们所谓的『元注解』,『元注解』一般用于指定某个注解生命周期以及作用目标等信息。

JAVA 中有以下几个『元注解』:

  • @Target:注解的作用目标
  • @Retention:注解的生命周期
  • @Documented:注解是否应当被包含在 JavaDoc 文档中
  • @Inherited:是否允许子类继承该注解

其中,@Target 用于指明被修饰的注解最终可以作用的目标是谁,也就是指明,你的注解到底是用来修饰方法的?修饰类的?还是用来修饰字段属性的。

@Target 的定义如下:

 
image

我们可以通过以下的方式来为这个 value 传值:

@Target(value = {ElementType.FIELD})

被这个 @Target 注解修饰的注解将只能作用在成员字段上,不能用于修饰方法或者类。其中,ElementType 是一个枚举类型,有以下一些值:

  • ElementType.TYPE:允许被修饰的注解作用在类、接口和枚举上
  • ElementType.FIELD:允许作用在属性字段上
  • ElementType.METHOD:允许作用在方法上
  • ElementType.PARAMETER:允许作用在方法参数上
  • ElementType.CONSTRUCTOR:允许作用在构造器上
  • ElementType.LOCAL_VARIABLE:允许作用在本地局部变量上
  • ElementType.ANNOTATION_TYPE:允许作用在注解上
  • ElementType.PACKAGE:允许作用在包上

@Retention 用于指明当前注解的生命周期,它的基本定义如下:

 
image

同样的,它也有一个 value 属性:

@Retention(value = RetentionPolicy.RUNTIME

这里的 RetentionPolicy 依然是一个枚举类型,它有以下几个枚举值可取:

  • RetentionPolicy.SOURCE:当前注解编译期可见,不会写入 class 文件
  • RetentionPolicy.CLASS:类加载阶段丢弃,会写入 class 文件
  • RetentionPolicy.RUNTIME:永久保存,可以反射获取

@Retention 注解指定了被修饰的注解的生命周期,一种是只能在编译期可见,编译后会被丢弃,一种会被编译器编译进 class 文件中,无论是类或是方法,乃至字段,他们都是有属性表的,而 JAVA 虚拟机也定义了几种注解属性表用于存储注解信息,但是这种可见性不能带到方法区,类加载时会予以丢弃,最后一种则是永久存在的可见性。

剩下两种类型的注解我们日常用的不多,也比较简单,这里不再详细的进行介绍了,你只需要知道他们各自的作用即可。@Documented 注解修饰的注解,当我们执行 JavaDoc 文档打包时会被保存进 doc 文档,反之将在打包时丢弃。@Inherited 注解修饰的注解是具有可继承性的,也就说我们的注解修饰了一个类,而该类的子类将自动继承父类的该注解。

JAVA 的内置三大注解

除了上述四种元注解外,JDK 还为我们预定义了另外三种注解,它们是:

  • @Override
  • @Deprecated
  • @SuppressWarnings

@Override 注解想必是大家很熟悉的了,它的定义如下:

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}

它没有任何的属性,所以并不能存储任何其他信息。它只能作用于方法之上,编译结束后将被丢弃。

所以你看,它就是一种典型的『标记式注解』,仅被编译器可知,编译器在对 java 文件进行编译成字节码的过程中,一旦检测到某个方法上被修饰了该注解,就会去匹对父类中是否具有一个同样方法签名的函数,如果不是,自然不能通过编译。

@Deprecated 的基本定义如下:

 
image

依然是一种『标记式注解』,永久存在,可以修饰所有的类型,作用是,标记当前的类或者方法或者字段等已经不再被推荐使用了,可能下一次的 JDK 版本就会删除。

当然,编译器并不会强制要求你做什么,只是告诉你 JDK 已经不再推荐使用当前的方法或者类了,建议你使用某个替代者。

@SuppressWarnings 主要用来压制 java 的警告,它的基本定义如下:

 
image

它有一个 value 属性需要你主动的传值,这个 value 代表一个什么意思呢,这个 value 代表的就是需要被压制的警告类型。例如:

public static void main(String[] args) {
Date date = new Date(2018, 7, 11);
}

这么一段代码,程序启动时编译器会报一个警告。

Warning:(8, 21) java: java.util.Date 中的 Date(int,int,int) 已过时

而如果我们不希望程序启动时,编译器检查代码中过时的方法,就可以使用 @SuppressWarnings 注解并给它的 value 属性传入一个参数值来压制编译器的检查。

@SuppressWarning(value = "deprecated")
public static void main(String[] args) {
Date date = new Date(2018, 7, 11);
}

这样你就会发现,编译器不再检查 main 方法下是否有过时的方法调用,也就压制了编译器对于这种警告的检查。

当然,JAVA 中还有很多的警告类型,他们都会对应一个字符串,通过设置 value 属性的值即可压制对于这一类警告类型的检查。

自定义注解的相关内容就不再赘述了,比较简单,通过类似以下的语法即可自定义一个注解。

public @interface InnotationName{

}

当然,自定义注解的时候也可以选择性的使用元注解进行修饰,这样你可以更加具体的指定你的注解的生命周期、作用范围等信息。

注解的内容

在上面的注解源码中可以看到有的注解中没有任何内容,有的注解的有内容,看似像方法。

注解的内容的语法格式:

数据类型 属性名() default 默认值,数据类型用于描述属性的数据类型,默认值是说当没有给属性赋值时使用默认值,一般String使用空字符串""作为默认值,数组一般使用空数组{ }作为默认值.

下面看一下SpringMVC中的RequestMapping的注解的声明

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Mapping
public @interface RequestMapping {
String name() default "";

@AliasFor("path")
String[] value() default {};

@AliasFor("value")
String[] path() default {};

RequestMethod[] method() default {};
String[] params() default {};
String[] headers() default {};
String[] consumes() default {};
String[] produces() default {};
}

注解和反射基本API

// 获取某个类型的注解
public <A extends Annotation> A getAnnotation(Class<A> annotationClass);

// 获取所有注解(包括父类中被Inherited修饰的注解)
public Annotation[] getAnnotations();

// 获取声明的注解(但是不包括父类中被Inherited修饰的注解)
public Annotation[] getDeclaredAnnotations();

// 判断某个对象上是否被某个注解进行标注
public boolean isAnnotationPresent(Class<? extends Annotation> annotationClass)

// 获取某个类声明的所有字段
public Field[] getDeclaredFields() throws SecurityException;
// 获取某个方法
public Method getMethod(String name, Class<?>... parameterTypes);

使用自定义注解可以在切面编程中使用,处理异常等

JAVA中注解的实现原理的更多相关文章

  1. Java自定义注解源码+原理解释(使用Java自定义注解校验bean传入参数合法性)

    Java自定义注解源码+原理解释(使用Java自定义注解校验bean传入参数合法性) 前言:由于前段时间忙于写接口,在接口中需要做很多的参数校验,本着简洁.高效的原则,便写了这个小工具供自己使用(内容 ...

  2. 从虚拟机指令执行的角度分析JAVA中多态的实现原理

    从虚拟机指令执行的角度分析JAVA中多态的实现原理 前几天突然被一个"家伙"问了几个问题,其中一个是:JAVA中的多态的实现原理是什么? 我一想,这肯定不是从语法的角度来阐释多态吧 ...

  3. 沉淀再出发:java中注解的本质和使用

    沉淀再出发:java中注解的本质和使用 一.前言 以前XML是各大框架的青睐者,它以松耦合的方式完成了框架中几乎所有的配置,但是随着项目越来越庞大,XML的内容也越来越复杂,维护成本变高.于是就有人提 ...

  4. 关于Java中注解的总结

    注解用来给类声明附加额外信息,可以标注在类.字段.方法等上面,编译器.JVM以及开发人员等都可以通过反射拿到注解信息,进而做一些相关处理 Java中注解的知识结构图 常用注解 @Override   ...

  5. java中注解的使用与实例(一)

    注解目前非常的流行,很多主流框架都支持注解,而且自己编写代码的时候也会尽量的去用注解,一时方便,而是代码更加简洁. 注解的语法比较简单,除了@符号的使用之外,它基本与Java固有语法一致.Java S ...

  6. [转] java中注解的使用与实例

    注解目前非常的流行,很多主流框架都支持注解,而且自己编写代码的时候也会尽量的去用注解,一时方便,而是代码更加简洁. 注解的语法比较简单,除了@符号的使用之外,它基本与Java固有语法一致.Java S ...

  7. spring中注解的实现原理

    @Autowired和@Resource的区别: 在Java中使用@Autowired和@Resource注解进行装配,这两个注解分别是:1.@Autowired按照默认类型(类名称)装配依赖对象,默 ...

  8. java中注解的使用与实例 (二)

    java 注解,从名字上看是注释,解释.但功能却不仅仅是注释那么简单.注解(Annotation) 为我们在代码中添加信息提供了一种形式化的方法,是我们可以在稍后 某个时刻方便地使用这些数据(通过 解 ...

  9. 【转】java中注解的使用与实例

    原文:http://www.cnblogs.com/pepcod/archive/2013/02/20/2918719.html java 注解,从名字上看是注释,解释.但功能却不仅仅是注释那么简单. ...

随机推荐

  1. Redis入门(一)

    Redis入门 什么是Redis Redis 是完全开源免费的,遵守BSD协议,是一个高性能的key-value数据库,属于非关系型数据库的一种(NoSQL). Redis 与其他 key - val ...

  2. eclipse调试断点

    1.条件断点 如果你不知道如何添加断点,只需点击左边面板(行号前面)断点即被创建.在调试界面中,“断点”视图会把所有被创建的断点列出来.我们可以给它加一个布尔条件,也就是说,该断点会被激活并且如果布尔 ...

  3. 【leetcode】1288. Remove Covered Intervals

    题目如下: Given a list of intervals, remove all intervals that are covered by another interval in the li ...

  4. MyEclipse运行项目出现 The user operation is waiting for "Building workspace" to complete

    如图所示 解决方式 1.选择菜单栏的“Project”,然后把菜单栏中“Build Automatically”前面的对钩去掉. 2.当你修改或添加代码后,选择菜单栏的“Project”,然后选择菜单 ...

  5. 02 CSS和DIV对界面优化

    01 网站首页的优化 <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> &l ...

  6. Ubuntu操作及Linux基础知识

    part 1: Ubuntu操作基础 1.调整字体的大小 调大:crtl+shift+“+”  调小:crtl+“-” 2.不要把虚拟机全屏的时候截屏,要不然会认为是Linux系统截屏而非Window ...

  7. vue项目更换目录后执行npm run dev 就报错(新手进)

    在我们搭建好一个VUE项目的环境后,觉得这个项目存放的位置不好,想移动一下,但是移动后我们发现执行npm run dev就会报下面的错误: 明明只是移动了一下位置,就报错,实在是太恶心了. 但是只要我 ...

  8. tensorflow实现siamese网络 (附代码)

    转载自:https://blog.csdn.net/qq1483661204/article/details/79039702 Learning a Similarity Metric Discrim ...

  9. 单链表之Java实现

    初次接触java,用java也写了一个链表.代码如下: import java.io.*; class Node{ public int data; //数据域 public Node next; / ...

  10. python 二维数组 转 矩阵

    x = numpy.array([[,,],[,,],[,,]]) print x print x.shape 输出 [[ ] [ ] [ ]] (3L, 3L) [Finished .2s]