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

jen*_*ens 1 java jvm bytecode

我在理解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
Run Code Online (Sandbox Code Playgroud)

有谁知道这里发生了什么?下面是复制问题的代码,我在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();

}
}
Run Code Online (Sandbox Code Playgroud)

Ant*_*ony 7

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

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

public class TestModifiers {
// this is the class to be tested
private class InnerClass {}
}
Run Code Online (Sandbox Code Playgroud)

拆卸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
Run Code Online (Sandbox Code Playgroud)

您可能会注意到,类文件没有private在访问标志中设置标志(它拥有的唯一标志是super,为所有普通类设置).这并不奇怪,因为ACC_PRIVATE实际上不是有效的类文件访问标志(JVMS8,第71页).因此,当您通过ASM检查类文件访问标志时,您自然会得到错误的结果.

但是,该类确实具有一个InnerClasses属性,并且此属性具有private其访问标志,因为ACC_PRIVATE是内部类属性的有效访问标志(JVMS8,第116页).

是不是java.lang.Class.getModifiers()从内部类属性获取数据?好吧,检查有点棘手.该方法是本机方法.在此处检查源显示它调用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);
Run Code Online (Sandbox Code Playgroud)

所以你有它.JVM_GetClassModifiers实际上检查了InnerClasses属性,而JVM_GetClassAccessFlags仅检查类文件访问标志,这与您对ASM的操作相同.