@Target

用于描述注解作用的「对象类型」,这个就非常多了,如下表所示:

@Documented

将注解的元素加入Javadoc中

@Inherited

如果被这个注解标记了,被标记的类、接口会继承父类、接口的上面的注解

@Repeatable

表示该注解可以重复标记

注解的属性

除了元注解之外,我们还能给注解添加属性,注解中的属性以无参方法的形式定义,方法名为属性名,返回值为成员变量的类型,还是以上述注解为例:

首先给这个注解加亿点点细节,生命周期改为Runtime,使得运行期存在可以被我们获取

@Retention(RetentionPolicy.RUNTIME)
public @interface PrintMsg {
    int count() default 1;
    String name() default "my name is PrintMsg";
}

@PrintMsg(count = 2020)
public class AnnotationTest {
    public static void main(String[] args) {
        //通过反射获取该注解
        PrintMsg annotation = AnnotationTest.class.getAnnotation(PrintMsg.class);
        System.out.println(annotation.count());
        System.out.println(annotation.name());
    }
}

输出如下:

2020
my name is PrintMsg

到这里就有两个疑问了:

等下聊

到底什么是注解?

按照注解的生命周期以及处理方式的不同,通常将注解分为「运行时注解」「编译时注解」

运行时注解原理详解

之前我们说注解是一种标记,只是针对注解的作用而言,而Java语言层面注解到底是什么呢?以JSL中的一段话开头

简单来说就是,注解只不过是在interface前面加了@符号的特殊接口,那么不妨以PrintMsg.class开始来看看,通过javap反编译的到信息如下:

