Java高级特性——Java注解介绍与底层实现原理(下)

本文详细介绍了Java注解的使用,包括自定义注解的实践,如@MyAnnotationField、@MyAnnotationMethod等。通过源码分析,揭示了注解的工作原理,解释了注解的本质是继承自Annotation的接口,并通过动态代理实现。同时,文章探讨了注解属性值的获取方式,指出这些值在运行时从常量池中读取。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

每天进步一点,不做curd工程师与Api调用工程师!

欢迎大家访问我的博客:https://coder-programming.cn/

为了防止篇幅过长不易阅读,在介绍Java注解时。分为两篇文章进行详细介绍。

Java高级特性——Java注解介绍与底层实现原理(下)

内容如下

Java注解

通过上一篇的介绍Java高级特性——Java注解介绍与底层实现原理(上),概念比较多。虽然多,但是我们也要去理解背下来,说不定哪天你在面试的时候面试官就对你进行发问呢!

对相关的知识点有了初步的后,我们来结合实际联系一下如果自己写自定义注解,并进一步了解其底层原理实现。

实现自定义注解

我们分别实现4个自定义注解,并进行测试。

  • @MyAnnotationField:自定义成员注解
  • @MyAnnotationMethod:自定义方法注解
  • @MyAnnotationParameter:自定义参数注解
  • @MyAnnotationType:自定义类注解

代码示例

MyAnnotationType.java

/**
 * @author Coder编程
 * @Title: MyAnnotationType
 * @ProjectName simple-framework
 * @Description: 定义一个可以注解在Class,interface,enum上的注解
 * @date 2021/4/22 14:15
 */
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotationType {

    /**
     * 定义注解的一个元素 并给定默认值
     * @return
     */
    String value() default "我是定义在类接口枚举类上的注解元素value的默认值";
}

MyAnnotationMethod.java

/**
 * @author Coder编程
 * @Title: MyAnnotationMethod
 * @ProjectName simple-framework
 * @Description:  定义一个可以注解在METHOD上的注解
 * @date 2021/4/22 14:17
 */
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotationMethod {
    /**
     * 定义注解的一个元素 并给定默认值
     * @return
     */
    String value() default "我是定义在方法上的注解元素value的默认值";


}

MyAnnotationField.java

/**
 * @author Coder编程
 * @Title: MyAnnotationField
 * @ProjectName simple-framework
 * @Description: 定义一个可以注解在FIELD上的注解
 * @date 2021/4/22 14:17
 */
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotationField {
    /**
     * 定义注解的一个元素 并给定默认值
     * @return
     */
    String value() default "我是定义在字段上的注解元素value的默认值";

    /**
     * 定义注解一个type类型
     * @return
     */
    int type();
}

MyAnnotationParameter.java

/**
 * @author Coder编程
 * @Title: MyAnnotationParameter
 * @ProjectName simple-framework
 * @Description: TODO
 * @date 2021/4/22 14:20
 */
