方法句柄
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);
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 对象无法操控私有字段,那么有什么替代方案呢?
关于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