Java注解(Annotation)详解

本文详细解析Java注解的工作原理,包括注解的定义、使用、分类及其在编译和运行时的应用。探讨了注解如何增强代码元数据,以及如何通过反射机制读取和处理注解信息。

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

注解: 提供一种为程序设置元数据的方法
元数据:

  • 是添加到程序元素如:字段,方法,类,包上的额外信息。
  • 注解是一种分散式的元数据设置方式,xml是一种集中式的元数据设置方式。
  • 注解的基本原则:注解不能直接干扰到程序代码运行
    注解的分类:
  • 标准注解: 如 Override @Deprecated(功能就是不推荐使用或者已经不在维护或者使用有风险的) @SuppressWarnings
    (忽列某一个warning)
    元注解(用于修饰注解的注解,通常定义在注解上) Retention Target() @Inherited @Documented)
    自定义注解 :根据实际需要定义一些特定的注解。
    注解的作用:
  • 作为特定的标记,用于告知编译器一些信息。
  • 编译时动态处理,如生成代码,如lombok的@Data.
  • 运行时动态处理,作为额外信息载体,如获取注解信息

自定义注解继承java.lang.annotation.Annotation
使用

javac 加要编译的java文件

在使用

javap -verbose 加上之前编译好的class文件

查看反编译的结果

public interface com.example.annotations.UserAnnotation extends java.lang.annotation.Annotation

Target分析:
Target注解的作用目标。有以下:

public enum ElementType {
    /**类声明 */
    TYPE,

    /** 成员变量声明 */
    FIELD,

    /** 方法声明 */
    METHOD,

    /**参数声明 */
    PARAMETER,

    /**构造器 */
    CONSTRUCTOR,

    /**局部变量声明 */
    LOCAL_VARIABLE,

    /** 注解声明(应用于另一个注解上) */
    ANNOTATION_TYPE,

    /** 包声明 */
    PACKAGE,

    /**
     * 类型参数声明
     */
    TYPE_PARAMETER,

    /**
     * 类型使用声明
     */
    TYPE_USE
}

Retention(保留):
Retention需要传入RetentionPolicy(保留政策)的枚举类(默认是class):

public enum RetentionPolicy {
    /**
     *注解将被编译器丢弃(该类型的注解信息只会保留在源码里,源码经过编译          后,注解信息会被丢弃,不会保留在编译好的class文件里)
     */
    SOURCE,

    /**
     注解在class文件中可用,但会被VM丢弃(该类型的注解信息会保留在源码里和class文件里,在执行的时候,不会加载到虚拟机(JVM)中)
     */
    CLASS,

    /**
     VM将在运行期也保留注解信息,因此可以通过反射机制读取注解的信息(源码、class文件和执行的时候都有注解的信息)
     */
    RUNTIME
}

@Documented 表示注解会被包含在javaapi文档中
@Inherited 允许子类继承父类的注解

自定义注解的实例:
自定义一个UserAnnotation实例作用在类上和方法及成员变量,注意自定义注解不能继承也不能实现,否则编译不过。

