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
相关产品推荐
相关产品推荐

