java高级开发

JVM 动态调用句柄 MethodHandle

2024-09-26  本文已影响0人  老鼠AI大米_Java全栈

一、MethodHandle概述

Q:MethodHandle是什么?
A:方法句柄类似于反射框架中的Method,只不过其功能更为强大、效率也更高
Q:MethodHandle比起反射框架中的对应类有什么不同?
A:(1)表示范围更广。MethodHandle不仅仅只是能够代表Method,它更像是反射框架中Method、Field、Constructor三个类的抽象整合。(2)效率更高。MethodHandle在方法的执行过程中将不会和反射一样会去检查访问权限之类的东西。(3)更加安全。方法查找阶段将会严格按照访问控制权限来执行,并不像反射一样可以访问控制权限之外的东西。(4)扩展性强。方法句柄能够对执行时候的参数、返回值、异常等部分进行扩展,比如目标方法需要2个参数,但是我想在实际使用时只想让使用者输入1个参数(也就是说我会默认提供一个参数),这就可以通过方法句柄的转换来完成。(5)可以和反射框架协同使用。

二、创建MethodHandle的三个基本步骤

  1. 方法类型的确定
  2. 获取方法查找的客户端
  3. 使用方法查找的客户端查找指定方法类型、名字、访问权限等等的方法,成功后返回代表对应的方法的方法句柄(MethodHandle)

以创建Math#max方法的句柄为例:

//创建一个参数类型为`int,int`,返回值类型为`int`的方法类型实例
MethodType type = MethodType.fromMethodDescriptorString("(II)I",null);
//获取方法查找的客户端
MethodHandles.Lookup lookup = MethodHandles.lookup();
//通过该客户端查找`Math`类中名字为`max`方法类型为type的静态方法
MethodHandle maxHandle = lookup .findStatic(Math.class , "max" , type);

三、MethodType详解

四、MethodHandles.Lookup详解

五、MethodHandle详解

MethodType methodType = MethodType.fromMethodDescriptorString("(II)Ljava/lang/String;" , null);
MethodHandle mh = MethodHandles.lookup().findVirtual(String.class , "substring" , methodType);
//由于System.out.println只接受Object类型的参数,故使用asType进行转换
mh = mh.asType(MethodType.fromMethodDescriptorString("(Ljava/lang/String;II)Ljava/lang/Object;" , null));
System.out.println(mh.invokeExact("hello" , 1 , 3));

六、MethodHandles详解

int[] arr = new int[]{1,2,3,4,5,6};
MethodHandle arrHandle = MethodHandles.arrayElementSetter(int[].class);
arrHandle = arrHandle.bindTo(arr);
//将index为2的数组元素改为9
arrHandle.invoke(2,9);
for(int item : arr){
    System.out.println(item);
}
//结果:1 2 9 4 5 6
MethodHandle dropArgHandle = MethodHandles.lookup().findVirtual(String.class , "toString" , MethodType.fromMethodDescriptorString("()Ljava/lang/String;" , null));
dropArgHandle = MethodHandles.dropArguments(dropArgHandle , 0 , String.class , int.class);
System.out.println(dropArgHandle.invoke("123" , 2 ,"hello"));
//结果:hello

为原句柄添加预先绑定的参数值的转换:第一个参数为待转换的句柄,第二个参数为要开始绑定参数的位置,第三个需要被绑定的参数值列表。相当于加强版的bindTo

MethodHandle bindArgHandle = MethodHandles.lookup().findVirtual(String.class , "concat" , MethodType.fromMethodDescriptorString("(Ljava/lang/String;)Ljava/lang/String;" , null));
bindArgHandle = MethodHandles.insertArguments(bindArgHandle , 1 , "654");
System.out.println(bindArgHandle.invoke("123"));
//结果:123654

为参数/返回值添加过滤机制的转换:MethodHandles#filterArguments/MethodHandles#filterReturnValue,前者接受三个参数,第一个参数是原句柄,第二个参数是需要添加过滤机制的位置,第三个参数是用于过滤的方法句柄。后者接受两个参数,第一个参数是原句柄,第二个参数是用于过滤的方法句柄。过滤的方法句柄应该满足这些原则:参数过滤的句柄的返回值类型应该和被过滤的参数类型一致,但是输入类型可以随意,返回值过滤的句柄的输入类型应当和被过滤的返回类型一致,但是输出类型可以随意。(参数->过滤参数句柄->过滤后的参数->原方法句柄->返回值->过滤返回值句柄->过滤后的返回值)

