美文网首页
java注解研究

java注解研究

作者: 多喝水JS | 来源:发表于2019-01-16 18:02 被阅读34次

    简介

    Spring框架经常用到两种注入方式:一种是注解,比如@Controller,@Service,@Repository,另一种是XML。采用注解可以提供更大的便捷性,易于维护修改,但耦合度高,而 XML 相对于注解则是相反的。两者各有优劣,本文主要讲注解。

    例子

    /**
     * 注解接口
     */
    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface MyAnnotation {
        int set() default 0;
    }
    @MyAnnotation(set=12)
    public class TestAnnotation {
        public static void main(String[] args) {
            // 检查TestAnnotation类是否有注解
            if (TestAnnotation.class.isAnnotationPresent(MyAnnotation.class)) {
                MyAnnotation annotation = (MyAnnotation) TestAnnotation.class.getAnnotation(MyAnnotation.class);
                System.out.println(annotation.set());
            }
        }
    }
    输出:
    12
    
    注解本质

    java.lang.annotation.Annotation接口

    //The common interface extended by all annotation types
    public interface Annotation
    

    注释说明了:所有的注解类型都继承自这个普通的接口(Annotation)
    比如上面的MyAnnotation,它本质上就是

    public interface MyAnnotation extends Annotation{   
    }
    
    注解语法

    通过上面的例子:
    注解通过 @interface 关键字进行定义。

    public @interface MyAnnotation{
    }
    

    它的形式跟接口很类似,不过前面多了一个 @ 符号。上面的代码就创建了一个名字为 MyAnnotation的注解。
    不过这样注解还不能工作,还需要一些标识告诉编译器这个注解用在什么地方,以及它的生命周期等。这些标识就是元注解。

    元注解

    元注解是可以注解到注解上的注解,或者说元注解是一种基本注解,但是它能够应用到其它的注解上面。
    元标签有@Retention、@Documented、@Target、@Inherited、@Repeatable5 种。
    (1)@Retention
    Retention 的英文意为保留期的意思。当 @Retention 应用到一个注解上的时候,它解释说明了这个注解的的存活时间。
    它的取值如下:
    RetentionPolicy.SOURCE:只在本编译单元的编译过程中保留,并不写入Class文件中。这种注解主要用于在本编译单元(这里一个Java源码文件可以看作一个编译单元)内触发注解处理器(annotation processor)的相关处理,例如说可以让注解处理器相应地生成一些代码,或者是让注解处理器做一些额外的类型检查,等等。例如:@Override@SuppressWarnings
    RetentionPolicy.CLASS:在编译的过程中保留并且会写入Class文件中,但是JVM在加载类的时候不需要将其加载为运行时可见的(反射可见)的注解。这里很重要的一点是编译多个Java文件时的情况:假如要编译A.java源码文件和B.class文件,其中A类依赖B类,并且B类上有些注解希望让A.java编译时能看到,那么B.class里就必须要持有这些注解信息才行。同时我们可能不需要让它在运行时对反射可见(例如说为了减少运行时元数据的大小之类),所以会选择CLASS而不是RUNTIME。
    RetentionPolicy.RUNTIME:在编译过程中保留,会写入Class文件,并且JVM加载类的时候也会将其加载为反射可见的注解。这就不用多说了,例如说Spring的依赖注入就会在运行时通过扫描类上的注解来决定注入啥。

    @Retention(RetentionPolicy.RUNTIME)
    public @interface MyAnnotation {
    }
    

    上面的代码中,我们指定 MyAnnotation可以在程序运行周期被获取到,因此它的生命周期非常的长。
    (2)@Documented
    当我们执行 JavaDoc 文档打包时会被保存进 doc 文档。
    (3)@Target
    Target 是目标的意思,@Target指定了注解运用的地方。
    @Target 有下面的取值

    ElementType.ANNOTATION_TYPE 可以给一个注解进行注解
    ElementType.CONSTRUCTOR 可以给构造方法进行注解
    ElementType.FIELD 可以给属性进行注解
    ElementType.LOCAL_VARIABLE 可以给局部变量进行注解
    ElementType.METHOD 可以给方法进行注解
    ElementType.PACKAGE 可以给一个包进行注解
    ElementType.PARAMETER 可以给一个方法内的参数进行注解
    ElementType.TYPE 可以给一个类型进行注解,比如类、接口、枚举

    (4)@Inherited
    是继承的意思,但是它并不是说注解本身可以继承,而是说如果一个超类被 @Inherited 注解过的注解进行注解的话,那么如果它的子类没有被任何注解应用的话,那么这个子类就继承了超类的注解。
    例子:

    @Inherited
    @Retention(RetentionPolicy.RUNTIME)
    @interface Test {}
    
    @Test
    public class A {}
    public class B extends A {}
    

    注解@ Test@Inherited 修饰,之后类 A 被 @Test 注解,类 B 继承 A,类 B 也拥有@Test 这个注解。
    (5)@Repeatable
    Repeatable 自然是可重复的意思。@Repeatable 是 Java 1.8 才加进来的,所以算是一个新的特性。

    什么样的注解会多次应用呢?通常是注解的值可以同时取多个。

    举个例子,一个人他既是程序员又是产品经理,同时他还是个画家。

    @interface Persons {
        Person[]  value();
    }
    
    @Repeatable(Persons.class)
    @interface Person{
        String role default "";
    }
    
    @Person(role="artist")
    @Person(role="coder")
    @Person(role="PM")
    public class SuperMan{
    
    }
    

    注意上面的代码,@Repeatable注解了 Person。而 @Repeatable 后面括号中的类相当于一个容器注解。

    Class 类中提供了以下一些方法用于反射注解

    getAnnotation:返回指定的注解
    isAnnotationPresent:判定当前元素是否被指定注解修饰
    getAnnotations:返回所有的注解
    getDeclaredAnnotation:返回本元素的指定注解
    getDeclaredAnnotations:返回本元素的所有注解,不包含父类继承而来的

    注解原理

    Debug上面的代码,


    可以看出MyAnnotation本质上通过由JDK生成动态代理类来进行调用的
    main方法加入
    public static void main(String[] args) {
        //把生成的代理字节码写到com/sun/proxy包下
            System.setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
            // 检查TestAnnotation类是否有注解
            if (TestAnnotation.class.isAnnotationPresent(MyAnnotation.class)) {
                MyAnnotation annotation = (MyAnnotation) TestAnnotation.class.getAnnotation(MyAnnotation.class);
                System.out.println(annotation.set());
            }
        }
    

    com\sun\proxy目录下生成$Proxy1.class
    反编译以后

    public final class $Proxy1
      extends Proxy
      implements MyAnnotation
    {
      private static Method m1;
      private static Method m2;
      private static Method m3;
      private static Method m4;
      private static Method m0;
      
      public $Proxy1(InvocationHandler paramInvocationHandler)
        throws 
      {
        super(paramInvocationHandler);
      }
      
      public final boolean equals(Object paramObject)
        throws 
      { 
          return ((Boolean)this.h.invoke(this, m1, new Object[] { paramObject })).booleanValue(); 
      }
      
      public final String toString()
        throws 
      {
              return (String)this.h.invoke(this, m2, null); 
      }
      
      public final int set()
        throws 
      { 
          return ((Integer)this.h.invoke(this, m3, null)).intValue();  
      }
      
      public final Class annotationType()
        throws 
      {    
          return (Class)this.h.invoke(this, m4, null);  
      }
      
      public final int hashCode()
        throws 
      {  
          return ((Integer)this.h.invoke(this, m0, null)).intValue();   
      }
      
      static
      {  
          m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") });
          m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
          m3 = Class.forName("com.huige.MyAnnotation").getMethod("set", new Class[0]);
          m4 = Class.forName("com.huige.MyAnnotation").getMethod("annotationType", new Class[0]);
          m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
          return;  
      }
    }
    
    

    从上面可以看出我们自定义的注解HelloAnnotation是一个接口,而$Proxy1这个Java生成的动态代理类就是它的实现类

    InvocationHandler

    既然MyAnnotation具体是通过Proxy1来调用的,那Proxy1调用最终会传递给绑定的InvocationHandler实例的invoke方法处理,那InvocationHandler是谁呢?


    从上图可以看出java提供了AnnotationInvocationHandler来处理注解
    跟踪进去
    最终调用sun.reflect.annotation.AnnotationInvocationHandler#invoke方法
     public Object invoke(Object var1, Method var2, Object[] var3) {
            String var4 = var2.getName();
            Class[] var5 = var2.getParameterTypes();
            if (var4.equals("equals") && var5.length == 1 && var5[0] == Object.class) {
                return this.equalsImpl(var3[0]);
            } else if (var5.length != 0) {
                throw new AssertionError("Too many parameters for an annotation method");
            } else {
                byte var7 = -1;
                switch(var4.hashCode()) {
                case -1776922004:
                    if (var4.equals("toString")) {
                        var7 = 0;
                    }
                    break;
                case 147696667:
                    if (var4.equals("hashCode")) {
                        var7 = 1;
                    }
                    break;
                case 1444986633:
                    if (var4.equals("annotationType")) {
                        var7 = 2;
                    }
                }
    
                switch(var7) {
                case 0:
                    return this.toStringImpl();
                case 1:
                    return this.hashCodeImpl();
                case 2:
                    return this.type;
                default:
                    Object var6 = this.memberValues.get(var4);
                    if (var6 == null) {
                        throw new IncompleteAnnotationException(this.type, var4);
                    } else if (var6 instanceof ExceptionProxy) {
                        throw ((ExceptionProxy)var6).generateException();
                    } else {
                        if (var6.getClass().isArray() && Array.getLength(var6) != 0) {
                            var6 = this.cloneArray(var6);
                        }
    
                        return var6;
                    }
                }
            }
        }
    
    

    看看invoke方法是如何处理我们annotation.set()方法的调用的
    debug进去


    上图可以看出,在default环节进行获取值的,明显memberValues是一个LinkedHashMap,set方法的返回值是从一个LinkedHashMap中获取到的。这个LinkedHashMap以key(注解方法名)—value(注解方法对应的值)存储

    那memberValues这个Map对象是怎么生成的,继续调试
    通过方法调用栈找到memberValues的本源
    调用栈如下:


    可以看出在执行TestAnnotation.class.isAnnotationPresent(MyAnnotation.class)时就生成memberValues
    具体创建LinkedHashMap是在sun.reflect.annotation.AnnotationParser#parseAnnotation2方法中
    private static Annotation parseAnnotation2(ByteBuffer var0, ConstantPool var1, Class<?> var2, boolean var3, Class<? extends Annotation>[] var4) {
           //。。省略
                LinkedHashMap var10 = new LinkedHashMap(var8.memberDefaults());
              //。。。省略:var10就是LinkedHashMap 
                return annotationForMap(var6, var10);
            }
        }
    

    具体获取值在sun.reflect.annotation.AnnotationParser#parseConst方法中


    即从运行时常量池地址为30中取出12,
    最后调用AnnotationInvocationHandler构造器创建AnnotationInvocationHandler对象,把已经生成的map赋值给memberValues
    番外篇

    上面提到MyAnnotation继承Annotation接口,翻了源码没发现在哪里继承了,到底是什么回事呢?
    我们查看下MyAnnotation的字节码

    D:\project\src\main\java\com\huige>javap -verbose MyAnnotat
    ion.class
    Classfile /D:/project/src/main/java/com/huige/MyAnnotation.
    class
      Last modified 2019-1-16; size 439 bytes
      MD5 checksum c1dd0a0bf103c12955a919a57ce48806
      Compiled from "MyAnnotation.java"
    public interface com.huige.MyAnnotation extends java.lang.annotation.Annotation
      minor version: 0
      major version: 52
      flags: ACC_PUBLIC, ACC_INTERFACE, ACC_ABSTRACT, ACC_ANNOTATION
    Constant pool:
       #1 = Class              #18            // com/huige/MyAnnotation
       #2 = Class              #19            // java/lang/Object
       #3 = Class              #20            // java/lang/annotation/Annotation
       #4 = Utf8               set
       #5 = Utf8               ()I
       #6 = Utf8               AnnotationDefault
       #7 = Integer            0
       #8 = Utf8               SourceFile
       #9 = Utf8               MyAnnotation.java
      #10 = Utf8               RuntimeVisibleAnnotations
      #11 = Utf8               Ljava/lang/annotation/Target;
      #12 = Utf8               value
      #13 = Utf8               Ljava/lang/annotation/ElementType;
      #14 = Utf8               TYPE
      #15 = Utf8               Ljava/lang/annotation/Retention;
      #16 = Utf8               Ljava/lang/annotation/RetentionPolicy;
      #17 = Utf8               RUNTIME
      #18 = Utf8               com/huige/MyAnnotation
      #19 = Utf8               java/lang/Object
      #20 = Utf8               java/lang/annotation/Annotation
    {
      public abstract int set();
        descriptor: ()I
        flags: ACC_PUBLIC, ACC_ABSTRACT
        AnnotationDefault:
          default_value: I#7}
    SourceFile: "MyAnnotation.java"
    RuntimeVisibleAnnotations:
      0: #11(#12=[e#13.#14])
      1: #15(#12=e#16.#17)
    

    从上面的字节码可以看出
    (1)MyAnnotation继承了Annotation的接口
    (2)虚拟机规范定义了一系列和注解相关的属性表,也就是说,无论是字段、方法或是类本身,如果被注解修饰了,就可以被写进字节码文件。属性表有以下几种:

    RuntimeVisibleAnnotations:记录所有运行时可见的Annotation
    RuntimeInVisibleAnnotations:记录所有运行时不可见的注解RuntimeVisibleParameterAnnotations:记录所有运行时可见的方法参数注解
    RuntimeInVisibleParameterAnnotations:记录所有运行时不可见的方法参数注解
    AnnotationDefault:注解类元素的默认值

    所以字节码倒数第三行采用RuntimeVisibleAnnotations来保存Target跟Retention注解,这两个都是运行时驻留的

    RuntimeVisibleAnnotations:
      0: #11(#12=[e#13.#14])  //Ljava/lang/annotation/Target;
      1: #15(#12=e#16.#17)   //Ljava/lang/annotation/Retention;
    

    顺便看看RuntimeVisibleAnnotations属性表都有哪些字段

    相关文章

      网友评论

          本文标题:java注解研究

          本文链接:https://www.haomeiwen.com/subject/hwupdqtx.html