我在理解Java中内部类的访问标志(特别是私有)的使用时遇到了问题。我在字节代码中找到的标志似乎与反射API提供的信息不一致。
我附上以下程序来说明问题。该程序有一个私有内部类,并使用三种不同的方法进行自我分析:
令人惊讶的是,这给出了不同的结果,这是输出:
inner class is private (inspection): false
inner class is private (reflection): true
inner class is private (ASM): false
有人知道这里发生了什么吗?下面是代码来复制问题,我在Mac上使用了JRE build 1.8.0_05-b13来运行它。
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.Opcodes;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.lang.reflect.Modifier;
public class TestModifiers {
// this is the class to be tested
private class InnerClass {}
public static void main(String[] args) throws Exception {
// check whether class is private using inspection, and comparison with standard
int flags = 0x0020; // inspect class file using JClassLib
int private_flag = 0x0002; // acc to http://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.7.6-300-D.1-D.1
System.out.println("inner class is private (inspection): " + ((flags & private_flag) == private_flag));
// check whether class is private using reflection
Class inner = InnerClass.class;
System.out.println("inner class is private (reflection): " + Modifier.isPrivate(inner.getModifiers()));
// now try to do the same by reading byte code using ASM
String PATH_TO_CLASSFILES = "<replace by path to class file>";
File classFile = new File(PATH_TO_CLASSFILES+"TestModifiers$InnerClass.class");
InputStream in = new FileInputStream(classFile);
class Visitor extends ClassVisitor {
public Visitor() {
super(Opcodes.ASM5);
}
@Override
public void visit(final int version, final int access, final String name,final String signature, final String superName,final String[] interfaces) {
boolean isPrivate = ((access & Opcodes.ACC_PRIVATE) == Opcodes.ACC_PRIVATE);
System.out.println("inner class is private (ASM): " + isPrivate);
}
}
new ClassReader(in).accept(new Visitor(), 0);
in.close();
}
}
答案 0 :(得分:7)
首先,你的第一个方法实际上并没有检查任何东西,只是显示一个常量的假。所以真正的问题是为什么后两种方法会产生不同的结果。
要了解真正发生的事情,我们可以从编译测试类开始
public class TestModifiers {
// this is the class to be tested
private class InnerClass {}
}
反汇编TestModifiers$InnerClass.class
给出了
.version 51 0
.source TestModifiers.java
.class super TestModifiers$InnerClass
.super java/lang/Object
.inner private InnerClass TestModifiers$InnerClass TestModifiers
.field synthetic final this$0 LTestModifiers;
.method private <init> : (LTestModifiers;)V
; method code size: 10 bytes
.limit stack 2
.limit locals 2
aload_0
aload_1
putfield TestModifiers$InnerClass this$0 LTestModifiers;
aload_0
invokespecial java/lang/Object <init> ()V
return
.end method
正如您所注意到的,类文件没有在访问标志中设置private
标志(它拥有的唯一标志是super
,它是为所有普通类设置的)。这并不奇怪,因为ACC_PRIVATE实际上不是有效的类文件访问标志(JVMS8,第71页)。因此,当您通过ASM检查类文件访问标志时,您自然会得到错误的结果。
但是,该类确实具有InnerClasses
属性,并且此属性在其访问标志中具有private
,因为ACC_PRIVATE是内部类属性的有效访问标志(JVMS8,第116页)。
java.lang.Class.getModifiers()是否可以从内部类属性中获取数据?嗯,检查起来有点棘手。该方法是本机方法。 Checking the source here表明它调用了JVM_GetClassModifiers。这包含在标题jvm.h中,其中包含一个有趣的评论。
/* Differs from JVM_GetClassModifiers in treatment of inner classes.
This returns the access flags for the class as specified in the
class file rather than searching the InnerClasses attribute (if
present) to find the source-level access flags. Only the values of
the low 13 bits (i.e., a mask of 0x1FFF) are guaranteed to be
valid. */
JNIEXPORT jint JNICALL
JVM_GetClassAccessFlags(JNIEnv *env, jclass cls);
所以你有它。 JVM_GetClassModifiers实际上检查了InnerClasses属性,而JVM_GetClassAccessFlags只检查类文件访问标志,这与您对ASM的操作相同。