• Java运行时生成类元数据,初始化注解信息的方式


    问题前因

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

    image-20220803175120622

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

    排查

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

    image-20220803175836921

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

    源码

    为了验证这个结果, 可以将断点打到类加载器加载此类的时候,并生成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的情况:

    image-20220803181425695

    image-20220803181509047

    image-20220803181639036

  • 相关阅读:
    如何拷贝CMD命令行文本到粘贴板
    Linux 系统时钟(date) 硬件时钟(hwclock)
    Android AIDL自动生成Java文件测试
    Windows Tftpd32 DHCP服务器 使用
    Cmockery macro demo hacking
    Linux setjmp longjmp
    GrepCode
    Windows bat with adb
    点分十进制IP校验、转换,掩码校验
    子网掩码、掩码长度关系
  • 原文地址:https://www.cnblogs.com/xjwhaha/p/16548211.html
Copyright © 2020-2023  润新知