likes
comments
collection
share

MethodHandle 介绍

作者站长头像
站长
· 阅读数 4

MethodHandle 是 Java 7 引入的一项重要特性,它是一种用于在运行时执行方法调用的强大而灵活的机制。MethodHandle 的设计目标是提供比反射更高效的方法调用,并且在某些情况下,它比传统的 Java 方法调用更灵活。

使用示例

public class MethodHandleExample {

    public static void main(String[] args) throws Throwable {

        MethodHandles.Lookup lookup = MethodHandles.lookup();

        // 定义方法类型
        MethodType methodType = MethodType.methodType(void.class, String.class);

        // 获取 MethodHandle,指定方法名、方法类型
        MethodHandle methodHandle = lookup.findVirtual(MethodHandleExample.class, "printMessage", methodType);

        // 创建实例
        MethodHandleExample example = new MethodHandleExample();

        // MethodHandle调用 printMessage 方法
        methodHandle.invokeExact(example, "Hello, MethodHandle!");
    }

    // 示例方法
    public void printMessage(String message) {
        System.out.println(message);
    }
}

输出

Hello, MethodHandle!

MethodHandles.Lookup

MethodHandles.Lookup 构造函数不是公有的,所以只能用静态工厂方法获取

private Lookup(Class<?> lookupClass, int allowedModes) {
    this.lookupClass = lookupClass;
    this.allowedModes = allowedModes;
}

MethodHandles 提供了三种获取 MethodHandles.Lookup 对象的方法这三种主要区别在于权限检查

  • 权限检查除了 allowedModes
  • 还需要检查 lookupClass : 检查lookupClass 这个类是否能够访问目标方法

两种都能够检测通过方法才能调用成功

// 允许调用所有方法
MethodHandles.Lookup lookup = MethodHandles.lookup(); 
//只能调用 public 方法
MethodHandles.Lookup publicLookup = MethodHandles.publicLookup(); 
 // 通过 privateLookupIn 方法可以重新构建一个 MethodHandles.Lookup 对象
MethodHandles.Lookup privateLookup = MethodHandles.privateLookupIn(clz, lookup);

MethodType

MethodType 是 Java 中用于描述方法类型的类。它包含了方法的返回类型、参数类型等信息,提供了一种表示方法签名的方式。MethodType 主要用于创建 MethodHandle,以确定方法句柄的类型

MethodType 构造函数是私有,所以只能用静态工厂方法获取

// 返回类型,参数类型
private MethodType(Class<?> rtype, Class<?>[] ptypes) {
    this.rtype = rtype;
    this.ptypes = ptypes;
}
// 返回类型
public static MethodType methodType(Class<?> rtype) {
    return makeImpl(rtype, NO_PTYPES, true);
}
// 返回类型,一个参数
public static MethodType methodType(Class<?> rtype, Class<?> ptype0) {
    return makeImpl(rtype, new Class<?>[]{ ptype0 }, true);
}
// 返回类型,多个参数
public static MethodType methodType(Class<?> rtype, Class<?> ptype0, Class<?>... ptypes) {
    Class<?>[] ptypes1 = new Class<?>[1+ptypes.length];
    ptypes1[0] = ptype0;
    System.arraycopy(ptypes, 0, ptypes1, 1, ptypes.length);
    return makeImpl(rtype, ptypes1, true);
}
// 返回类型,多个参数
public static MethodType methodType(Class<?> rtype, Class<?>[] ptypes) {
    return makeImpl(rtype, ptypes, false);
}
// 返回类型,多个参数
public static MethodType methodType(Class<?> rtype, List<Class<?>> ptypes) {
    boolean notrust = false;  // random List impl. could return evil ptypes array
    return makeImpl(rtype, listToArray(ptypes), notrust);
}
// 返回类型,通过 MethodType 获取参数类型
public static MethodType methodType(Class<?> rtype, MethodType ptypes) {
    return makeImpl(rtype, ptypes.ptypes, true);
}

// 返回类型是Object ,参数类型都是 Object,参数个数
public static MethodType genericMethodType(int objectArgCount) {
    return genericMethodType(objectArgCount, false);
}
// 返回类型是Object ,参数类型都是 Object,或者 Object[] 
// 参数个数 objectArgCount ,finalArray 决定最后一个参数是否 Object[]
public static MethodType genericMethodType(int objectArgCount, boolean finalArray) {
    MethodType mt;
    checkSlotCount(objectArgCount);
    int ivarargs = (!finalArray ? 0 : 1);
    int ootIndex = objectArgCount*2 + ivarargs;
    if (ootIndex < objectOnlyTypes.length) {
        mt = objectOnlyTypes[ootIndex];
        if (mt != null)  return mt;
    }
    Class<?>[] ptypes = new Class<?>[objectArgCount + ivarargs];
    Arrays.fill(ptypes, Object.class);
    if (ivarargs != 0)  ptypes[objectArgCount] = Object[].class;
    mt = makeImpl(Object.class, ptypes, true);
    if (ootIndex < objectOnlyTypes.length) {
        objectOnlyTypes[ootIndex] = mt;     // cache it here also!
    }
    return mt;
}

