Java 反射中的 AccessFlag(访问标志)

1、概览

Java 中的反射是一个强大的功能,它允许我们操纵不同的成员,如类、接口、字段和方法。此外,使用反射,我们可以在编译时实例化类、调用方法和访问字段,而无需知道类型。

本文将带你了解如何使用 JVM AccessFlag(访问标志),以及 ModifierAccessFlag 之间的区别。

2、JVM AccessFlag

Java 虚拟机规范 定义了 JVM 中已编译类的结构,它由一个 ClassFile 组成:

ClassFile {
    u4             magic;
    u2             minor_version;
    u2             major_version;
    u2             constant_pool_count;
    cp_info        constant_pool[constant_pool_count-1];
    u2             access_flags;
    u2             this_class;
    u2             super_class;
    u2             interfaces_count;
    u2             interfaces[interfaces_count];
    u2             fields_count;
    field_info     fields[fields_count];
    u2             methods_count;
    method_info    methods[methods_count];
    u2             attributes_count;
    attribute_info attributes[attributes_count];
}

除其他项目外,ClassFile 还包含 access_flags 项。简而言之,access_flags 是一个掩码,由定义类的访问权限和其他属性的各种标志组成。

此外,ClassFile 还包括 field_infomethod_info 项,每个项都包含其 access_flags 项。

JavassistASM 等库使用 JVM AccessFlag 来操作 Java 字节码。

除 Java 外,JVM 还支持其他语言,如 KotlinScala。每种语言都定义了自己的 Modifier。例如,Java 中的 Modifier 类包含 Java 编程语言特有的所有 Modifier。此外,在使用反射时,我们通常会依赖从这些类中获取的信息。

然而,当需要将 Modifier 转换为 JVM AccessFlag 时,问题出现了。

3、Modifier 的 AccessFlag

varargstransientvolatilebridgeModifier 使用相同的整数位掩码。为了解决不同修饰符之间的位冲突问题,Java 20 引入了 AccessFlag 枚举,其中包括我们可以在类、字段或方法中使用的所有修饰符。

枚举模拟了 JVM 的 AccessFlag,从而简化了 ModifierAccessFlag 之间的映射。如果没有 AccessFlag 枚举,我们就需要考虑元素的上下文来确定使用哪个 Modifier,尤其是那些具有精确位表示的 Modifier

创建一个具多个方法的 AccessFlagDemo 类,每个方法都使用不同的修饰符:

public class AccessFlagDemo {
    public static final void staticFinalMethod() {
    }

    public void varArgsMethod(String... args) {
    }

    public strictfp void strictfpMethod() {
    }
}

来看一下 staticFinalMethod() 方法中使用的 AccessFlag

@Test
void givenStaticFinalMethod_whenGetAccessFlag_thenReturnCorrectFlags() throws Exception {
    Class<?> clazz = Class.forName(AccessFlagDemo.class.getName());

    Method method = clazz.getMethod("staticFinalMethod");
    Set<AccessFlag> accessFlagSet = method.accessFlags();

    assertEquals(3, accessFlagSet.size());
    assertTrue(accessFlagSet.contains(AccessFlag.PUBLIC));
    assertTrue(accessFlagSet.contains(AccessFlag.STATIC));
    assertTrue(accessFlagSet.contains(AccessFlag.FINAL));
}

如上,我们调用了 accessFlags() 方法,该方法返回了一个包装在不可修改集合中的 EnumSet。在内部,该方法使用了 getModifiers() 方法,并根据可以应用标志的位置返回 AccessFlag。我们的方法包含三个访问标志:PUBLICSTATICFINAL

此外,从 Java 17 开始,strictfp Modifier 已经是多余的了,不再编译到字节码中:

@Test
void givenStrictfpMethod_whenGetAccessFlag_thenReturnOnlyPublicFlag() throws Exception {
    Class<?> clazz = Class.forName(AccessFlagDemo.class.getName());
    Method method = clazz.getMethod("strictfpMethod");

    Set<AccessFlag> accessFlagSet = method.accessFlags();

    assertEquals(1, accessFlagSet.size());
    assertTrue(accessFlagSet.contains(AccessFlag.PUBLIC));
}

我们可以看到,strictfpMethod() 包含一个 AccessFlag。

4、getModifiers() 和 accessFlags() 方法

在 Java 中使用反射时,我们经常使用 getModifiers() 方法来检索定义在类、接口、方法或字段上的所有 modifier(修饰符):

@Test
void givenStaticFinalMethod_whenGetModifiers_thenReturnIsStaticTrue() throws Exception {
    Class<?> clazz = Class.forName(AccessFlagDemo.class.getName());
    Method method = clazz.getMethod("staticFinalMethod");

    int methodModifiers = method.getModifiers();

    assertEquals(25, methodModifiers);
    assertTrue(Modifier.isStatic(methodModifiers));
}

getModifiers() 方法返回一个 integer 值,表示编码后的 modifier 标志。我们调用 Modifier 类中定义的 isStatic() 方法来检查该方法是否包含 static 修饰符,Java 会对方法内部的标志进行解码,以确定方法是否为 static 方法。

此外,需要注意的是,AccessFlag 与 Java 中定义的 Modifier 并不相同。有些 AccessFlag 和修饰符(如 public)是一一对应的。然而,有些修饰符(如 sealed)没有指定的 AccessFlag。同样,我们也无法将某些访问标志(如 synthetic)映射到相应的 Modifier 值。

更进一步说,由于某些修饰符共享精确的位表示,如果我们不考虑修饰符的上下文,就可能得出错误的结果。

varArgsMethod() 上调用 Modifier.toString()

@Test
void givenVarArgsMethod_whenGetModifiers_thenReturnPublicTransientModifiers() throws Exception {
    Class<?> clazz = Class.forName(AccessFlagDemo.class.getName());
    Method method = clazz.getMethod("varArgsMethod", String[].class);

    int methodModifiers = method.getModifiers();

    assertEquals("public transient", Modifier.toString(methodModifiers));
}

该方法返回的结果是 public transient。如果不考虑上下文,我们可能会得出 varArgsMethod()transient 的结论,但这并不准确。

只有考虑到正确的上下文环境,才能得到正确的结果:

@Test
void givenVarArgsMethod_whenGetAccessFlag_thenReturnPublicVarArgsFlags() throws Exception {
    Class<?> clazz = Class.forName(AccessFlagDemo.class.getName());
    Method method = clazz.getMethod("varArgsMethod", String[].class);

    Set<AccessFlag> accessFlagSet = method.accessFlags();

    assertEquals("[PUBLIC, VARARGS]", accessFlagSet.toString());
}

5、总结

本文介绍了什么是 JVM Access Flag(访问标志)以及如何使用它们。

总而言之,JVM Access Flag 包含访问权限信息和运行时成员(如类、方法和字段)的其他属性。我们可以利用 AccessFlag 来获取特定元素上 Modifier 的准确信息。


Ref:https://www.baeldung.com/java-reflection-accessflag-modifiers