public class Test {
    private int fieldInt = 2;
    public Test(int fieldInt){
        this.fieldInt = fieldInt;
    }
    public int getSum(int otherInt){
        return fieldInt + otherInt;
    }
}
//................................
MethodHandle getSumHandle = MethodHandles.lookup().findVirtual(Test.class , "getSum" , MethodType.fromMethodDescriptorString("(I)I" , null));
getSumHandle = getSumHandle.bindTo(new Test(2));
MethodHandle filterLengthHandle = MethodHandles.lookup().findVirtual(String.class , "length" , MethodType.fromMethodDescriptorString("()I" , null));
getSumHandle = MethodHandles.filterArguments(getSumHandle , 0 , filterLengthHandle);
MethodHandle valueOfHandle = MethodHandles.lookup().findStatic(String.class , "valueOf" , MethodType.fromMethodDescriptorString("(I)Ljava/lang/String;" , null));
getSumHandle = MethodHandles.filterReturnValue(getSumHandle , valueOfHandle);
String result = (String) getSumHandle.invoke("123456789");
//(参数类型String->过滤参数句柄(String->int)->过滤后的参数int->原方法句柄(int->int)->返回值int->过滤返回值句柄(int->String)->过滤后的返回值String
System.out.println(result);
//结果:11

将多个参数整合成一个新参数的句柄转换:MethodHandles#foldArguments,该方法第一个参数为待转换的句柄,第二个参数是从哪个位置开始取多个参数(至于具体取几个参数,由第三个参数方法句柄的参数列表长度决定),第三个参数是来执行该转换的句柄,该句柄的输出值将会添加在第二个参数指定的位置。(参数列表【参数类型1,参数类型2,参数类型3】-> 转换句柄【(参数类型1,参数类型2)-> 参数类型4】-> 转换后参数列表【参数类型4,参数类型1,参数类型2,参数类型3】->原句柄执行)

public class Test {
    private int fieldInt = 2;
    public Test(int fieldInt){
        this.fieldInt = fieldInt;
    }
    public int getSumFromTwoArgs(int first , int second){
        return first + second + fieldInt;
    }
}
//.............................
MethodHandle getSumPlusHandle = MethodHandles.lookup().findVirtual(Test.class , "getSumFromTwoArgs" , MethodType.fromMethodDescriptorString("(II)I",null));
getSumPlusHandle = getSumPlusHandle.bindTo(new Test(2));
MethodHandle filterMaxlengthHandle = MethodHandles.lookup().findStatic(Main.class , "maxLength" , MethodType.fromMethodDescriptorString("(Ljava/lang/String;Ljava/lang/String;)I" , null));
getSumPlusHandle = MethodHandles.dropArguments(getSumPlusHandle , 1 , String.class , String.class);
//参数列表(int,String,String,int)  
getSumPlusHandle = MethodHandles.foldArguments(getSumPlusHandle , 0 ,filterMaxlengthHandle);
System.out.println(getSumPlusHandle.invoke("12332112" , "2358646" , 6));
//结果:16

将参数顺序重新排列的句柄转换:MethodHandles#permuteArguments,第一个参数是待转换的句柄,第二个参数是顺序变换后的方法类型,第三个参数接受句柄参数多个的整型值,该值表示其所代表的参数在重新排列后应当处于什么位置。详情看示例:

MethodType oldType = MethodType.fromMethodDescriptorString("(Ljava/lang/String;I)I" , null);
MethodHandle indexOfHandle = MethodHandles.lookup().findVirtual(String.class , "indexOf" , oldType);
MethodType newType = MethodType.fromMethodDescriptorString("(Ljava/lang/String;ILjava/lang/String;)I" , null);
indexOfHandle = MethodHandles.permuteArguments(indexOfHandle , newType , 0 , 2 , 1);
//(String,String,int)->(String,int,String)
System.out.println(indexOfHandle.invoke("hello ele" , 2 , "e"));
//结果:6

