问题背景
在使用ServiceComb开发的进程启动时,生成契约偶现不一致(描述偶尔为空,导致赋值了一个默认的描述),导致服务注册不上。开发定位过程中,最后发现是因为getAnnotations这个方法返回的顺序order不一致。所以只能看下源码为啥会出现这种情况。
源码分析
从Method方法跟踪getAnnotations,最终会调用Executable.getDeclaredAnnotations
/**
* {@inheritDoc}
*/
public Annotation[] getDeclaredAnnotations() {
return AnnotationParser.toArray(declaredAnnotations());
}
private transient Map<Class<? extends Annotation>, Annotation> declaredAnnotations;
private synchronized Map<Class<? extends Annotation>, Annotation> declaredAnnotations() {
if (declaredAnnotations == null) {
Executable root = getRoot();
if (root != null) {
declaredAnnotations = root.declaredAnnotations();
} else {
declaredAnnotations = AnnotationParser.parseAnnotations(
getAnnotationBytes(),
sun.misc.SharedSecrets.getJavaLangAccess().
getConstantPool(getDeclaringClass()),
getDeclaringClass());
}
}
return declaredAnnotations;
}
这个是declaredAnnotations是返回一个map,所以需要排查下这个方法返回的AnnotationParser.parseAnnotations 是否有序。
跟踪到AnnotationParser实现里面,这个需要下载openjdk得源码才可以。
private static Map<Class<? extends Annotation>, Annotation> parseAnnotations2(
byte[] rawAnnotations,
ConstantPool constPool,
Class<?> container,
Class<? extends Annotation>[] selectAnnotationClasses) {
Map<Class<? extends Annotation>, Annotation> result =
new LinkedHashMap<Class<? extends Annotation>, Annotation>();
ByteBuffer buf = ByteBuffer.wrap(rawAnnotations);
int numAnnotations = buf.getShort() & 0xFFFF;
for (int i = 0; i < numAnnotations; i++) {
Annotation a = parseAnnotation2(buf, constPool, container, false, selectAnnotationClasses);
if (a != null) {
Class<? extends Annotation> klass = a.annotationType();
if (AnnotationType.getInstance(klass).retention() == RetentionPolicy.RUNTIME &&
result.put(klass, a) != null) {
throw new AnnotationFormatError(
"Duplicate annotation for class: "+klass+": " + a);
}
}
}
return result;
}
rawAnnotations是一个byte数组,是Method.getAnnotationBytes返回的,这个是从class文件加载得到的,编译是什么样的就返回什么样的,map也是一个LinkedHashMap,可以保证顺序。所以正常情况肯定是有序的,所以就排查了代码的问题。
问题原因
最后通过打印getAnnotations数组,发现多了很多其他的注解,就怀疑是不是启动过程中有别的东西对它进行了修改。最终发现是启动过程中设置了一个javaagent,这个agent会织入一些注解,顺序可能会重新排,导致会随机出现一些该问题。