问题前因
在一次技术升级中, 把分布式配置中心组件由百度的Disconf
改成 Nacos , 在对项目进行改造时, 首先将所有Disconf客户端依赖全部移除后, 依赖的封装的jar包中, 所有依赖DIsconf 注解的配置类, 在项目启动时, 本该理所当然的报找不到类信息
, 如下:
但是, 项目却顺利启动成功, 仅仅只是没有获取到Disconf配置中心的数据而已,
排查
后续查看了此类的Class元数据信息, 也能顺利的获取到, 说明类加载器, 在加载此class信息时, 并没有因为类注解的没有加载到,而报错(例如如果父类不存在, 则类无法成功加载), 但是在仔细查看已经加载的注解信息时, 发现不存在的注解被忽略,如下图:
分别为存在的注解信息, 不存在的注解信息,被忽略;
源码
为了验证这个结果, 可以将断点打到类加载器加载此类的时候,并生成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的情况: