有 Java 编程相关的问题?

你可以在下面搜索框中键入要查询的问题!

Java规范中私有内部类的jvm访问标志与反射API不一致?

我在理解Java内部类的访问标志(尤其是私有)的使用时遇到了问题。我在字节码中找到的标志似乎与反射API提供的信息不一致

我附上以下程序来说明这个问题。该程序有一个私有的内部类,并使用三种不同的方法进行分析:

  1. 使用JClassLib检查类文件,并将访问标志与来自JVM Spec的def进行比较
  2. 使用反射API
  3. 使用ASM 5.0

令人惊讶的是,这会给出不同的结果,这是输出:

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();

}
}

共 (1) 个答案

  1. # 1 楼答案

    首先,您的第一个方法实际上并没有检查任何东西,它只是显示一个常量false。所以真正的问题是为什么后两种方法给出不同的结果

    要了解实际情况,我们可以从编译测试类开始

    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实际上不是有效的类文件访问标志(JVM8,第71页)。因此,当您通过ASM检查类文件访问标志时,自然会得到错误的结果

    然而,该类确实有一个InnerClasses属性,并且该属性在其访问标志中有private,因为ACC_PRIVATE是内部类属性的有效访问标志(JVM8,第116页)

    可能是java。朗,同学们。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相当