您链接的规范指出:
要解析,使用以下四个步骤来解析 字节码行为中MH
对类、接口、字段和方法的所有符号引用:MH
R
已解决。当的字节码行为是种类 1、2、3 或 4时,这就像通过字段解析(§5.4.3.2)发生,并且当的字节码行为是种类 5时,好像通过方法解析( §5.4.3.3)发生, 6、7 或 8,并且好像通过接口方法解析(§5.4.3.4)当的字节码行为是种类 9。MH
MH
MH
链接的章节,即针对字段的§5.4.3.2,描述了普通的解析过程,包括访问控制。即使没有该显式声明,您也可以从前面的描述中推导出访问控制的存在,即这些符号方法句柄引用应该等同于列出的特定字节码行为。
因此,通过类文件常量池的条目获取的直接方法句柄CONSTANT_MethodHandle_info
无法访问字节码指令也无法直接访问的类或成员。
但是从 JDK 11 开始,您可以使用动态常量来加载由任意引导过程定义的任意类型的常量。因此,当您可以用 Java 代码表达如何获取常量时,例如使用privateLookupIn
,您还可以将其定义为动态常量的引导,并将该常量加载到您将加载直接方法句柄的位置。
考虑以下起点:
public class DynConstant {
private static void inacessibleMethod() {
new Exception("inacessibleMethod() called").printStackTrace();
}
public static void main(String[] args) throws Throwable {
// express the constant
Handle theHandle = new Handle(H_INVOKESTATIC,
Type.getInternalName(DynConstant.class), "inacessibleMethod",
Type.getMethodDescriptor(Type.VOID_TYPE), false);
String generatedClassName
= DynConstant.class.getPackageName().replace('.', '/')+"/Test";
ClassWriter cw = new ClassWriter(0);
cw.visit(55, ACC_INTERFACE|ACC_ABSTRACT,
generatedClassName, null, "java/lang/Object", null);
MethodVisitor mv = cw.visitMethod(
ACC_PUBLIC|ACC_STATIC, "test", "()V", null, null);
mv.visitCode();
mv.visitLdcInsn(theHandle);
mv.visitMethodInsn(INVOKEVIRTUAL,
"java/lang/invoke/MethodHandle", "invokeExact", "()V", false);
mv.visitInsn(RETURN);
mv.visitMaxs(1, 0);
mv.visitEnd();
cw.visitEnd();
byte[] code = cw.toByteArray();
ToolProvider.findFirst("javap").ifPresentOrElse(javap -> {
String fName = generatedClassName+".class";
try {
Path dir = Files.createTempDirectory("javapTmp");
Path classFile = dir.resolve(fName);
Files.createDirectories(classFile.getParent());
Files.write(classFile, code);
javap.run(System.out, System.err, "-c", "-cp",
dir.toAbsolutePath().toString(), generatedClassName);
for(Path p = classFile;;p=p.getParent()) {
Files.delete(p);
if(p.equals(dir)) break;
}
} catch (IOException ex) {
throw new UncheckedIOException(ex);
}
}, () -> System.out.println("javap not found in current environment"));
try {
MethodHandles.Lookup lookup = MethodHandles.lookup();
lookup.findStatic(lookup.defineClass(code),
"test", MethodType.methodType(void.class)).invokeExact();
}
catch(Throwable t) {
t.printStackTrace();
}
}
}
它尝试定义一个新的运行时类,该类MethodHandle
尝试inacessibleMethod()
通过CONSTANT_MethodHandle_info
. 程序打印
interface instexamples.Test {
public static void test();
Code:
0: ldc #12 // MethodHandle REF_invokeStatic instexamples/DynConstant.inacessibleMethod:()V
2: invokevirtual #17 // Method java/lang/invoke/MethodHandle.invokeExact:()V
5: return
}
java.lang.IllegalAccessError: class instexamples.Test tried to access private method 'void instexamples.DynConstant.inacessibleMethod()' (instexamples.Test and instexamples.DynConstant are in unnamed module of loader 'app')
at instexamples.Test.test(Unknown Source)
at instexamples.DynConstant.main(DynConstant.java:100)
现在,让我们将常量更改为动态常量,该常量将执行等价于
MethodHandles.Lookup l = MethodHandles.lookup();
l = MethodHandles.privateLookupIn(DynConstant.class, l);
MethodHandle mh = l.findStatic(
DynConstant.class, "inacessibleMethod", MethodType.methodType(void.class));
当常数第一次解决时。常数的定义“有点”复杂。由于代码包含三个方法调用,因此该定义需要三个方法句柄,此外,还需要一个现有引导方法的句柄ConstantBootstraps.invoke(…)
,允许使用任意方法调用进行引导。这些句柄可用于定义动态常量,而动态常量允许作为另一个动态常量的常量输入。
所以我们将// express the constant
注释后的定义替换为:
Type string = Type.getType(String.class), clazz = Type.getType(Class.class);
Type oArray = Type.getType(Object[].class), object = oArray.getElementType();
Type mhLookup = Type.getType(MethodHandles.Lookup.class);
Type mHandle = Type.getType(MethodHandle.class), mType = Type.getType(MethodType.class);
Type targetType = Type.getType(DynConstant.class);
String methodHandles = Type.getInternalName(MethodHandles.class);
Handle methodHandlesLookup = new Handle(H_INVOKESTATIC, methodHandles,
"lookup", Type.getMethodDescriptor(mhLookup), false);
Handle privateLookupIn = new Handle(H_INVOKESTATIC, methodHandles,
"privateLookupIn", Type.getMethodDescriptor(mhLookup, clazz, mhLookup), false);
Handle findStatic = new Handle(H_INVOKEVIRTUAL, mhLookup.getInternalName(),
"findStatic", Type.getMethodDescriptor(mHandle, clazz, string, mType), false);
Handle invoke = new Handle(H_INVOKESTATIC,
Type.getInternalName(ConstantBootstraps.class), "invoke",
Type.getMethodDescriptor(object, mhLookup, string, clazz, mHandle, oArray), false);
ConstantDynamic methodHandlesLookupC = new ConstantDynamic("lookup",
mhLookup.getDescriptor(), invoke, methodHandlesLookup);
ConstantDynamic privateLookupInC = new ConstantDynamic("privateLookupIn",
mhLookup.getDescriptor(), invoke, privateLookupIn, targetType, methodHandlesLookupC);
ConstantDynamic theHandle = new ConstantDynamic("findStatic",
mHandle.getDescriptor(), invoke, findStatic,
privateLookupInC, targetType, "inacessibleMethod", Type.getMethodType("()V"));
为了避免重复很长的常量方法描述符字符串,我使用了 ASM 的Type
抽象。原则上,我们可以为所有类型名称和签名使用常量字符串。
该程序打印:
interface instexamples.Test {
public static void test();
Code:
0: ldc #45 // Dynamic #2:findStatic:Ljava/lang/invoke/MethodHandle;
2: invokevirtual #50 // Method java/lang/invoke/MethodHandle.invokeExact:()V
5: return
}
java.lang.Exception: inacessibleMethod() called
at instexamples.DynConstant.inacessibleMethod(DynConstant.java:23)
at instexamples.Test.test(Unknown Source)
at instexamples.DynConstant.main(DynConstant.java:89)
由方法调用创建的三个常量组成的动态常量的复杂性将导致相当大的常量池。尽管我们有一个额外的方法,但我们可能会生成一个自定义引导方法并获得一个小得多的类文件:
public class DynConstant {
private static void inacessibleMethod() {
new Exception("inacessibleMethod() called").printStackTrace();
}
public static void main(String[] args) throws Throwable {
Type string = Type.getType(String.class), clazz = Type.getType(Class.class);
Type mhLookup = Type.getType(MethodHandles.Lookup.class);
Type mHandle = Type.getType(MethodHandle.class), mType = Type.getType(MethodType.class);
Type targetType = Type.getType(DynConstant.class);
String myBootstrapName = "privateLookup";
String myBootstrapDesc = Type.getMethodDescriptor(mHandle, mhLookup, string, clazz, clazz, mType);
String generatedClassName = DynConstant.class.getPackageName().replace('.', '/')+"/Test";
Handle myBootStrap = new Handle(H_INVOKESTATIC, generatedClassName,
myBootstrapName, myBootstrapDesc, true);
ConstantDynamic theHandle = new ConstantDynamic("inacessibleMethod",
mHandle.getDescriptor(), myBootStrap, targetType, Type.getMethodType("()V"));
ClassWriter cw = new ClassWriter(0);
cw.visit(55, ACC_INTERFACE|ACC_ABSTRACT, generatedClassName, null, "java/lang/Object", null);
MethodVisitor mv = cw.visitMethod(ACC_PUBLIC|ACC_STATIC, "test", "()V", null, null);
mv.visitCode();
mv.visitLdcInsn(theHandle);
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/invoke/MethodHandle", "invokeExact", "()V", false);
mv.visitInsn(RETURN);
mv.visitMaxs(1, 0);
mv.visitEnd();
mv = cw.visitMethod(ACC_PRIVATE|ACC_STATIC, myBootstrapName, myBootstrapDesc, null, null);
mv.visitCode();
mv.visitVarInsn(ALOAD, 3); // bootstrap argument, i.e. DynConstant.class
mv.visitVarInsn(ALOAD, 0); // MethodHandles.lookup() generated as JVM arg
mv.visitMethodInsn(INVOKESTATIC, "java/lang/invoke/MethodHandles", "privateLookupIn",
Type.getMethodDescriptor(mhLookup, clazz, mhLookup), false);
mv.visitVarInsn(ALOAD, 3); // bootstrap argument, i.e. DynConstant.class
mv.visitVarInsn(ALOAD, 1); // invoked name, i.e. "inacessibleMethod"
mv.visitVarInsn(ALOAD, 4); // bootstrap argument, i.e. MethodType ()V
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/invoke/MethodHandles$Lookup", "findStatic",
Type.getMethodDescriptor(mHandle, clazz, string, mType), false);
mv.visitInsn(ARETURN);
mv.visitMaxs(4, 5);
mv.visitEnd();
cw.visitEnd();
byte[] code = cw.toByteArray();
ToolProvider.findFirst("javap").ifPresentOrElse(javap -> {
String fName = generatedClassName+".class";
try {
Path dir = Files.createTempDirectory("javapTmp");
Path classFile = dir.resolve(fName);
Files.createDirectories(classFile.getParent());
Files.write(classFile, code);
javap.run(System.out, System.err, "-p", "-c", "-cp",
dir.toAbsolutePath().toString(), generatedClassName);
for(Path p = classFile;;p=p.getParent()) {
Files.delete(p);
if(p.equals(dir)) break;
}
} catch (IOException ex) {
throw new UncheckedIOException(ex);
}
}, () -> System.out.println("javap not found in current environment"));
try {
MethodHandles.Lookup lookup = MethodHandles.lookup();
lookup.findStatic(lookup.defineClass(code),
"test", MethodType.methodType(void.class)).invokeExact();
}
catch(Throwable t) {
t.printStackTrace();
}
}
}
interface instexamples.custombootstrap.Test {
public static void test();
Code:
0: ldc #18 // Dynamic #0:inacessibleMethod:Ljava/lang/invoke/MethodHandle;
2: invokevirtual #23 // Method java/lang/invoke/MethodHandle.invokeExact:()V
5: return
private static java.lang.invoke.MethodHandle privateLookup(java.lang.invoke.MethodHandles$Lookup, java.lang.String, java.lang.Class, java.lang.Class, java.lang.invoke.MethodType);
Code:
0: aload_3
1: aload_0
2: invokestatic #29 // Method java/lang/invoke/MethodHandles.privateLookupIn:(Ljava/lang/Class;Ljava/lang/invoke/MethodHandles$Lookup;)Ljava/lang/invoke/MethodHandles$Lookup;
5: aload_3
6: aload_1
7: aload 4
9: invokevirtual #35 // Method java/lang/invoke/MethodHandles$Lookup.findStatic:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/MethodHandle;
12: areturn
}
java.lang.Exception: inacessibleMethod() called
at instexamples.custombootstrap.DynConstant.inacessibleMethod(DynConstant.java:22)
at instexamples.custombootstrap.Test.test(Unknown Source)
at instexamples.custombootstrap.DynConstant.main(DynConstant.java:91)
bootstrap 方法被设计为可重用。它接收所有必要的信息作为常量参数,因此不同的ldc
指令可以使用它来获取不同成员的句柄。JVM 确实已经将调用者的查找上下文作为第一个参数传递了,所以我们可以使用它而不需要调用MethodHandles.lookup()
. 搜索成员的类是第一个附加参数,它用作两者的第一个参数,privateLookupIn
和findStatic
。由于每个动态常量都有一个标准名称参数,我们可以使用它来表示成员的名称。最后一个参数表示MethodType
要查找的方法。当我们为字段查找进行改造时,我们可以删除该参数,作为第三个标准参数,预期的常量类型可以与预期的字段类型匹配。
基本上,自定义引导方法会执行privateLookupIn
您在问题中描述的基于查找,但使用它ldc
允许进行延迟初始化(而不是字段的类初始化时间),同时在链接指令后static final
仍像字段一样进行优化。static final
此外,这些动态常量允许作为其他动态常量或指令的其他引导方法的常量输入(不过,您也可以使用此引导方法invokedynamic
将现有static final
字段调整为动态常量)。