方法句柄

JDK6之前我们会使用java反射来实现动态方法调用,多数框架用反射的比较多,例如mybatis、spring等。在JDK7中,新增了java.lang.invoke.MethodHandle(方法句柄),称之为“现代化反射”。其实反射和java.lang.invoke.MethodHandle都是间接调用方法的途径,但java.lang.invoke.MethodHandle比反射更简洁,用反射功能会写一大堆冗余代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public interface Animal {
void race();
}

public class Horse implements Animal {

@Override
public void race() {
System.out.println("Horse.race()");
}
}

.....
// 利用接口抽象方法
public static void main(String[] args) throws Exception {
Animal animal = new Horse();
animal.race();
}
// 反射调用
public static void main(String[] args) throws Exception {
Horse object = new Horse();
Method method = object.getClass().getMethod("race");
method.invoke(object);
}

MethodType用来描述方法的返回值类型以及入参类型。
MehodHandle包含一个指向Method对象(方法在jvm内部的对等体)的指针。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public class Main {

public String toString(String s) {
return "hello," + s + "MethodHandle";
}

public static void main(String[] args) throws Throwable {
Main main = new Main();
MethodHandle mh = getToStringMH();
System.out.println((String) mh.invokeExact(main, "ss"));
}


public static MethodHandle getToStringMH() {

MethodType mt = MethodType.methodType(String.class, String.class); //获取方法类型 参数为:1.返回值类型,2方法中参数类型

MethodHandle mh = null;
try {
mh = MethodHandles.lookup().findVirtual(Main.class, "toString", mt); //查找方法句柄
} catch (NoSuchMethodException | IllegalAccessException e) {
e.printStackTrace();
}

return mh;
}

}

方法句柄是一个强类型的,能够被直接执行的引用。该引用可以指向常规的静态方法或者实例方法,也可以指向构造器或者字段。当指向字段时,方法句柄实则指向包含字段访问字节码的虚构方法,语义上等价于目标字段的 getter 或者 setter 方法。

方法句柄的类型(MethodType)是由所指向方法的参数类型以及返回类型组成的。它是用来确认方法句柄是否适配的唯一关键。当使用方法句柄时,我们其实并不关心方法句柄所指向方法的类名或者方法名。

MethodHandle 的创建方式:

方式一、通过反射创建 MethodHandle(不符合初衷)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Main {
public static void race() {
System.out.println("hello");
}
public static MethodHandles.Lookup lookup() {
return MethodHandles.lookup();
}
public static void main(String[] args) throws Throwable {
Method method = Main.class.getDeclaredMethod("race");
MethodHandle methodHandle = Main.lookup().unreflect(method);
methodHandle.invoke();
}
}

输出: hello

方式二、根据 MethodType 创建 MethodHandle

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class Main {

public static void race(){
System.out.println("race");
}
public void say(){
System.out.println("say");
}

public static MethodHandles.Lookup lookup() {
return MethodHandles.lookup();
}
public static void main(String[] args) throws Throwable {
MethodHandles.Lookup lookup = Main.lookup();
MethodType methodType = MethodType.methodType(void.class);
MethodHandle methodHandle = lookup.findStatic(Main.class, "race", methodType);

MethodHandle methodHandle2 = lookup.findVirtual(Main.class, "say", methodType);
methodHandle.invoke();
methodHandle2.invoke(new Main());
}

}
输出:
race
say

小结一下:

1、方法句柄的访问权限不取决于方法句柄的创建位置,而是取决于 Lookup 对象的创建位置。

2、如果 JDK 版本大于8,那么可以在其他类中,也能够通过该 Lookup 对象对类私有属性进行赋值、取值操作。

外类中操控私有字段

因为权限问题,外类中创建的 Lookup 对象无法操控私有字段,那么有什么替代方案呢?

  • 通过操控get和set方法
  • 反射

关于invoke和invokeExtract方法的区别:

invokeExtract要求更加精确,
如下 methodHandle2.invokeExact(test1,5.1,new Integer(1));可以执行,
methodHandle2.invokeExact(test1,5.1,1);会报错,因为要将1转换为integer,所以不合要求。这个方法要求不能有任何类型转换,也就是参数严格一致。
invoke相对要轻松很多。

关键概念

Lookup
MethodHandle 的创建工厂,通过它可以创建MethodHandle,值得注意的是检查工作是在创建时处理的,而不是在调用时处理。

MethodType
顾名思义,就是代表方法的签名。一个方法的返回值类型是什么,有几个参数,每个参数的类型什么?

MethodHandle
方法句柄,通过它我们就可以动态访问类型信息了。

如何使用

当理解了上面几个关键概念后使用起来就比较简单了,总的来说只需要4步:

创建Lookup
创建MethodType
基于Lookup与MethodType获得MethodHandle
调用MethodHandle