1,什么是注解

注解也叫元数据,例如常见的@Override和@Deprecated,注解是JDK1.5版本开始引入的一个特性,用于对代码进行说明,可以对包、类、接口、字段、方法参数、局部变量等进行注解

一般常用的注解可以分为三类:

  1. 一类是Java自带的标准注解,包括@Override(标明重写某个方法)、@Deprecated(标明某个类或方法过时)和@SuppressWarnings(标明要忽略的警告),使用这些注解后编译器就会进行检查。

  2. 一类为元注解,元注解是用于定义注解的注解,包括@Retention(标明注解被保留的阶段)、@Target(标明注解使用的范围)、@Inherited(标明注解可继承)、@Documented(标明是否生成javadoc文档)

  3. 一类为自定义注解,可以根据自己的需求定义注解

2,注解的用途

在看注解的用途之前,有必要简单的介绍下XML和注解区别,

注解:是一种分散式的元数据,与源代码紧绑定。

xml:是一种集中式的元数据,与源代码无绑定

当然网上存在各种XML与注解的辩论哪个更好,这里不作评论和介绍,主要介绍一下注解的主要用途:

  1. 生成文档,通过代码里标识的元数据生成javadoc文档。

  2. 编译检查,通过代码里标识的元数据让编译器在编译期间进行检查验证。

  3. 编译时动态处理,编译时通过代码里标识的元数据动态处理,例如动态生成代码。

  4. 运行时动态处理,运行时通过代码里标识的元数据动态处理,例如使用反射注入实例

3,注解使用演示

这边总共定义了4个注解来演示注解的使用

  1. 定义一个可以注解在Class,interface,enum上的注解,

  2. 定义一个可以注解在METHOD上的注解

  3. 定义一个可以注解在FIELD上的注解

  4. 定义一个可以注解在PARAMETER上的注解

具体代码如下:

