Java之道系列:Annotation实现机制

今天来研究研究Java的注解是如何实现的。最简单的,先来看看注解的class文件是什么样的。

class文件支持

直接看栗子,

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Ann0tation {
    String attr();
}

javap一下,

Classfile /home/blues/Projects/just4fun/out/production/just4fun/me/kisimple/just4fun/Ann0tation.class
  Last modified Feb 10, 2015; size 432 bytes
  MD5 checksum d87c1a02d469944f093b8a1815add76f
  Compiled from "Ann0tation.java"
public interface me.kisimple.just4fun.Ann0tation extends java.lang.annotation.Annotation
  SourceFile: "Ann0tation.java"
  RuntimeVisibleAnnotations:
    0: #9(#10:[e#11.#12])
    1: #13(#10:e#14.#15)
  minor version: 0
  major version: 51
  flags: ACC_PUBLIC, ACC_INTERFACE, ACC_ABSTRACT, ACC_ANNOTATION
Constant pool:
   #1 = Class              #16;           // me/kisimple/just4fun/Ann0tation
   #2 = Class              #17;           //  java/lang/Object
   #3 = Class              #18;           //  java/lang/annotation/Annotation
   #4 = Utf8               attr;
   #5 = Utf8               ()Ljava/lang/String;;
   #6 = Utf8               SourceFile;
   #7 = Utf8               Ann0tation.java;
   #8 = Utf8               RuntimeVisibleAnnotations;
   #9 = Utf8               Ljava/lang/annotation/Target;;
  #10 = Utf8               value;
  #11 = Utf8               Ljava/lang/annotation/ElementType;;
  #12 = Utf8               METHOD;
  #13 = Utf8               Ljava/lang/annotation/Retention;;
  #14 = Utf8               Ljava/lang/annotation/RetentionPolicy;;
  #15 = Utf8               RUNTIME;
  #16 = Utf8               me/kisimple/just4fun/Ann0tation;
  #17 = Utf8               java/lang/Object;
  #18 = Utf8               java/lang/annotation/Annotation;
{
  public abstract java.lang.String attr();
    flags: ACC_PUBLIC, ACC_ABSTRACT
}

可以看到,所有的注解都继承了java.lang.annotation.Annotation这个接口,

public interface me.kisimple.just4fun.Ann0tation extends java.lang.annotation.Annotation

接下来看下使用了注解后的class文件,

public class Main {