添加对异常进行处理的句柄转换:Methodhandles#catchException,第一个参数是待处理的句柄,第二个参数是要处理的异常类型,第三个参数是处理异常的句柄,该处理异常的句柄的参数应当为(Exception,String),返回值应当和原句柄的返回值的类型一致。也就是说当异常发生后,该异常会被异常处理句柄处理,然后异常处理句柄的返回值将作为原句柄的返回值被返回

public class Main {
    public static void main(String[] args) throws Throwable {
        MethodHandle parseIntHandle =   MethodHandles.lookup().findStatic(Integer.class , "parseInt" , MethodType.fromMethodDescriptorString("(Ljava/lang/String;)I" , null));
        MethodHandle exceptionHandle = MethodHandles.lookup().findStatic(Main.class , "exceptionHandle" , MethodType.fromMethodDescriptorString("(Ljava/lang/Exception;Ljava/lang/String;)I" , null));
        parseIntHandle = MethodHandles.catchException(parseIntHandle , Exception.class , exceptionHandle);
        parseIntHandle.invoke("0sad");
    }
    
    public static int exceptionHandle(Exception e , String str){
        System.out.println(e.getMessage());
        return 0;
    }
}
//结果:For input string: "0sad"

为句柄添加条件执行能力:MethodHandles#guardWithTest,第一个参数是条件句柄,该句柄参数列表应当为空,返回值类型应当为boolean,第二个参数是当条件为真时执行的句柄,第三个参数是当条件为假时执行的句柄,第二、三个句柄应当具有相同的方法类型。句柄执行时,会先执行条件句柄,然后根据结果选择对应的执行句柄去真正执行。

MethodHandle boolHandle = MethodHandles.constant(boolean.class , true);
MethodType type = MethodType.fromMethodDescriptorString("(II)I" , null);
MethodHandle maxHandle = MethodHandles.lookup().findStatic(Integer.class , "max" , type);
MethodHandle minHandle = MethodHandles.lookup().findStatic(Integer.class , "min" , type);
boolHandle =MethodHandles.guardWithTest(boolHandle , maxHandle , minHandle);
System.out.println(boolHandle.invoke(7,9));
//结果:9

句柄模板
MethodHandles#invoker,句柄模板用于解决这么一种问题:假设现在有10个MethodType一样的方法句柄,我想对这10个方法句柄都添加一个返回值过滤的机制,那么我得转换10次。而有了模板句柄之后,我们可以这么做,创建一个模板句柄,MethodType与待转换的句柄们一致,使用该模板句柄添加一个返回值过滤的机制,然后在使用时指定哪个句柄进行执行就可以了。

MethodType type = MethodType.fromMethodDescriptorString("(II)I",null);
MethodHandle maxHandle = MethodHandles.lookup().findStatic(Math.class , "max" , type);
MethodHandle minHandle = MethodHandles.lookup().findStatic(Math.class , "min" , type);
MethodHandle templateInvoker = MethodHandles.invoker(type);
MethodHandle intToStringFilter = MethodHandles.lookup().findStatic(String.class , "valueOf" , MethodType.methodType(String.class , int.class));
templateInvoker = MethodHandles.filterReturnValue(templateInvoker , intToStringFilter);
String result = (String) templateInvoker.invoke(maxHandle , 56 ,78);
System.out.println(result);
//结果:78

七、其他

MethodHandle handle = MethodHandles.lookup().findStatic(Main.class , "printOk" , MethodType.fromMethodDescriptorString("()V" , null));
        Callable runnableProxy = MethodHandleProxies.asInterfaceInstance(Callable.class , handle);
        runnableProxy.call();
//结果:ok
MethodType type = MethodType.fromMethodDescriptorString("(II)I",null);
MethodHandle maxHandle = MethodHandles.lookup().findStatic(Math.class , "max" , type);
MethodHandle minHandle = MethodHandles.lookup().findStatic(Math.class , "min" , type);
SwitchPoint switchPoint = new SwitchPoint();
MethodHandle ultimateHandle = switchPoint.guardWithTest(maxHandle , minHandle);
System.out.println(ultimateHandle.invoke(1,10));
SwitchPoint.invalidateAll(new SwitchPoint[]{switchPoint});
System.out.println(ultimateHandle.invoke(1,10));
//结果:10 1
上一篇 下一篇

猜你喜欢

热点阅读