@Target({ElementType.TYPE, ElementType.FIELD, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface UserAnnotation {

    public String name() default "张三";

    public int age() default 90;

    public String gender();

    public String addr();

    public String desc() default "简单的描述";
}

注解的使用:

package com.example.annotations;

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

@UserAnnotation(name = "作用在类上的UserAnnotation的名字", age = 100, gender = "作用在类上的", addr = "作用在类上的哦",
        desc = "作用在类上的")
public class UserInfo {

    private String name;

    private int age;

    @UserAnnotation(gender = "作用在成员变上的UserAnnotation",addr = "在成员变量上的注解哦")
    private String gender;

    private String desc;

    private String addr;

    @UserAnnotation(gender = "作用在方法上的UserAnnotation",addr = "在方法上的注解哦")
    public void analysis() {
        System.out.println("测试java注解");
    }
    public static void main(String[] args) throws ClassNotFoundException {
        AnnotationUtils.annotationByClass();
        AnnotationUtils.annotationByMethod();
        AnnotationUtils.annotationByFiled();
    }
}

如果不同过反射来对这些注解进行操作,实际上注解是对程序没有多大的作用,需要通过方式来对注解的信息进行操作,如通过自定义三个方法(通过反射)解析注解在类上和成员变量及方法上的三个方法:

package com.example.annotations;


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

/**
 * 解析作用在类上的注解
 */
public class AnnotationUtils {

    public static void annotationByClass() throws ClassNotFoundException {
        // 通过反射的方式获取这个UserInfo的信息
        Class<?> aClass = Class.forName("com.example.annotations.UserInfo");
        // 获取作用在UserInfo类上的注解Annotation
        Annotation[] annotations = aClass.getAnnotations();
        for (Annotation annotation : annotations) {
            UserAnnotation userAnnotation = (UserAnnotation) annotation;
            System.out.println("name:" + userAnnotation.name() + "\n" + "age:" + userAnnotation.age() + "\n"
                    + "addr:" + userAnnotation.addr() + "\n" + "gender:" + userAnnotation.gender() + "\n" +
                    "desc:" + userAnnotation.desc());
        }
    }

    public static void annotationByMethod() throws ClassNotFoundException {
        // 通过反射的方式获取这个UserInfo的信息
        Class<?> aClass = Class.forName("com.example.annotations.UserInfo");
        Method[] declaredMethods = aClass.getDeclaredMethods();
        for (Method method : declaredMethods) {
            boolean hashAnnotation = method.isAnnotationPresent(UserAnnotation.class);
            if (hashAnnotation) {
                UserAnnotation userAnnotation = method.getAnnotation(UserAnnotation.class);
                System.out.println("name:" + userAnnotation.name() + "\n" + "age:" + userAnnotation.age() + "\n"
                        + "addr:" + userAnnotation.addr() + "\n" + "gender:" + userAnnotation.gender() + "\n" +
                        "desc:" + userAnnotation.desc());
            }
        }
    }

    public static void annotationByFiled() throws ClassNotFoundException {
        // 通过反射的方式获取这个UserInfo的信息
        Class<?> aClass = Class.forName("com.example.annotations.UserInfo");
        Field[] fields = aClass.getDeclaredFields();
        for (Field field : fields) {
            boolean hashPresent = field.isAnnotationPresent(UserAnnotation.class);
            if (hashPresent) {
                UserAnnotation userAnnotation = field.getAnnotation(UserAnnotation.class);
                System.out.println("name:" + userAnnotation.name() + "\n" + "age:" + userAnnotation.age() + "\n"
                        + "addr:" + userAnnotation.addr() + "\n" + "gender:" + userAnnotation.gender() + "\n" +
                        "desc:" + userAnnotation.desc());
            }
        }
    }
}

注解原理
通过设置jvm参数为-Dsun.misc.ProxyGenerator.saveGeneratedFiles=true -XX:+TraceClassLoading
通过在idea里找到这个测试类的Edit Configurations 点击进去然后设置,然后在VM options 设置参数-Dsun.misc.ProxyGenerator.saveGeneratedFiles=true
表示通过jdk动态代理生成器。运行该配置的main方法,则jdk动态代理生成器会在根目录生成一个com.sun.proxy下会有 P r o x y 0. c l a s s 和 Proxy0.class和 Proxy0.classProxy1.class的代理对象。

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

package com.sun.proxy;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;

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

    public $Proxy0(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 RetentionPolicy value() throws  {
        try {
            return (RetentionPolicy)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("java.lang.annotation.Retention").getMethod("annotationType");
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
            m3 = Class.forName("java.lang.annotation.Retention").getMethod("value");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}

从上面$Proxy0 可知,public final class KaTeX parse error: Expected '}', got 'EOF' at end of input: …究java注解的原理,直接查看Proxy1。从$Proxy1中public final class $Proxy1 extends Proxy implements UserAnnotation实现了我们上文定义的UserAnnotation的注解。查看源码$Proxy1定义了从m0到m8九个静态成员变量,代码如下:

    private static Method m1;
    private static Method m7;
    private static Method m5;
    private static Method m2;
    private static Method m6;
    private static Method m4;
    private static Method m8;
    private static Method m3;
    private static Method m0;

这九个静态成员变量一一对应着:

static {
        try {
            m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
            m7 = Class.forName("com.example.annotations.UserAnnotation").getMethod("name");
            m5 = Class.forName("com.example.annotations.UserAnnotation").getMethod("addr");
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m6 = Class.forName("com.example.annotations.UserAnnotation").getMethod("gender");
            m4 = Class.forName("com.example.annotations.UserAnnotation").getMethod("age");
            m8 = Class.forName("com.example.annotations.UserAnnotation").getMethod("annotationType");
            m3 = Class.forName("com.example.annotations.UserAnnotation").getMethod("desc");
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }

除了m0的hashCode和m1的Object及m2的toString的成员变量以外其他六个都是UserAnnotation的成员变量.通过查看$Proxy1的构造函数:

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

构造函数传入InvocationHandler对象并传给super(var1),通过查看super()的源码:

protected Proxy(InvocationHandler h) {
        Objects.requireNonNull(h);
        this.h = h;
    }

super()实际就是InvocationHandler本身.通过阅读代码发现m0到m8都会对应一一对应着一个方法,并且方法的返回值就是成员变量的类型。以m3的desc为例:

m3 = Class.forName("com.example.annotations.UserAnnotation").getMethod("desc");
 public final String desc() throws  {
        try {
            return (String)super.h.invoke(this, m3, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

点击desc()方法里的invoke(),进去了以后发现
public interface InvocationHandler只是一个接口invoke没有具体的实现,和上文中的一样在VM options后面打一个空格然后接上

-XX:+TraceClassLoading

查看那个这个main用多少类,以达到追踪到那个实现类实现了InvocationHandler接口,控制台的打印结果中发现

[Loaded sun.reflect.annotation.AnnotationInvocationHandler from /app/jdk1.8/jre/lib/rt.jar]

是AnnotationInvocationHandler继承了InvocationHandler并实现了其中的invoke方法.其中invke的源码如下:

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;
                }
            }
        }
    }

其中除了toString,还有hashCode,type外,var6需要从memberValues中取值,而memberValues是一个map的键值对:

private final Map<String, Object> memberValues;

memberValues存储了UserAnnotation的属性值,key注解的成员变量的名称,vaule则为注解的值.拿到map的值后会判断如果是Array数组则返回数组的副本,
注解原理:若为单个则返回单个元素.
从上面的代跟踪可以得出以下注解的实际运行原理如下:
1 通过键值对的形式为注解属性赋值
2 在编译器检查注解的使用范围,将注解信息写入元素属性表中
3 运行时jvm将runtime所有注解属性取出来并最终存在map中
4 然后创建AnnotationInvocationHandler实例并传入前面的map

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值