@Target({ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotationParameter {
    /**
     * 定义注解的一个元素 并给定默认值
     * @return
     */
    String value() default "我是定义在参数上的注解元素value的默认值";

    /**
     * 定义注解类型
     * @return
     */
    int type() default 1;

}

测试

package demo.annotation;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;

/**
 * @author Coder编程
 * @Title: TestAnnotation
 * @ProjectName simple-framework
 * @Description: TODO
 * @date 2021/4/22 14:20
 */
@MyAnnotationType
public class TestAnnotation {

    @MyAnnotationField(type = 1)
    private String field = "我是字段";

    @MyAnnotationMethod("测试方法")
    public void methodTest(@MyAnnotationParameter("测试参数")String str){
        System.out.println("我是测试方法,Str = " + str);
    }

    /**
     * 获取类上的注解
     * @throws ClassNotFoundException
     */
    public static void parseTypeAnnotation() throws ClassNotFoundException {
        //获取类上的注解
        MyAnnotationType annotation = TestAnnotation.class.getAnnotation(MyAnnotationType.class);
        System.out.println("类上注解"+annotation);

        //另外一种方法
        Class clazz = Class.forName("demo.annotation.TestAnnotation");
        /*获取到类上的所有注解,只在类上,不包括成员,方法上的注解*/
        Annotation[] annotations = clazz.getAnnotations();
        for (Annotation annotation1 : annotations) {
            MyAnnotationType myAnnotationType = (MyAnnotationType)annotation1;
            System.out.println(myAnnotationType.value());
        }
    }

    /**
     * 获取字段上的注解
     * @throws ClassNotFoundException
     */
    public static void parseFieldAnnotation() throws ClassNotFoundException {
        Class clazz = Class.forName("demo.annotation.TestAnnotation");
        Field[] declaredFields = clazz.getDeclaredFields();
        for (Field declaredField : declaredFields) {
            //判断成员变量中是否有指定注解类型的注解
            boolean hasAnnotation = declaredField.isAnnotationPresent(MyAnnotationField.class);
            if(hasAnnotation){
                MyAnnotationField annotation = declaredField.getAnnotation(MyAnnotationField.class);
                System.out.println(annotation.value());
            }
        }
    }

    /**
     * 获取方法,参数上的注解
     * @throws ClassNotFoundException
     * @throws NoSuchMethodException
     */
    public static void parseMethodAnnotation() throws ClassNotFoundException, NoSuchMethodException {
        Class clazz = Class.forName("demo.annotation.TestAnnotation");
        Method methodTest = clazz.getDeclaredMethod("methodTest", String.class);
        MyAnnotationMethod annotation = methodTest.getAnnotation(MyAnnotationMethod.class);
        System.out.println(annotation.value());

        Annotation[][] parameterAnnotations = methodTest.getParameterAnnotations();
        for (Annotation[] parameterAnnotation : parameterAnnotations) {
            for (Annotation annotation1 : parameterAnnotation) {
                if(annotation1 instanceof MyAnnotationParameter){
                    System.out.println("方法参数上的注解:"+((MyAnnotationParameter) annotation1).value());
                }
            }
        }
    }


    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException {
        //parseTypeAnnotation();
        parseFieldAnnotation();
        //parseMethodAnnotation();
    }
}

大家可以将以上代码多多动手,实战敲一下,加深印象!上面有涉及到 Java的反射内容,我的文章中好像还没写过,下次补上。不太了解的同学也可以自行查询下。

注解实现原理

通过第一篇文章,我们知道了什么是注解,通过上面的自定义注解练习,我们又加深了对注解的印象。可是好像总是感觉少了点什么?注解的工作原理是什么?它是怎么获取到属性的值?注解的本质到底是什么?是接口呢?还是抽象类呢?

我们下来看下我们MyAnnotationType的类关系图。

IDEA工具图1

IDEA工具图2

可以发现MyAnnotationType底层是继承了Annotation接口。当然,我们除了用这种方法,还可以通过查看字节码的方式来查看MyAnnotationType 底层到底是啥。

这里我们的命令是:javap -v MyAnnotation.class 注:-v 是 -verbose 缩写

$ javap -v MyAnnotationType.class
Classfile /E:/Github/simple-framework/target/classes/demo/annotation/MyAnnotationType.class
  Last modified 2021-4-23; size 530 bytes
  MD5 checksum 142d541c8ff55e25c20881f705f9d876
  Compiled from "MyAnnotationType.java"
public interface demo.annotation.MyAnnotationType extends java.lang.annotation.Annotation
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_INTERFACE, ACC_ABSTRACT, ACC_ANNOTATION
Constant pool:
   #1 = Class              #17            // demo/annotation/MyAnnotationType
   #2 = Class              #18            // java/lang/Object
   #3 = Class              #19            // java/lang/annotation/Annotation
   #4 = Utf8               value
   #5 = Utf8               ()Ljava/lang/String;
   #6 = Utf8               AnnotationDefault
   #7 = Utf8               我是定义在类接口枚举类上的注解元素value的默认值
   #8 = Utf8               SourceFile
   #9 = Utf8               MyAnnotationType.java
  #10 = Utf8               RuntimeVisibleAnnotations
  #11 = Utf8               Ljava/lang/annotation/Target;
  #12 = Utf8               Ljava/lang/annotation/ElementType;
  #13 = Utf8               TYPE
  #14 = Utf8               Ljava/lang/annotation/Retention;
  #15 = Utf8               Ljava/lang/annotation/RetentionPolicy;
  #16 = Utf8               RUNTIME
  #17 = Utf8               demo/annotation/MyAnnotationType
  #18 = Utf8               java/lang/Object
  #19 = Utf8               java/lang/annotation/Annotation
{
  public abstract java.lang.String value();
    descriptor: ()Ljava/lang/String;
    flags: ACC_PUBLIC, ACC_ABSTRACT
    AnnotationDefault:
      default_value: s#7}
SourceFile: "MyAnnotationType.java"
RuntimeVisibleAnnotations:
  0: #11(#4=[e#12.#13])
  1: #14(#4=e#15.#16)

很明显,中间写了public interface demo.annotation.MyAnnotationType extends java.lang.annotation.Annotation 因此,可以看出注解的本质是 继承Annotation接口

接下来我们继续探索。

我们用IDEA断点来调试下我们 parseTypeAnnotation()方法

断点调试

为了能更清晰的看到代码中间运行的过程。我们需要在IDEA 启动的时候加入一些参数:

-Djdk.proxy.ProxyGenerator.saveGeneratedFiles=true//记录JDK动态代理生成器,将生成代理类的文件保留下来

VM ptions中写入

设置参数

运行完程序后,我们会得到一些文件

生成文件

我们直接通过IDEA打开 $proxy1 文件。

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package com.sun.proxy;

import demo.annotation.MyAnnotationType;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;

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

    public $Proxy1(InvocationHandler var1) throws  {
        super(var1);
    }

    public final boolean equals(Object var1) throws  {
        try {
            return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

    public final String toString() throws  {
        try {
            return (String)super.h.invoke(this, m2, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final Class annotationType() throws  {
        try {
            return (Class)super.h.invoke(this, m4, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final int hashCode() throws  {
        try {
            return (Integer)super.h.invoke(this, m0, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final String value() throws  {
        try {
            return (String)super.h.invoke(this, m3, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    static {
        try {
            m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m4 = Class.forName("demo.annotation.MyAnnotationType").getMethod("annotationType");
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
            m3 = Class.forName("demo.annotation.MyAnnotationType").getMethod("value");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}

小结

走到这里,我们做一次小结:我们通过 IDEA 工具图和字节码看出我们的MyAnnotationType注解(接口)其实是继承了Annotation接口的,我们通过断点的方式和设置jdk启动参数查看到了MyAnnotationType注解的中间过程,它其实 是Java运行时生成的动态代理对象$Proxy1,该类就MyAnnotationType注解(接口)的具体实现类。

动态代理类$Proxy1是如何处理annotation.value()方法的调用?

这里我们需要去补充一下Java中动态代理的知识。这里暂时不过多介绍,下次补上。需要知道的是:动态代理方法的调用最终会传递给绑定的InvocationHandler实例的invoke方法处理。
我们继续看源码

$Proxy1
Proxy1

我们的value()方法法最终会执行(String)super.h.invoke(this, m3, (Object[])null);,而这其中的h对象类型就是InvocationHandler接口的某个实现类

Proxy
Proxy

我们通过Proxy源码进行断点调试的时候发现是AnnotationInbocationHandler

我们通过使用IDEA 搜索到这个AnnotationInbocationHandler 这个类。找到 invoke这个方法。

class AnnotationInvocationHandler implements InvocationHandler, Serializable {
    private static final long serialVersionUID = 6182022883658399397L;
    private final Class<? extends Annotation> type;
    private final Map<String, Object> memberValues;
    private transient volatile Method[] memberMethods;

    AnnotationInvocationHandler(Class<? extends Annotation> type, Map<String, Object> memberValues) {
        Class<?>[] superInterfaces = type.getInterfaces();
        if (type.isAnnotation() && superInterfaces.length == 1 && superInterfaces[0] == Annotation.class) {
            this.type = type;
            this.memberValues = memberValues;
        } else {
            throw new AnnotationFormatError("Attempt to create proxy for a non-annotation type.");
        }
    }

    public Object invoke(Object proxy, Method method, Object[] args) {
        String member = method.getName();
        int parameterCount = method.getParameterCount();
        if (parameterCount == 1 && member == "equals" && method.getParameterTypes()[0] == Object.class) {
            return this.equalsImpl(proxy, args[0]);
        } else if (parameterCount != 0) {
            throw new AssertionError("Too many parameters for an annotation method");
        } else if (member == "toString") {
            return this.toStringImpl();
        } else if (member == "hashCode") {
            return this.hashCodeImpl();
        } else if (member == "annotationType") {
            return this.type;
        } else {
            Object result = this.memberValues.get(member);
            if (result == null) {
                throw new IncompleteAnnotationException(this.type, member);
            } else if (result instanceof ExceptionProxy) {
                throw ((ExceptionProxy)result).generateException();
            } else {
                if (result.getClass().isArray() && Array.getLength(result) != 0) {
                    result = this.cloneArray(result);
                }

                return result;
            }
        }
    }
****

我们源码中打上断点,再来改下我们的type值。

@MyAnnotationType("我是注解类")
public class TestAnnotation {
    .....
}

通过main函数中调用 parseTypeAnnotation()方法:

/**
     * 获取类上的注解
     * @throws ClassNotFoundException
     */
    public static void parseTypeAnnotation() throws ClassNotFoundException {
        //获取类上的注解
        MyAnnotationType annotation = TestAnnotation.class.getAnnotation(MyAnnotationType.class);
        System.out.println("类上注解"+annotation);

        //另外一种方法
        Class clazz = Class.forName("demo.annotation.TestAnnotation");
        /*获取到类上的所有注解,只在类上,不包括成员,方法上的注解*/
        Annotation[] annotations = clazz.getAnnotations();
        for (Annotation annotation1 : annotations) {
            MyAnnotationType myAnnotationType = (MyAnnotationType)annotation1;
            System.out.println(myAnnotationType.value());
        }
    }

AnnotationInvoocationHandler

可以看到value()方法的返回值是从一个Map中获取到的。

这个map private final Map<String, Object> memberValues;

以key(注解方法名)— value(注解方法对应的值)存储MyAnnotationType类上的注解.

那么我们的memerValues的值又是从哪里来的呢?我们在断点的时候可以跟这个IDEA的调用栈通过按F8的方式重新来一遍,发现会在AnnotationParser这个类中进行赋值。

AnnotationParser

在断点的时候,可以发现在memberValues.put(memberName, value);的位置进行设值,那么我们再往上看 memberName和 value是怎么取值的呢?

我们会发现memerName是通过:

String memberName = constPool.getUTF8At(memberNameIndex);

我们在看看value是怎么取值的?

Object value = parseMemberValue(memberType, buf, constPool, container);

发现是调用了parseMemberValue方法,我们进入该方法AnnotationParser2

最后进入了parseConst()方法,我们继续进入该方法。

AnnotationParser3

发现最后调用的是constPool.getUTF8At()。通过百度:我们可以知道constPool就是常量池的意思,也就是说这些值是存储在常量池中的。constIndex也就是常量池的序号。

我们再看下整个过程:

AnnotationParser4

还有一种方法,可以通过查看字节码的方式,可以看到底层是从常量池取的。由于代码过长,我这里只放部分截图

字节码文件

这个#96 = Utf8 我是注解类 就是 在常量池中取的。

小结

通过代理对象调用自定义注解(接口)的方法,会最终调用AnnotationInvocationHandler的invoke方法。该方法会从memberValues这个Map中索引出对应的值。而memberValues的来源是Java常量池。

总结

我们通过两篇文章,对注解有了更深入的了解。知道了注解的概念、功能、分类、元注解、如何自定义注解、以及注解的工作原理。

  • 注解是通过键值对的形式为注解属性赋值
  • 通过元注解定义注解使用范围,编译器会检查,并将注解信息写入元素属性表
  • 运行时JVM将RUNTIME的所有注解属性取出并存入map里
  • map中的值是从常量池中获取
  • 创建AnnotationInvoocationHandler实例并传入前面的map
  • JVM使用JDK动态代理为注解生成代理类,并初始化处理器
  • 调用invoke方法,通过传入方法名返回注解对应的属性值

关于上面提出的疑问:注解的工作原理是什么?它是怎么获取到属性的值?注解的本质到底是什么?是接口呢?还是抽象类呢?相信面试官再问到关于注解的相关知识点。你一定没有问题!

面试顺利通过

参考

https://blog.csdn.net/lylwo317/article/details/52163304

文末

文章收录至
Github: https://github.com/CoderMerlin/coder-programming
Gitee: https://gitee.com/573059382/coder-programming

欢迎关注并star~

微信公众号

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值