    @Ann0tation(attr = "hello main.")
    public static void main(String[] args) throws Exception {
        Method mm = Main.class.getMethod("main", String[].class);
        for (Annotation anno : mm.getDeclaredAnnotations()) {
            if(anno instanceof Ann0tation) {
                System.out.println(((Ann0tation)anno).attr());
            }
        }
    }

}
  public static void main(java.lang.String[]) throws java.lang.Exception;
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=6, locals=6, args_size=1
         0: ldc_w         #2;                 // class me/kisimple/just4fun/Main
         3: ldc           #3;                 // String main
         5: iconst_1
         6: anewarray     #4;                 // class java/lang/Class
         9: dup
        10: iconst_0
        11: ldc_w         #5;                 // class "[Ljava/lang/String;"
        14: aastore
        15: invokevirtual #6;                 // Method java/lang/Class.getMethod:(Ljava/lang/String;[Ljava/lang/Class;)Ljava/lang/reflect/Method;
        18: astore_1
        19: aload_1
        20: invokevirtual #7;                 // Method java/lang/reflect/Method.getDeclaredAnnotations:()[Ljava/lang/annotation/Annotation;
        23: astore_2
        24: aload_2
        25: arraylength
        26: istore_3
        27: iconst_0
        28: istore        4
        30: iload         4
        32: iload_3
        33: if_icmpge     72
        36: aload_2
        37: iload         4
        39: aaload
        40: astore        5
        42: aload         5
        44: instanceof    #8;                 // class me/kisimple/just4fun/Ann0tation
        47: ifeq          66
        50: getstatic     #9;                 // Field java/lang/System.out:Ljava/io/PrintStream;
        53: aload         5
        55: checkcast     #8;                 // class me/kisimple/just4fun/Ann0tation
        58: invokeinterface #10,  1;          // InterfaceMethod me/kisimple/just4fun/Ann0tation.attr:()Ljava/lang/String;
        63: invokevirtual #11;                // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        66: iinc          4, 1
        69: goto          30
        72: return
      LineNumberTable:
        line 13: 0
        line 14: 19
        line 15: 42
        line 16: 50
        line 14: 66
        line 19: 72
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
              42      24     5  anno   Ljava/lang/annotation/Annotation;
              24      48     2  arr$   [Ljava/lang/annotation/Annotation;
              27      45     3  len$   I
              30      42     4    i$   I
               0      73     0  args   [Ljava/lang/String;
              19      54     1    mm   Ljava/lang/reflect/Method;
      StackMapTable: number_of_entries = 3
           frame_type = 255 /* full_frame */
          offset_delta = 30
          locals = [ class "[Ljava/lang/String;", class java/lang/reflect/Method, class "[Ljava/lang/annotation/Annotation;", int, int ]
          stack = []
           frame_type = 35 /* same */
           frame_type = 248 /* chop */
          offset_delta = 5

    Exceptions:
      throws java.lang.Exception
    RuntimeVisibleAnnotations:
      0: #39(#40:s#41)

会发现最后多出来了RuntimeVisibleAnnotations这么一个属性,而它的值就是我们给main方法添加的注解,

  #37 = Utf8               Lme/kisimple/just4fun/Ann0tation;;
  #38 = Utf8               attr;
  #39 = Utf8               hello main.;

其实在上面Ann0tation的class文件里面就有这个属性了,因为Ann0tation使用了Target跟Retention注解。关于该属性可以参考虚拟机规范

这么看下来,注解的定义与普通的接口其实并没有什么两样,只是编译器会在使用了注解的地方去添加一个RuntimeVisibleAnnotations属性,保存下我们添加的注解。这样即使在运行时,我们还是可以通过class文件来拿到我们添加的注解。

反射注解实例

既然注解只是个普通的接口,那么当我们使用反射去拿到的注解的实例(看上面的栗子)又是个什么鬼?看看源码就知道了。

上面我们所调用的Method#getDeclaredAnnotations,最后是会去调用

sun.reflect.annotation.AnnotationParser#parseAnnotations

    /**
     * Parses the annotations described by the specified byte array.
     * resolving constant references in the specified constant pool.
     * The array must contain an array of annotations as described
     * in the RuntimeVisibleAnnotations_attribute:
     *
     *   u2 num_annotations;
     *   annotation annotations[num_annotations];
     *
     * @throws AnnotationFormatError if an annotation is found to be
     *         malformed.
     */
    public static Map<Class<? extends Annotation>, Annotation> parseAnnotations(
                byte[] rawAnnotations,
                ConstantPool constPool,
                Class<?> container) {
        if (rawAnnotations == null)
            return Collections.emptyMap();

        try {
            return parseAnnotations2(rawAnnotations, constPool, container, null);
        } catch(BufferUnderflowException e) {
            throw new AnnotationFormatError("Unexpected end of annotations.");
        } catch(IllegalArgumentException e) {
            // Type mismatch in constant pool
            throw new AnnotationFormatError(e);
        }
    }

注解的解析其实就是去处理上面我们所说到的class文件中的RuntimeVisibleAnnotations属性相关的一些东东。继续往下看,最后会来到annotationForMap方法,

    public static Annotation annotationForMap(
        Class<? extends Annotation> type, Map<String, Object> memberValues)
    {
        return (Annotation) Proxy.newProxyInstance(
            type.getClassLoader(), new Class[] { type },
            new AnnotationInvocationHandler(type, memberValues));
    }

原来是使用动态代理实例化了一个注解的接口返回,那我们可以使用SA中的ClassDump把这个动态代理类dump下来,再用jd反编译看看,结果如下,

package com.sun.proxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
import me.kisimple.just4fun.Ann0tation;

public final class $Proxy1 extends Proxy
  implements Ann0tation
{
  private static Method m1;
  private static Method m3;
  private static Method m0;
  private static Method m4;
  private static Method m2;

  public $Proxy1(InvocationHandler paramInvocationHandler)
  {
    super(paramInvocationHandler);
  }

  static
  {
    try
    {
      m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") });
      m3 = Class.forName("me.kisimple.just4fun.Ann0tation").getMethod("attr", new Class[0]);
      m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
      m4 = Class.forName("me.kisimple.just4fun.Ann0tation").getMethod("annotationType", new Class[0]);
      m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
      return;
    }
    catch (NoSuchMethodException localNoSuchMethodException)
    {
      throw new NoSuchMethodError(localNoSuchMethodException.getMessage());
    }
    catch (ClassNotFoundException localClassNotFoundException)
    {
      throw new NoClassDefFoundError(localClassNotFoundException.getMessage());
    }
  }

  public final boolean equals(Object paramObject)
  {
    try
    {
      return ((Boolean)this.h.invoke(this, m1, new Object[] { paramObject })).booleanValue();
    }
    catch (Error|RuntimeException localError)
    {
      throw localError;
    }
    catch (Throwable localThrowable)
    {
      throw new UndeclaredThrowableException(localThrowable);
    }
  }

  public final String toString()
  {
    try
    {
      return (String)this.h.invoke(this, m2, null);
    }
    catch (Error|RuntimeException localError)
    {
      throw localError;
    }
    catch (Throwable localThrowable)
    {
      throw new UndeclaredThrowableException(localThrowable);
    }
  }

  public final int hashCode()
  {
    try
    {
      return ((Integer)this.h.invoke(this, m0, null)).intValue();
    }
    catch (Error|RuntimeException localError)
    {
      throw localError;
    }
    catch (Throwable localThrowable)
    {
      throw new UndeclaredThrowableException(localThrowable);
    }
  }

  public final Class annotationType()
  {
    try
    {
      return (Class)this.h.invoke(this, m4, null);
    }
    catch (Error|RuntimeException localError)
    {
      throw localError;
    }
    catch (Throwable localThrowable)
    {
      throw new UndeclaredThrowableException(localThrowable);
    }
  }

  public final String attr()
  {
    try
    {
      return (String)this.h.invoke(this, m3, null);
    }
    catch (Error|RuntimeException localError)
    {
      throw localError;
    }
    catch (Throwable localThrowable)
    {
      throw new UndeclaredThrowableException(localThrowable);
    }
  }
}

AnnotationInvocationHandler做的事情很简单,举上面的栗子,当我们调用((Ann0tation)anno).attr()时,AnnotationInvocationHandler直接从memberValues这个map取出来并返回,而这个map就是通过解析RuntimeVisibleAnnotations得到并传给AnnotationInvocationHandler的。

所以就像R大说的,注解就是接口+Map,然后通过动态代理将他们组合起来就OK了^_^

参考资料

时间: 2024-08-04 21:50:25

Java之道系列:Annotation实现机制的相关文章

Java之道系列:WeakHashMap实现浅析

举个栗子 关于Reference对象,java.lang.ref包的文档说了一堆,跑起来看看才是王道, public class Foo { @Override protected void finalize() throws Throwable { System.out.println("finalize#" + this); super.finalize(); } } private static ReferenceQueue<Foo> queue = new Refe

Java之道系列:BigDecimal如何解决浮点数精度问题

如题,今天我们来看下java.math.BigDecimal是如何解决浮点数的精度问题的,在那之前当然得先了解下浮点数精度问题是什么问题了.下面我们先从IEEE 754说起. IEEE 754 IEEE二进制浮点数算术标准(IEEE 754)是20世纪80年代以来最广泛使用的浮点数运算标准,为许多CPU与浮点运算器所采用.这个标准定义了表示浮点数的格式(包括负零-0)与反常值(denormal number)),一些特殊数值(无穷(Inf)与非数值(NaN)),以及这些数值的"浮点数运算符&qu

java基础解析系列(十一)---equals、==和hashcode方法

java基础解析系列(十一)---equals.==和hashcode方法 目录 java基础解析系列(一)---String.StringBuffer.StringBuilder java基础解析系列(二)---Integer缓存及装箱拆箱 java基础解析系列(三)---HashMap原理 java基础解析系列(四)---LinkedHashMap的原理及LRU算法的实现 java基础解析系列(五)---HashMap并发下的问题以及HashTable和CurrentHashMap的区别 j

Java面向对象程序设计--与C++对比说明:系列2(类机制)

1. Java中的包机制(Package): 1.1   Java容许将一系列的类组合到一个叫package的集合中.package机制是一种非常好的管理工作的方式并可以将你自己的工作和系统或第三方提供的代码分开. 一个类(class)可以使用其本身所在包中的任何类和其他包中的任何public类. 注意:只能使用*号来导入一个单独的包中的所有的类.像这种java.*或java.*.*形式的包的导入都是错误的. 例如:有两个包com.a.b和com.a.b.c,那么com.a.b.*导入的类将不包

Java Annotation注释机制

简介 Annotation 提供了一条与程序元素关联任何信息或者任何元数据(metadata)的途径.从某些方面看,annotation就像修饰符一样被使用,并应用于包.类型.构造方法.方法.成员变量.参数.本地变量的声明中.这些信息被存储在annotation的"name=value"结构对中. annotation类型是一种接口,能够通过java反射API的方式提供对其信息的访问. annotation能被用来为某个程序元素(类.方法.成员变量等)关联任何的信息.需要注意的是,这里

Java总结篇系列:java.lang.Object

从本篇开始,将对Java中各知识点进行一次具体总结,以便对以往的Java知识进行一次回顾,同时在总结的过程中加深对Java的理解. Java作为一个庞大的知识体系,涉及到的知识点繁多,本文将从Java中最基本的类java.lang.Object开始谈起. Object类是Java中其他所有类的祖先,没有Object类Java面向对象无从谈起.作为其他所有类的基类,Object具有哪些属性和行为, 是Java语言设计背后的思维体现. Object类位于java.lang包中,java.lang包包

深入理解Java:注解(Annotation)--注解处理器

深入理解Java:注解(Annotation)--注解处理器 如果没有用来读取注解的方法和工作,那么注解也就不会比注释更有用处了.使用注解的过程中,很重要的一部分就是创建于使用注解处理器.Java SE5扩展了反射机制的API,以帮助程序员快速的构造自定义注解处理器. 注解处理器类库(java.lang.reflect.AnnotatedElement): Java使用Annotation接口来代表程序元素前面的注解,该接口是所有Annotation类型的父接口.除此之外,Java在java.l

Java内存区域划分和GC机制

Java 内存区域和GC机制 目录 Java垃圾回收概况 Java内存区域 Java对象的访问方式 Java内存分配机制 Java GC机制 垃圾收集器 Java垃圾回收概况 Java GC(Garbage Collection,垃圾收集,垃圾回收)机制,是Java与C++/C的主要区别之一,作为Java开发者,一般不需要专门编写内存回收和垃圾清理代 码,对内存泄露和溢出的问题,也不需要像C程序员那样战战兢兢.这是因为在Java虚拟机中,存在自动内存管理和垃圾清扫机制.概括地说,该机制对 JVM

Java Web学习系列——Maven Web项目中集成使用Spring、MyBatis实现对MySQL的数据访问

本篇内容还是建立在上一篇Java Web学习系列——Maven Web项目中集成使用Spring基础之上,对之前的Maven Web项目进行升级改造,实现对MySQL的数据访问. 添加依赖Jar包 这部分内容需要以下Jar包支持 mysql-connector:MySQL数据库连接驱动,架起服务端与数据库沟通的桥梁: MyBatis:一个支持普通SQL查询,存储过程和高级映射的优秀持久层框架: log4j:Apache的开源项目,一个功能强大的日志组件,提供方便的日志记录: 修改后的pom.xm