public interface com.hustdj.jdkStudy.annotation.PrintMsg extends java.lang.annotation.Annotation
  minor version: 0
  major version: 52
  flags: (0x2601ACC_PUBLICACC_INTERFACEACC_ABSTRACTACC_ANNOTATION
  this_class: #1                          // com/hustdj/jdkStudy/annotation/PrintMsg
  super_class: #3                         // java/lang/Object
  interfaces: 1, fields: 0, methods: 2, attributes: 2
Constant pool:
   #1 
= Class              #2             // com/hustdj/jdkStudy/annotation/PrintMsg
   #2 = Utf8               com/hustdj/jdkStudy/annotation/PrintMsg
   #3 = Class              #4             // java/lang/Object
   #4 = Utf8               java/lang/Object
   #5 = Class              #6             // java/lang/annotation/Annotation
   #6 = Utf8               java/lang/annotation/Annotation
   #7 = Utf8               count
   #8 = Utf8               ()I
   #9 = Utf8               AnnotationDefault
  #10 = Integer            1
  #11 = Utf8               name
  #12 = Utf8               ()Ljava/lang/String;
  #13 = Utf8               my name is PrintMsg
  #14 = Utf8               SourceFile
  #15 = Utf8               PrintMsg.java
  #16 = Utf8               RuntimeVisibleAnnotations
  #17 = Utf8               Ljava/lang/annotation/Retention;
  #18 = Utf8               value
  #19 = Utf8               Ljava/lang/annotation/RetentionPolicy;
  #20 = Utf8               RUNTIME
{
  public abstract int count();
    descriptor: ()I
    flags: (0x0401) ACC_PUBLIC, ACC_ABSTRACT
    AnnotationDefault:
      default_value: I#10

  public abstract java.lang.String name();
    descriptor: ()Ljava/lang/String;
    flags: (0x0401) ACC_PUBLIC, ACC_ABSTRACT
    AnnotationDefault:
      default_value: s#13
}
SourceFile: "PrintMsg.java"
RuntimeVisibleAnnotations:
  0: #17(#18=e#19.#20)

从第一行就不难看出,注解是一个继承自Annotation接口的接口,它并不是一个类,那么getAnnotation()拿到的到底是什么呢?不难想到,通过动态代理生成了代理类,是这样的嘛?通过启动参数-Dsun.misc.ProxyGenerator.saveGeneratedFiles=true或者在上述代码中添加:

System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles","true");将通过JDK的proxyGenerator生成的代理类保存下来在com.sun.proxy文件夹下面找到这个class文件,通过javap反编译结果如下:

public final class com.sun.proxy.$Proxy1 extends java.lang.reflect.Proxy implements com.hustdj.jdkStudy.annotation.PrintMsg

可以看出JDK通过动态代理实现了一个类继承我们自定义的PrintMsg接口,由于这个方法字节码太长了,看起来头疼,利用idea自带的反编译直接在idea中打开该class文件如下:

public final class $Proxy1 extends Proxy
    implements PrintMsg
{

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

    public final boolean equals(Object obj)
    
{
        try
        {
            return ((Boolean)super.h.invoke(this, m1, new Object[] {
                obj
            })).booleanValue();
        }
        catch(Error _ex) { }
        catch(Throwable throwable)
        {
            throw new UndeclaredThrowableException(throwable);
        }
    }

    public final String name()
    
{
        try
        {
            return (String)super.h.invoke(this, m3, null);
        }
        catch(Error _ex) { }
        catch(Throwable throwable)
        {
            throw new UndeclaredThrowableException(throwable);
        }
    }

    public final String toString()
    
{
        try
        {
            return (String)super.h.invoke(this, m2, null);
        }
        catch(Error _ex) { }
        catch(Throwable throwable)
        {
            throw new UndeclaredThrowableException(throwable);
        }
    }

    public final int count()
    
{
        try
        {
            return ((Integer)super.h.invoke(this, m4, null)).intValue();
        }
        catch(Error _ex) { }
        catch(Throwable throwable)
        {
            throw new UndeclaredThrowableException(throwable);
        }
    }

    public final Class annotationType()
    
{
        try
        {
            return (Class)super.h.invoke(this, m5, null);
        }
        catch(Error _ex) { }
        catch(Throwable throwable)
        {
            throw new UndeclaredThrowableException(throwable);
        }
    }

    public final int hashCode()
    
{
        try
        {
            return ((Integer)super.h.invoke(this, m0, null)).intValue();
        }
        catch(Error _ex) { }
        catch(Throwable throwable)
        {
            throw new UndeclaredThrowableException(throwable);
        }
    }

    private static Method m1;
    private static Method m3;
    private static Method m2;
    private static Method m4;
    private static Method m5;
    private static Method m0;

    static 
    {
        try
        {
            m1 = Class.forName("java.lang.Object").getMethod("equals"new Class[] {
                Class.forName("java.lang.Object")
            });
            m3 = Class.forName("com.hustdj.jdkStudy.annotation.PrintMsg").getMethod("name"new Class[0]);
            m2 = Class.forName("java.lang.Object").getMethod("toString"new Class[0]);
            m4 = Class.forName("com.hustdj.jdkStudy.annotation.PrintMsg").getMethod("count"new Class[0]);
            m5 = Class.forName("com.hustdj.jdkStudy.annotation.PrintMsg").getMethod("annotationType"new Class[0]);
            m0 = Class.forName("java.lang.Object").getMethod("hashCode"new Class[0]);
        }
        catch(NoSuchMethodException nosuchmethodexception)
        {
            throw new NoSuchMethodError(nosuchmethodexception.getMessage());
        }
        catch(ClassNotFoundException classnotfoundexception)
        {
            throw new NoClassDefFoundError(classnotfoundexception.getMessage());
        }
    }
}

小结

至此就解决了第一个疑问了,「所谓的注解其实就是一个实现了Annotation的接口,而我们通过反射获取到的实际上是通过JDK动态代理生成的代理类,这个类实现了我们的注解接口」

AnnotationInvocationHandler

那么问题又来了,具体是如何调用的呢?

$Proxy1的count方法为例

public final int count()
{
    try
    {
        return ((Integer)super.h.invoke(this, m4, null)).intValue();
    }
    catch(Error _ex) { }
    catch(Throwable throwable)
    {
        throw new UndeclaredThrowableException(throwable);
    }
}

跟进super

public class Proxy implements java.io.Serializable {
    protected InvocationHandler h;
}

这个InvocationHandler是谁呢?通过在Proxy(InvocationHandler h)方法上打断点追踪结果如下:

原来我们对于count方法的调用传递给了AnnotationInvocationHandler

看看它的invoke逻辑

public Object invoke(Object var1, Method var2, Object[] var3) {
    //var4-方法名
    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:
                //因为我们是count方法,走这个分支
                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);
                    }
     //返回var6
                    return var6;
                }
        }
    }
}

