实战:掌控方法分派规则
invokedynamic指令与此前4条传统的“invoke*”指令的最大区别就是它的分派逻辑不是虚拟机决定的,而是由程序员决定的。
方法调用问题,举个例子:
package com.jvm.test;
public class invoketest {
class GrandFather{
void thinking(){
System.out.println("i am grandfather");
}
}
class Father extends GrandFather{
void thinking(){
System.out.println("i am father");
}
}
class Son extends Father{
void thinking(){
//填入适当的代码(不能修改其他地方的代码)
//实现调用祖父类的thinking()方法,打印“ i am grandfather”
}
}
}
在Java程序中,可通过“super”关键字很方便地调用父类中的方法,但如果要访问祖类的方法呢?
在拥有invokedynamic和java.lang.invoke包之前,使用纯粹的Java语言很难处理这个问题(使用ASM等字节码工具直接生成字节码可以处理,但这已经是在字节码而不是Java语言层面),原因是在Son类的thinking()方法中根本无法获取到一个实际类型是GrandFather的对象引用,而invokevirtual指令的分派逻辑是固定的,只能按照方法接收者的实际类型进行分派,这个逻辑完全固话在虚拟机中,程序员无法改变。
如果是JDK7 Update9之前,可使用以下代码来解决该问题。
package com.jvm.test;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
public class invoketest {
class GrandFather{
void thinking(){
System.out.println("i am grandfather");
}
}
class Father extends GrandFather{
void thinking(){
System.out.println("i am father");
}
}
class Son extends Father{
void thinking(){
try{
MethodType mt = MethodType.methodType(void.class);
MethodHandle mh = MethodHandles.lookup().findSpecial(
GrandFather.class,
"thinking",
mt,
getClass());
mh.invoke(this);
}catch(Throwable e){
}
}
}
}
运行结果:
i am grandfather
但是这个逻辑在JDK7 Update9之后被视作一个潜在的安全性缺陷修正了,原因是必须保证findSpecial()查找方法版本时受到的访问约束(如对访问控制的限制、对参数类型的限制)应与使用invokespecial指令一样,两者必须保持精确对等,包括在上面的场景中它只能访问到其父类中的方法版本。所以在JDK7 Update10修正之后,运行以上代码得到的结果如下:
i am father
查看MethodHandles.Lookup类的代码,在该API实现时预留了后门。访问保护是通过一个allowedModes的参数控制,而且这个参数可以被设置成“TRUSTED”来绕开所有 的保护措施。尽管这个参数只是在Java类库本身是引用,没有开放给外部设置,但我们通过反射可轻易打破这种限制。由此,我们可把代码修改成如下来解决问题:
package com.jvm.test;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.lang.reflect.Field;
public class invoketest {
class GrandFather{
void thinking(){
System.out.println("i am grandfather");
}
}
class Father extends GrandFather{
void thinking(){
System.out.println("i am father");
}
}
class Son extends Father{
void thinking(){
try{
MethodType mt = MethodType.methodType(void.class);
Field lookupIMPL = MethodHandles.Lookup.class.getDeclaredField("IMPL_LOOKUP");
lookupIMPL.setAccessible(true);
MethodHandle mh = ((MethodHandles.Lookup) lookupIMPL.get(null))
.findSpecial(
GrandFather.class,
"thinking",
mt,
GrandFather.class);
mh.invoke(this);
}catch(Throwable e){
}
}
}
}
《深入理解Java虚拟机》第三版 学习
网友评论