You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Java如何安全初始化注解内部声明的默认实例字段

Java注解内部定义默认实例循环依赖问题修复方案

问题背景

开发者尝试在注解内部定义静态常量作为当前注解的默认实例,代码如下:

import java.lang.annotation.*;
import java.util.function.Supplier;

public class AnnoTester {

    @Retention(RetentionPolicy.RUNTIME)
    @Target({ ElementType.FIELD, ElementType.TYPE })
    public static @interface Anno {
        Anno DEFAULT = annotation(() -> {
            @Anno
            class Default {}
            return Default.class;
        }, Anno.class);

        String s() default "s";
    }

    @Anno(s = "T")
    public static class TestClass {}

    private static <T extends Annotation> T annotation(Supplier<Class<?>> supplier,
        Class<T> annotationClass) {
        return supplier.get().getAnnotation(annotationClass);
    }

    public static void main(String[] args) {
        // 移除此行代码会触发空指针异常
        // System.out.println(Anno.DEFAULT.s());
        System.out.println(TestClass.class.getAnnotation(Anno.class).s());
    }
}

异常现象

删除main方法中对Anno.DEFAULT的引用后,程序抛出初始化异常,核心栈追踪如下:

Exception in thread "main" java.lang.ExceptionInInitializerError
    at java.base/java.lang.Class.forName0(Native Method)
    at java.base/java.lang.Class.forName(Class.java:375)
    at jdk.proxy2/jdk.proxy2.$Proxy1.<clinit>(Unknown Source)
    at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
    at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:78)
    at java.base/jdk.internal.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
    at java.base/java.lang.reflect.Constructor.newInstanceWithCaller(Constructor.java:499)
    at java.base/java.lang.reflect.Constructor.newInstance(Constructor.java:480)
    at java.base/java.lang.reflect.Proxy.newProxyInstance(Proxy.java:1044)
    at java.base/java.lang.reflect.Proxy.newProxyInstance(Proxy.java:1030)
    at java.base/sun.reflect.annotation.AnnotationParser$1.run(AnnotationParser.java:306)
    at java.base/sun.reflect.annotation.AnnotationParser$1.run(AnnotationParser.java:304)
    at java.base/java.security.AccessController.doPrivileged(AccessController.java:312)
    at java.base/sun.reflect.annotation.AnnotationParser.annotationForMap(AnnotationParser.java:304)
    at java.base/sun.reflect.annotation.AnnotationParser.parseAnnotation2(AnnotationParser.java:294)
    at java.base/sun.reflect.annotation.AnnotationParser.parseAnnotations2(AnnotationParser.java:121)
    at java.base/sun.reflect.annotation.AnnotationParser.parseAnnotations(AnnotationParser.java:73)
    at java.base/java.lang.Class.createAnnotationData(Class.java:3997)
    at java.base/java.lang.Class.annotationData(Class.java:3986)
    at java.base/java.lang.Class.getAnnotation(Class.java:3869)
    at AnnoTester.main(AnnoTester.java:30)
Caused by: java.lang.NullPointerException: Cannot invoke "java.lang.reflect.Method.getName()" because "method" is null
    at java.base/sun.reflect.annotation.AnnotationInvocationHandler.invoke(AnnotationInvocationHandler.java:61)
    at jdk.proxy2/jdk.proxy2.$Proxy1.annotationType(Unknown Source)
    at java.base/sun.reflect.annotation.AnnotationParser.parseAnnotations2(AnnotationParser.java:123)
    at java.base/sun.reflect.annotation.AnnotationParser.parseAnnotations(AnnotationParser.java:73)
    at java.base/java.lang.Class.createAnnotationData(Class.java:3997)
    at java.base/java.lang.Class.annotationData(Class.java:3986)
    at java.base/java.lang.Class.getAnnotation(Class.java:3869)
    at AnnoTester.annotation(AnnoTester.java:25)
    at AnnoTester$Anno.<clinit>(AnnoTester.java:11)
    ... 21 more

根因分析

该问题是JVM类初始化循环依赖导致:

  • 解析TestClass上的@Anno注解时,会触发Anno注解类的初始化
  • Anno类初始化需要完成静态常量DEFAULT的赋值,调用annotation方法获取Default类上的@Anno注解
  • 此时Anno类还未完成初始化,注解代理的调用处理器AnnotationInvocationHandler未加载到annotationType()方法的元数据,调用时触发空指针

当代码中提前引用Anno.DEFAULT时,会先触发Anno类初始化流程,后续解析TestClass注解时代理元数据已加载完成,因此不会报错。

修复方案(无需移出DEFAULT字段)

只需修改annotation方法,在返回注解实例前主动调用一次annotationType(),提前完成元数据缓存即可:

private static <T extends Annotation> T annotation(Supplier<Class<?>> supplier, Class<T> annotationClass) {
    T annotationInstance = supplier.get().getAnnotation(annotationClass);
    // 主动触发annotationType方法调用,提前缓存方法元数据,规避循环依赖空指针
    annotationInstance.annotationType();
    return annotationInstance;
}

方案验证

修改后无论是否保留main方法中对Anno.DEFAULT的引用,程序都能正常输出s和T,符合预期,且无需调整DEFAULT字段的定义位置。


内容的提问来源于stack exchange,提问作者boot-and-bonnet

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.06 10:57:03