问题前因

在一次技术升级中, 把分布式配置中心组件由百度的Disconf 改成 Nacos , 在对项目进行改造时, 首先将所有Disconf客户端依赖全部移除后, 依赖的封装的jar包中, 所有依赖DIsconf 注解的配置类, 在项目启动时, 本该理所当然的报找不到类信息 , 如下:

JAVA中的元数据区 java类的元数据_父类

但是, 项目却顺利启动成功, 仅仅只是没有获取到Disconf配置中心的数据而已,

排查

后续查看了此类的Class元数据信息, 也能顺利的获取到, 说明类加载器, 在加载此class信息时, 并没有因为类注解的没有加载到,而报错(例如如果父类不存在, 则类无法成功加载), 但是在仔细查看已经加载的注解信息时, 发现不存在的注解被忽略,如下图:

JAVA中的元数据区 java类的元数据_JAVA中的元数据区_02

分别为存在的注解信息, 不存在的注解信息,被忽略;

源码

为了验证这个结果, 可以将断点打到类加载器加载此类的时候,并生成Class对象时(类元数据绑定在Class对象中), 如何加载注解信息, 在Class 对象中, 有这个方法,此方法初始化类的注解信息:

private AnnotationData createAnnotationData(int classRedefinedCount) {
    // 获取类本身的注解
        Map<Class<? extends Annotation>, Annotation> declaredAnnotations =
            AnnotationParser.parseAnnotations(getRawAnnotations(), getConstantPool(), this);
    // 获取父类对象
        Class<?> superClass = getSuperclass();
        Map<Class<? extends Annotation>, Annotation> annotations = null;
    // 如果父类对象不为空,则获取父类上的注解
        if (superClass != null) {
            Map<Class<? extends Annotation>, Annotation> superAnnotations =
                superClass.annotationData().annotations;
            for (Map.Entry<Class<? extends Annotation>, Annotation> e : superAnnotations.entrySet()) {
                Class<? extends Annotation> annotationClass = e.getKey();
                if (AnnotationType.getInstance(annotationClass).isInherited()) {
                    if (annotations == null) { // lazy construction
                        annotations = new LinkedHashMap<>((Math.max(
                                declaredAnnotations.size(),
                                Math.min(12, declaredAnnotations.size() + superAnnotations.size())
                            ) * 4 + 2) / 3
                        );
                    }
                    annotations.put(annotationClass, e.getValue());
                }
            }
        }
    // 合并父类注解和子类注解
        if (annotations == null) {
            // no inherited annotations -> share the Map with declaredAnnotations
            annotations = declaredAnnotations;
        } else {
            // at least one inherited annotation -> declared may override inherited
            annotations.putAll(declaredAnnotations);
        }
        return new AnnotationData(annotations, declaredAnnotations, classRedefinedCount);
    }

其中getRawAnnotations() 方法和getConstantPool() 方法, 都是native原生方法, 大概是获取类注解的符号在内存中字节码信息和地址信息

并交由AnnotationParser对象的parseAnnotations 处理,

public static Map<Class<? extends Annotation>, Annotation> parseAnnotations(byte[] var0, ConstantPool var1, Class<?> var2) {
        if (var0 == null) {
            return Collections.emptyMap();
        } else {
            try {
                return parseAnnotations2(var0, var1, var2, (Class[])null);
            } catch (BufferUnderflowException var4) {
                throw new AnnotationFormatError("Unexpected end of annotations.");
            } catch (IllegalArgumentException var5) {
                throw new AnnotationFormatError(var5);
            }
        }
    }

没有多余的处理过程, 再看parseAnnotations2 方法

private static Map<Class<? extends Annotation>, Annotation> parseAnnotations2(byte[] var0, ConstantPool var1, Class<?> var2, Class<? extends Annotation>[] var3) {
    LinkedHashMap var4 = new LinkedHashMap();
    ByteBuffer var5 = ByteBuffer.wrap(var0);
    int var6 = var5.getShort() & '\uffff';

    // 遍历类注解上声明的注解信息
    for(int var7 = 0; var7 < var6; ++var7) {
        // 获取实际注解对象
        Annotation var8 = parseAnnotation2(var5, var1, var2, false, var3);
        // 如果获取不为空,则返回, 但是不为空, 则没有进行抛错, 而是跳过
        if (var8 != null) {
            Class var9 = var8.annotationType();
            if (AnnotationType.getInstance(var9).retention() == RetentionPolicy.RUNTIME && var4.put(var9, var8) != null) {
                throw new AnnotationFormatError("Duplicate annotation for class: " + var9 + ": " + var8);
            }
        }
    }

    return var4;
}

根据上面的代码可以看到, 如果注解信息没有找到, 并没有抛错处理, 看下debug的情况:

JAVA中的元数据区 java类的元数据_JAVA中的元数据区_03

JAVA中的元数据区 java类的元数据_父类_04

JAVA中的元数据区 java类的元数据_JAVA中的元数据区_05