这个memberValues是啥?

private final Map<String, Object> memberValues;

他是一个map,存放的是方法名(String)与值的键值对

这里以count()方法的invoke执行为例

可以看到它走了default的分支,从上面的map中取到了,我们所定义的2020,那这个memberValues是什么时候解析出来的呢?

通过查看方法调用栈,我们发现在下图这个时候countname还没有赋值

在方法中加入断点重新调试得到如下结果

2020出现了,再跟进parseMemberValue方法中,再次重新调试

再跟进parseConst方法

康康javap反编译的字节码中的常量池吧

#71 = Integer            2020

好巧啊,正好是2020!!

因此发现最后是从ConstantPool中根据偏移量来获取值的,至此另一个疑问也解决了,我们在注解中设置的方法,最终在调用的时候,是从一个以<方法名,属性值>为键值对的map中获取属性值,定义成方法只是为了在反射调用作为参数而已,所以也可以将它看成属性吧。

总结

运行时注解的产生作用的步骤如下:

编译时注解初探

由于编译时注解的很多处理逻辑内化在Javac中,这里不做过多探讨,仅对《深入理解JVM》中的知识点进行梳理和总结。

在JDK5中,Java语言提供了对于注解的支持,此时的注解只在程序运行时发挥作用,但是在JDK6中,JDK新加入了一组插入式注解处理器的标准API,这组API使得我们对于注解的处理可以提前至编译期,从而影响到前端编译器的工作!!常用的Lombok就是通过注解处理器来实现的

「自定义简单注解处理器」

实现自己的注解处理器,首先需要继承抽象类javax.annotation.processing.AbstractProcessor,只有process()方法需要我们实现,process()方法如下:

//返回值表示是否修改Element元素
public abstract boolean process(Set<? extends TypeElement> annotations,
                                RoundEnvironment roundEnv)
;

此外还有一个重要的实例变量processingEnv,它提供了上下文环境,需要创建新的代码,向编译器输出信息,获取其他工具类都可以通过它

实现一个简单的编译器注解处理器也非常简单,继承AbstractProcessor实现process()方法,在process()方法中实现自己的处理逻辑即可,此外需要两个注解配合一下:

「实例」

@SupportedAnnotationTypes("com.hustdj.jdkStudy.annotation.PrintMsg")
@SupportedSourceVersion(SourceVersion.RELEASE_8)
public class PrintNameProcessor extends AbstractProcessor {

    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        Messager messager = processingEnv.getMessager();
        for (Element element : roundEnv.getRootElements()) {
            messager.printMessage(Diagnostic.Kind.NOTE,"my name is "+element.toString());
        }
        //不修改语法树,返回false
        return false;
    }
}

输出如下:

G:\ideaIU\ideaProjects\cookcode\src\main\java>javac com\hustdj\jdkStudy\annotation\PrintMsg.java

G:\ideaIU\ideaProjects\cookcode\src\main\java>javac com\hustdj\jdkStudy\annotation\PrintNameProcessor.java

G:\ideaIU\ideaProjects\cookcode\src\main\java>javac -processor com.hustdj.jdkStudy.annotation.PrintNameProcessor com\hustdj\jdkStudy\annotation\AnnotationTest.java
警告: 来自注释处理程序 'com.hustdj.jdkStudy.annotation.PrintNameProcessor' 的受支持 source 版本 'RELEASE_8' 低于 -source '1.9'
注: my name is com.hustdj.jdkStudy.annotation.AnnotationTest
1 个警告


来源: cnblogs.com/danzZ/p/14142814.html

作者: 南风知我不易

--完--



   

原来注解是这么实现的啊!-LMLPHP

本文分享自微信公众号 - web项目开发(javawebkaifa)。
如有侵权,请联系 [email protected] 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。

04-04 22:20