/**
* 定义一个可以注解在Class,interface,enum上的注解
*/
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnTargetType {
   /**
    * 定义注解的一个元素 并给定默认值
    * @return
    */
   String value() default "我是定义在类接口枚举类上的注解元素value的默认值";
}
/**
* 定义一个可以注解在METHOD上的注解
*/
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnTargetMethod {
   /**
    * 定义注解的一个元素 并给定默认值
    * @return
    */
   String value() default "我是定义在方法上的注解元素value的默认值";
}
/**
* 定义一个可以注解在FIELD上的注解
*/
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnTargetField {
   /**
    * 定义注解的一个元素 并给定默认值
    * @return
    */
   String value() default "我是定义在字段上的注解元素value的默认值";
}
/**
* 定义一个可以注解在PARAMETER上的注解
*/
@Target({ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnTargetParameter {
   /**
    * 定义注解的一个元素 并给定默认值
    * @return
    */
   String value() default "我是定义在参数上的注解元素value的默认值";
}

编写一个测试处理类处理以上注解,这边不过多解释,代码中都写了对应的注释如下:

/**
* 测试java注解类
*/
@MyAnTargetType
public class AnnotationTest {
   @MyAnTargetField
   private String field = "我是字段";
   @MyAnTargetMethod("测试方法")
   public void test(@MyAnTargetParameter String args) {
       System.out.println("参数值 === "+args);
   }
   public static void main(String[] args) {
       // 获取类上的注解MyAnTargetType
       MyAnTargetType t = AnnotationTest.class.getAnnotation(MyAnTargetType.class);
       System.out.println("类上的注解值 === "+t.value());
       MyAnTargetMethod tm = null;
       try {
           // 根据反射获取AnnotationTest类上的test方法
           Method method = AnnotationTest.class.getDeclaredMethod("test",String.class);
           // 获取方法上的注解MyAnTargetMethod
           tm = method.getAnnotation(MyAnTargetMethod.class);
           System.out.println("方法上的注解值 === "+tm.value());
           // 获取方法上的所有参数注解  循环所有注解找到MyAnTargetParameter注解
           Annotation[][] annotations = method.getParameterAnnotations();
           for(Annotation[] tt : annotations){
               for(Annotation t1:tt){
                   if(t1 instanceof MyAnTargetParameter){
                       System.out.println("参数上的注解值 === "+((MyAnTargetParameter) t1).value());
                   }
               }
           }
           method.invoke(new AnnotationTest(), "改变默认参数");
           // 获取AnnotationTest类上字段field的注解MyAnTargetField
           MyAnTargetField fieldAn = AnnotationTest.class.getDeclaredField("field").getAnnotation(MyAnTargetField.class);
           System.out.println("字段上的注解值 === "+fieldAn.value());
       } catch (Exception e) {
           e.printStackTrace();
       }
   }
}

运行结果如下:

类上的注解值 === 我是定义在类接口枚举类上的注解元素value的默认值
参数上的注解值 === 我是定义在参数上的注解元素value的默认值
参数值 === 改变默认参数
方法上的注解值 === 测试方法
字段上的注解值 === 我是定义在字段上的注解元素value的默认值

4,注解的实现原理

以上只抽取了注解的其中几种类型演示,下面来看看他们是怎么工作的

先看一下实现注解三要素:

1,注解声明、

2,使用注解的元素、

3,操作注解使其起作用(注解处理器)

注解声明

首先让看一下java中的元注解(也就是上面提到的注解的注解),总共有4个如下:

  • @Target,

  • @Retention,

  • @Documented,

  • @Inherited

这4个元注解都是在jdk的java.lang.annotation包下面

@Target

Target说明的是Annotation所修饰的对象范围,源码如下:

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Target {
   /**
    * Returns an array of the kinds of elements an annotation type
    * can be applied to.
    * @return an array of the kinds of elements an annotation type
    * can be applied to
    */
   ElementType[] value();
}

其中只有一个元素ElementType,再看看它的源码如下:

public enum ElementType {
   /** Class, interface (including annotation type), or enum declaration */
   TYPE,
   /** Field declaration (includes enum constants) */
   FIELD,
   /** Method declaration */
   METHOD,
   /** Formal parameter declaration */
   PARAMETER,
   /** Constructor declaration */
   CONSTRUCTOR,
   /** Local variable declaration */
   LOCAL_VARIABLE,
   /** Annotation type declaration */
   ANNOTATION_TYPE,
   /** Package declaration */
   PACKAGE,
   /**
    * Type parameter declaration
    *
    * @since 1.8
    */
   TYPE_PARAMETER,
   /**
    * Use of a type
    *
    * @since 1.8
    */
   TYPE_USE
}

ElementType是一个枚举类定义注解可以作用的类型上,上面例子中演示了TYPE,FIELD,METHOD,PARAMETER 4种可以作用的目标

@Retention

定义了该Annotation被保留的时间长短:某些Annotation仅出现在源代码中,而被编译器丢弃;而另一些却被编译在class文件中;编译在class文件中的Annotation可能会被虚拟机忽略,而另一些在class被装载时将被读取(请注意并不影响class的执行,因为Annotation与class在使用上是被分离的)。使用这个元注解可以对 Annotation的“生命周期”限制

@Documented

@Documented用于描述其它类型的annotation应该被作为被标注的程序成员的公共API,因此可以被例如javadoc此类的工具文档化。Documented是一个标记注解,没有成员

@Inherited

@Inherited 元注解是一个标记注解,@Inherited阐述了某个被标注的类型是被继承的。如果一个使用了@Inherited修饰的annotation类型被用于一个class,则这个annotation将被用于该class的子类

注意:@Inherited annotation类型是被标注过的class的子类所继承。类并不从它所实现的接口继承annotation,方法并不从它所重载的方法继承annotation。

当@Inherited annotation类型标注的annotation的Retention是RetentionPolicy.RUNTIME,则反射API增强了这种继承性。如果使用java.lang.reflect去查询一个@Inherited annotation类型的annotation时,反射代码检查将展开工作:检查class和其父类,直到发现指定的annotation类型被发现,或者到达类继承结构的顶层。

写了一个例子,在以上MyAnTargetType注解类中增加@Inherited注解,如下:

/**
* 定义一个可以注解在Class,interface,enum上的注解
* 增加了@Inherited注解代表允许继承
*/
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface MyAnTargetType {
   /**
    * 定义注解的一个元素 并给定默认值
    * @return
    */
   String value() default "我是定义在类接口枚举类上的注解元素value的默认值";
}

增加一个子类ChildAnnotationTest继承AnnotationTest测试如下:

/**
* 增加一个子类继承AnnotationTest 演示@Inherited注解允许继承
*/
public class ChildAnnotationTest extends AnnotationTest {
   public static void main(String[] args) {
       // 获取类上的注解MyAnTargetType
       MyAnTargetType t = ChildAnnotationTest.class.getAnnotation(MyAnTargetType.class);
       System.out.println("类上的注解值 === "+t.value());
   }
}

运行如下:

类上的注解值 === 我是定义在类接口枚举类上的注解元素value的默认值

说明已经获取到了父类AnnotationTest的注解了

如果MyAnTargetType去掉@Inherited注解运行则报错如下:

Exception in thread "main" java.lang.NullPointerException
   at com.zhang.run.ChildAnnotationTest.main(ChildAnnotationTest.java:17)

使用注解的元素

使用注解没什么好说的就是在你需要的地方加上对应的你写好的注解就行

注解处理器

这个是注解使用的核心了,前面我们说了那么多注解相关的,那到底java是如何去处理这些注解的呢

从getAnnotation进去可以看到java.lang.class实现了AnnotatedElement方法

MyAnTargetType t = AnnotationTest.class.getAnnotation(MyAnTargetType.class);
public final class Class<T> implements java.io.Serializable,
                             GenericDeclaration,
                             Type,
                             AnnotatedElement

java.lang.reflect.AnnotatedElement 接口是所有程序元素(Class、Method和Constructor)的父接口,所以程序通过反射获取了某个类的AnnotatedElement对象之后,程序就可以调用该对象的如下四个个方法来访问Annotation信息:

  方法1:<T extends Annotation> T getAnnotation(Class<T> annotationClass): 返回改程序元素上存在的、指定类型的注解,如果该类型注解不存在,则返回null。
  方法2:Annotation[] getAnnotations():返回该程序元素上存在的所有注解。
   方法3:boolean is AnnotationPresent(Class<?extends Annotation> annotationClass):判断该程序元素上是否包含指定类型的注解,存在则返回true,否则返回false.
  方法4:Annotation[]

getDeclaredAnnotations():返回直接存在于此元素上的所有注释。与此接口中的其他方法不同,该方法将忽略继承的注释。(如果没有注释直接存在于此元素上,则返回长度为零的一个数组。)该方法的调用者可以随意修改返回的数组;这不会对其他调用者返回的数组产生任何影响

认识下java注解的实现原理的更多相关文章

  1. Java 注解及其底层原理

    目录 什么是注解? 注解的分类 Java自带的标准注解 元注解 @Retention @Documented @Target @Inherited @Repeatable 自定义注解 自定义注解的读取 ...

  2. Java Spring Boot VS .NetCore (八) Java 注解 vs .NetCore Attribute

    Java Spring Boot VS .NetCore (一)来一个简单的 Hello World Java Spring Boot VS .NetCore (二)实现一个过滤器Filter Jav ...

  3. 使用Java注解来简化你的代码

         注解(Annotation)就是一种标签,可以插入到源代码中,我们的编译器可以对他们进行逻辑判断,或者我们可以自己写一个工具方法来读取我们源代码中的注解信息,从而实现某种操作.需要申明一点, ...

  4. java自定义注解知识实例及SSH框架下,拦截器中无法获得java注解属性值的问题

    一.java自定义注解相关知识 注解这东西是java语言本身就带有的功能特点,于struts,hibernate,spring这三个框架无关.使用得当特别方便.基于注解的xml文件配置方式也受到人们的 ...

  5. Java注解的原理

    自Java5.0版本引入注解之后,它就成为了Java平台中非常重要的一部分.开发过程中,我们也时常在应用代码中会看到诸如@Override,@Deprecated这样的注解.这篇文章中,我将向大家讲述 ...

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

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

  7. Java 注解指导手册(下)

    9. 自定义注解   正如我们之前多次提及的,可以定义和实现自定义注解.本章我们即将探讨. 首先,定义一个注解:   public @interface CustomAnnotationClass   ...

  8. 浅谈Java/Android下的注解

    什么是注解 java.lang.annotation,接口 Annotation,在JDK5.0及以后版本引入. 注解是代码里的特殊标记,这些标记可以在编译.类加载.运行时被读取,并执行相应的处理.通 ...

  9. java@ 注解原理与使用

    Java反射 java反射机制的定义: 在运行转态时(动态的)时. 对于任意一个类,都能够知道这个类的所有属性和方法 对于任意一个对象,都能够知道调用它的任意属性和方法 Class对象 java中用对 ...

随机推荐

  1. Go sql insert update使用举例

    本文结合使用场景简单介绍sql中的insert.update的使用. 以下是代码: 如果记录已经存在,则更新,否则插入新记录. package main import ( "database ...

  2. Hive 特殊分隔符处理

    HIVE特殊分隔符处理 Hive对文件中的分隔符默认情况下只支持单字节分隔符,,默认单字符是\001.当然你也可以在创建表格时指定数据的分割符号.但是如果数据文件中的分隔符是多字符的,如下图: 01| ...

  3. 53题看透java线程

    1) 什么是线程? 线程是操作系统能够进行运算调度的最小单位,它被包含在进程之中,是进程中的实际运作单位.程序员可以通过它进行多处理器编程,你可以使用多线程对运算密集型任务提速.比如,如果一个线程完成 ...

  4. 实现java随机数Random的几招

    一,在java.util这个包里面提供了一个Random的类,我们可以新建一个Random的对象来产生随机数,可以产生随机整数.随机float.随机double,随机long,这个也是我们经常用的一个 ...

  5. Centos7 在 Xshell里 vim的配置

    Centos里的VI只默认安装了vim-minimal-7.x.所以无论是输入vi或者vim查看文件,syntax功能都无法正常启用.因此需要用yum安装另外两个组件:vim-common-7.x和v ...

  6. C++进阶--编译器自动生成的类函数

    //############################################################################ /* 在C++ 03标准下 在没有显式定义 ...

  7. HTTP API网关选择之一Kong介绍

    为什么需要 API 网关 在微服务架构之下,服务被拆的非常零散,降低了耦合度的同时也给服务的统一管理增加了难度.如上图左所示,在旧的服务治理体系之下,鉴权,限流,日志,监控等通用功能需要在每个服务中单 ...

  8. 1127 ZigZagging on a Tree (30 分)

    1127 ZigZagging on a Tree (30 分) Suppose that all the keys in a binary tree are distinct positive in ...

  9. tesseract 训练

    下载chi_sim.traindata字库下载tesseract-ocr-setup-3.02.02.exe 下载地址:http://code.google.com/p/tesseract-ocr/d ...

  10. if判断、while循环、for循环

    一. if判断 基本结构: if 执行语句1 print(代码块1); print(代码块2); # 满足执行语句1时,执行代码块1和代码块2,否则只执行代码块2. if 执行语句1 print(代码 ...