// 通过字符串描述方法签名来获取
public static MethodType fromMethodDescriptorString(String descriptor, ClassLoader loader)
    throws IllegalArgumentException, TypeNotPresentException
{
    return fromDescriptor(descriptor,
                          (loader == null) ? ClassLoader.getSystemClassLoader() : loader);
}

可使用 javap 命令获取签名

以下是通过方法签名获取 MethodType 示例

public void generateMethodTypesFromDescriptor() {
   ClassLoader cl = this.getClass().getClassLoader();
   String descriptor = "(Ljava/lang/String;)Ljava/lang/String;";
   MethodType mt = MethodType.fromMethodDescriptorString(descriptor, cl);
   System.out.println(mt);
}

MethodHandle

MethodHandle 属于抽象类,可通过 MethodHandles.Lookup 对象方法获取

  1. 查找静态方法句柄

获取静态方法的方法句柄。(由于静态方法不接收接收者,因此没有像findVirtual或findSpecial那样将其他接收者参数插入方法句柄类型。)该方法及其所有参数类型必须对查找对象是可访问的。

/*
参数:
    refc-从中访问方法的类
    name-方法名称
    type-方法的类型
*/
public MethodHandle findStatic(Class<?> refc, String name, MethodType type)
  1. 查找虚方法句柄

获取虚拟方法的方法句柄。 方法句柄的类型将是方法的类型,并带有接收者类型(通常是refc)。 该方法及其所有参数类型必须对查找对象是可访问的。

/*
     refc-从中访问方法的类或接口
     name-方法名称
     type-方法的类型,省略接收方参数
*/
public MethodHandle findVirtual(Class<?> refc, String name,MethodType type)

  1. 查找构造方法
/*
参数:
     refc-从中访问方法的类或接口
     type-方法的类型,省略了接收器参数,并返回 void
*/
public MethodHandle findConstructor(Class<?> refc,  MethodType type)

  1. 查找构造方法、私有方法和父类中的方法句柄

注意:即使在特殊情况下,invokespecial指令可以引用名为<init>的JVM内部方法,该API也不可见。使用findConstructor以安全的方式访问实例初始化方法。)

/*
参数:
     refc-从中访问方法的类或接口 (定义的方法的类,父类)
     name-方法的名称(不能为“ <init>”)
     type-方法的类型,省略接收方参数
     specialCaller-建议的调用类以执行invokespecial (实际调用的方法的类)
*/
public MethodHandle findSpecial(Class<?> refc,String name,MethodType type,Class<?> specialCaller)
  1. 获取对象字段值
/*
参数:
     refc-从中访问方法的类或接口
     name-字段名称
     type-字段的类型
*/
public MethodHandle findGetter(Class<?> refc,String name,Class<?> type)
  1. 设置对象字段值

/*
参数:
     refc - 从中访问方法的类或接口
     name - 字段名称
     type - 字段的类型
*/
public MethodHandle findSetter(Class<?> refc, String name,Class<?> type)
  1. 获取静态字段值
/*
参数:
     refc - 从中访问方法的类或接口
     name - 字段名称
     type - 字段的类型
*/
public MethodHandle findStaticGetter(Class<?> refc, String name,Class<?> type)
  1. 设置静态字段值
/*
参数:
     refc-从中访问方法的类或接口
     name-字段名称
     type-字段的类型
*/
public MethodHandle findStaticSetter(Class<?> refc,String name, Class<?> type)
  1. 绑定

/*
参数:
     receiver-从中访问方法的对象
     name-方法名称
     type-方法的类型,省略接收方参数
*/
public MethodHandle bind(Object receiver, String name,MethodType type)
  1. 普通反射方法转换为方法句柄
/*
参数:

     m - 反射方法
*/
public MethodHandle unreflect(Method m)
  1. Special转化为方法句柄
/*
参数:
     m-反射方法
     specialCaller-名义上调用该方法的类
*/
public MethodHandle unreflectSpecial(Method m,
                                     Class<?> specialCaller)
  1. 构造函数转换为方法句柄
/*
参数:
    c-反射构造函数
*/
public MethodHandle unreflectConstructor(Constructor<?> c)
  1. 获取反射字段转化为方法句柄
