Java 反射中的 AccessFlag(访问标志)
1、概览
Java 中的反射是一个强大的功能,它允许我们操纵不同的成员,如类、接口、字段和方法。此外,使用反射,我们可以在编译时实例化类、调用方法和访问字段,而无需知道类型。
本文将带你了解如何使用 JVM AccessFlag(访问标志),以及 Modifier
和 AccessFlag
之间的区别。
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_info
和 method_info
项,每个项都包含其 access_flags
项。
Javassist 和 ASM 等库使用 JVM AccessFlag
来操作 Java 字节码。
除 Java 外,JVM 还支持其他语言,如 Kotlin 或 Scala。每种语言都定义了自己的 Modifier。例如,Java 中的 Modifier
类包含 Java 编程语言特有的所有 Modifier。此外,在使用反射时,我们通常会依赖从这些类中获取的信息。
然而,当需要将 Modifier
转换为 JVM AccessFlag
时,问题出现了。
3、Modifier 的 AccessFlag
varargs
和 transient
或 volatile
和 bridge
等 Modifier
使用相同的整数位掩码。为了解决不同修饰符之间的位冲突问题,Java 20 引入了 AccessFlag 枚举,其中包括我们可以在类、字段或方法中使用的所有修饰符。
枚举模拟了 JVM 的 AccessFlag
,从而简化了 Modifier
和 AccessFlag
之间的映射。如果没有 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
。我们的方法包含三个访问标志:PUBLIC
、STATIC
和 FINAL
。
此外,从 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