/*
参数:
     f-反射字段
*/
public MethodHandle unreflectGetter(Field f)
  1. 设置反射字段转化为方法句柄
/*
参数:
    f-反射字段
*/
public MethodHandle unreflectSetter(Field f)
  1. 调用方法

// 在使用 `invoke` 时,参数的数量和类型可以与方法句柄的类型不完全匹配,
// 因为它会尝试在运行时适应目标方法的类型。
/*
参数:
     args-签名多态参数列表,使用varargs静态表示
*/
public final Object invoke(Object... args);


// 方法要求参数的数量和类型与方法句柄的类型精确匹配,包括返回类型
// 如果参数类型和数量不精确匹配,将抛出 `WrongMethodTypeException` 异常。
/*
参数:
     args-签名多态参数列表,使用varargs静态表示
*/
public final Object invokeExact(Object... args);

// invokeWithArguments 会在运行时检查参数的数量和类型,并进行必要的类型转换,
// 但要求参数列表的总数必须与方法句柄类型的参数数量匹配。
/*
参数:
     args-签名多态参数列表,使用varargs静态表示
*/
public Object invokeWithArguments(Object... args);

/*
参数:
     args-参数可以用 List 传递参数
*/
public Object invokeWithArguments(List<?> args);

VarHandle

java.lang.invoke.VarHandle 是 Java 9 引入的一个新的 API,用于提供对数组和字段的原子操作、比较和交换等操作。它提供了对内存中的数据进行低级别操作的能力,以替代传统的 java.util.concurrent.atomicsun.misc.Unsafe 类。

a. 针对数组元素的 VarHandle:

public class VarHandleArrayExample {

    public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
        int[] array = new int[10];
        // 获取数组元素的 VarHandle
        VarHandle arrayElementVarHandle = MethodHandles.arrayElementVarHandle(int[].class);
        // 使用 VarHandle 读写数组元素
        arrayElementVarHandle.set(array, 1, 42);
        int value = (int) arrayElementVarHandle.get(array, 1);
        System.out.println("Array element at index 1: " + value);
    }
}
Array element at index 1: 42
b. 针对字段的 VarHandle:
public class VarHandleFieldExample {

    private int fieldValue;

    public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
        VarHandleFieldExample example = new VarHandleFieldExample();
        // 获取字段的 VarHandle
        VarHandle fieldVarHandle = MethodHandles.lookup().findVarHandle(VarHandleFieldExample.class, "fieldValue", int.class);
        // 使用 VarHandle 读写字段
        fieldVarHandle.set(example, 42);
        int value = (int) fieldVarHandle.get(example);
        System.out.println("Field value: " + value);
    }
}
Field value: 42

VarHandle 的操作

VarHandle 提供了一系列的原子操作,包括读取、写入、比较并交换等。以下是一些示例:

public class VarHandleOperationsExample {
    private int value;

    public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {

        VarHandleOperationsExample example = new VarHandleOperationsExample();
        VarHandle varHandle = MethodHandles.lookup().findVarHandle(VarHandleOperationsExample.class, "value", int.class);
        // 读取值
        int currentValue = (int) varHandle.get(example);
        System.out.println("Current value: " + currentValue);
        // 原子性地增加值
        int newValue = (int) varHandle.getAndAdd(example, 5);
        System.out.println("New value after getAndAdd: " + newValue);
        // 原子性地比较并交换值
        boolean success = varHandle.compareAndSet(example, currentValue, 20);
        
        System.out.println("Compare and set success: " + success);
        System.out.println("Current value after compareAndSet: " + varHandle.get(example));
    }
}
Current value: 0
New value after getAndAdd: 0
Compare and set success: false
Current value after compareAndSet: 5

MethodHandle 与 Reflection (反射) 的区别:

  • Reflection 和 MethodHandle 机制本质上都是在模拟方法调用,但是 Reflection 是在模拟Java代码层次的方法调用,而MethodHandle是在模拟字节码层次的方法调用 因此通常比反射更快。

  • Reflection 中的 java.lang.reflect.Method 对象远比 MethodHandle 机制中的 java.lang.invoke.MethodHandle 对象所包含的信息来得多。前者是方法在Java端的全面映像,包含了方法的签名、描述符以及方法属性表中各种属性的 Java 端表示方式,还包含执行权限等的运行期信息。而后者仅包含执行该方法的相关信息。用开发人员通俗的话来讲,Reflection 是重量级,而 MethodHandle 是轻量级。

  • Reflection API 的设计目标是只为Java语言服务的,而 MethodHandle 则设计为可服务于所有Java虚拟机之上的语言,其中也包括了Java语言而已,而且Java在这里并不是主角。