0

我正在尝试研究如何使用 LambdaMetafactory 生成可用的 callSite。

这是我最新的 Groovy 脚本尝试。我尝试了多种参数排列,但无法使第二个基于 getter 的示例正常工作。

我做的第一个例子最终开始工作,从一个闭包中生成一个供应商,经过大量的摆弄和阅读 Java 文档和 Stack Overflow。

import java.lang.invoke.LambdaMetafactory
import java.lang.invoke.MethodHandle
import java.lang.invoke.MethodHandles
import java.lang.invoke.MethodType
import java.util.function.Supplier

/**
 * LambdaMetafactory example with closure - works
 */
Closure myClosure = {"hello from closure"}

MethodHandles.Lookup lookup= MethodHandles.lookup()

def delegateImpl = lookup.findVirtual(Closure.class, "call", MethodType.methodType (Object.class))

//now get a callSite for the handle - https://wttech.blog/blog/2020/method-handles-and-lambda-metafactory/
java.lang.invoke.CallSite closureCallSite = LambdaMetafactory.metafactory(
        lookup,
        "get",
        MethodType.methodType(Supplier.class, Closure.class),
        MethodType.methodType (Object.class),
        delegateImpl,
        MethodType.methodType(String)
)

MethodHandle closureFactory = closureCallSite.getTarget()

Supplier closureLambda = closureFactory.bindTo(myClosure).invokeWithArguments()
def res = closureLambda.get()
println res


/**
 * LambdaMetafactory example with standard class  - cant get to work with any combinations
 */
class ExampleClass {
    private String value = "hello from getter"

    ExampleClass() {}  //constructor

    String getValue () {return value}
    void setValue (String val) {value = val}
}

ExampleClass instance = new ExampleClass()

MethodHandle getterDelegateImpl = lookup.findVirtual(ExampleClass.class, "getValue", MethodType.methodType (String.class))

java.lang.invoke.CallSite getterCallSite = LambdaMetafactory.metafactory(
         lookup,
        "get",
        MethodType.methodType(Supplier.class, ExampleClass),
        MethodType.methodType (Object.class),
        getterDelegateImpl,
        MethodType.methodType(String.class)
)

MethodHandle classFactory = getterCallSite.getTarget()

Supplier lambda =  classFactory.bindTo(instance).invokeWithArguments()
def ret = lambda.get ()
println ret

当您运行此程序时,第一个示例有效,我可以获得有效的 callSite 以从 Closure 获取动态供应商引用。第二个示例采用相同的方法,但使用带有 getter 方法的标准类。

hello from closure
Caught: java.lang.invoke.LambdaConversionException: Exception finding constructor
java.lang.invoke.LambdaConversionException: Exception finding constructor
    at script.testLambdaMetafactory.run(testLambdaMetafactory.groovy:69)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
Caused by: java.lang.IllegalAccessException: no such constructor: org.codehaus.groovy.vmplugin.v8.IndyInterface$$InjectedInvoker_0x0000000800dc2000$$Lambda$261/0x0000000800dc6a78.<init>(ExampleClass)void/invokeSpecial
    ... 4 more
Caused by: java.lang.LinkageError: bad method type alias: (ExampleClass)void not visible from class org.codehaus.groovy.vmplugin.v8.IndyInterface$$InjectedInvoker_0x0000000800dc2000$$Lambda$261/0x0000000800dc6a78
    ... 4 more 

我尝试了许多类型的排列,methodTypes 的参数数量都没有效果。

鉴于我想生成有效的 invokeDynamic 引用,我必须做什么才能生成可行的代码?

4

1 回答 1

0

好的 - 似乎部分原因是我使用了一个 groovy 脚本并在该脚本中标记了我的 bean 类。lamdbaMetafactory 不喜欢。所以我将 bean 类分离到它自己的类文件中

package lamda

class ExampleBeanClass {
    private String value = "hello from getter"
    private static String staticValue = "static string value"

    ExampleBeanClass() {}  //constructor

    String getValue () {return value}
    void setValue (String val) {value = val}

    static String getStaticValue () {return staticValue}
    static String setStaticValue (String staticVal) {staticValue = staticVal}
}

现在你可以编写一些测试了——我在这里展示了三个,一个使用通过供应商的访问,另一个使用函数生成的接口。如果您访问非静态方法,则需要绑定实例,并且需要在 invokedType 参数中声明它。

如果您调用静态方法 - 您不需要在 invokedType 中声明 bean,也不需要绑定实例

这三个测试现在正在工作

package lambda

import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import lamda.ExampleBeanClass

import java.lang.invoke.CallSite
import java.lang.invoke.LambdaMetafactory
import java.lang.invoke.MethodHandle
import java.lang.invoke.MethodHandles
import java.lang.invoke.MethodType
import java.lang.reflect.Method
import java.util.function.Function
import java.util.function.Supplier

class InvokeDynamicTest {

    ExampleBeanClass bean
    MethodHandles.Lookup callerCtx
    MethodHandle implementationDelegate

    @BeforeEach
    void init() {
        bean = new ExampleBeanClass()
        callerCtx = MethodHandles.lookup()
    }

    @Test
    void dummyTest () {
        assert true
        assert bean
        assert callerCtx
    }

    /**
     * get value by generating a Supplier interface reference
     */
    @Test
    void accessViaNonStaticBeanSupplierGetter () {

        Method reflected = ExampleBeanClass.class.getDeclaredMethod("getValue")
        implementationDelegate = callerCtx.unreflect (reflected)

        //if you want bind an instance value to your lambda, you have to include those types in the InvokedType signature, and then do the bind
        CallSite site = LambdaMetafactory.metafactory(
                callerCtx,
                "get",  //functional interface method name
                MethodType.methodType (Supplier.class, ExampleBeanClass),       //invoked type
                MethodType.methodType (Object.class),                           // SAM method type signature of required interface
                implementationDelegate,                                         //code thats doing the real work
                MethodType.methodType (String)                                  //expected return type of instantiated method, expected as subtype of SAM type
        )

        MethodHandle factory = site.getTarget().bindTo (bean)                   //invokedType defined bean class, so now bind one here

        Supplier func = (Supplier) factory.invokeWithArguments()

        assert func.get() == "hello from getter"
    }

    /**
     * get value by generating a functional interface reference
     */
    @Test
    void accessViaNonStaticBeanFunctionTypeGetter () {

        Method reflected = ExampleBeanClass.class.getDeclaredMethod("getValue")
        implementationDelegate = callerCtx.unreflect (reflected)

        //if you want bind an instance value to your lambda, you have to include those types in the InvokedType signature, and then do the bind
        CallSite site = LambdaMetafactory.metafactory(
                callerCtx,
                "apply",                                       //functional interface method name
                MethodType.methodType (Function.class, ExampleBeanClass),       //invoked type
                MethodType.methodType (Object.class),                           // SAM method type signature of required interface
                implementationDelegate,                                         //code thats doing the real work
                MethodType.methodType (String)                                  //expected return type of instantiated method, expected as subtype of SAM type
        )

        MethodHandle factory = site.getTarget().bindTo (bean)                   //invokedType defined bean class, so now bind one here

        Function func = (Function) factory.invokeWithArguments()

        assert func.apply() == "hello from getter"
    }

    @Test
    void accessViaStaticBeanGetter () {

        Method reflected = ExampleBeanClass.class.getDeclaredMethod("getStaticValue")
        implementationDelegate = callerCtx.unreflect (reflected)

        //as we are invoking static type we don't need to bind an instance to the site for this test case
        CallSite site = LambdaMetafactory.metafactory(
                callerCtx,
                "get",  //functional interface method name
                MethodType.methodType (Supplier.class),       //invoked type, doesnt need bean class for static invocation
                MethodType.methodType (Object.class),         // SAM method type signature of required interface
                implementationDelegate,                       //code thats doing the real work
                MethodType.methodType (String)                //expected return type of instantiated method, expected as subtype of SAM type
        )

        MethodHandle factory = site.getTarget()

        Supplier func = (Supplier) factory.invokeWithArguments()

        assert func.get() == "static string value"
    }
}

我希望这可以安全地讨论如何使用 LambdaMetafactory 几个小时。但是,如果您想使用它,它的使用仍然非常棘手,并且了解如何驾驶它

为了帮助改进 fiddley 的使用,我尝试生成几个静态的“helper”方法,为你伪装一些 fiddley 类型匹配。

class ClassUtils {

    private static MethodHandles.Lookup lookup = MethodHandles.lookup()

    /**
     * generates a functional interface from a callSite
     *
     * @param functionalInterfaceReturnClass
     * @param instance
     * @param sourceMethodName
     * @param sourceMethodArgTypes
     * @return
     */
    static def getLambdaFromReflectionMethod(Class<?> functionalInterfaceReturnClass, Object instance, String sourceMethodName, Class<?>... sourceMethodArgTypes) {
        Method reflectedCall
        String funcionalInterfaceMethodName

        switch (functionalInterfaceReturnClass) {
            case Supplier -> funcionalInterfaceMethodName = "get"
            case Function -> funcionalInterfaceMethodName = "apply"
            case BiFunction -> funcionalInterfaceMethodName = "apply"
            case Consumer -> funcionalInterfaceMethodName = "accept"
            case Predicate -> funcionalInterfaceMethodName = "test"
            case Callable -> funcionalInterfaceMethodName = "call"
            case Runnable -> funcionalInterfaceMethodName = "run"

            default -> funcionalInterfaceMethodName = "apply"
        }

        Class runtimeClazz = instance.getClass()
        def size = sourceMethodArgTypes.size()
        if (sourceMethodArgTypes?.size() > 0 ) {
            reflectedCall    = instance.class.getMethod(sourceMethodName, *sourceMethodArgTypes )
        } else {
            reflectedCall    = instance.class.getMethod(sourceMethodName)
        }

        MethodHandle delegateImplHandle  = lookup.unreflect(reflectedCall)

        MethodType invokedMethodType = MethodType.methodType(functionalInterfaceReturnClass, runtimeClazz)
        MethodType samMethodType = (instance instanceof Closure ) ? MethodType.methodType (Object)
                                                                    : delegateImplHandle.type().dropParameterTypes(0,1).erase()
        MethodType instantiatedMethodType = (instance instanceof Closure ) ? MethodType.methodType (Object)
                                                                            : delegateImplHandle.type().dropParameterTypes(0,1)

        //now get a callSite for the handle - https://wttech.blog/blog/2020/method-handles-and-lambda-metafactory/
        java.lang.invoke.CallSite callSite = LambdaMetafactory.metafactory(
                lookup,                     //calling Ctx for methods
                funcionalInterfaceMethodName,                 //name of the functional interface name to invoke
                invokedMethodType,          // MethodType.methodType(Supplier, Closure ),
                samMethodType,              //MethodType.methodType(Object),              // samMthodType: signature and return type of method to be implemented after type erasure
                delegateImplHandle,         //implMethod handle that does the work - the handle for closure call()
                instantiatedMethodType      //instantiatedMethodType: signature and return type that should be forced dynamically at invocation.
        )

        MethodHandle factory = callSite.getTarget()

        return factory.bindTo(instance).invokeWithArguments()
    }

    /**
     * generates a functional interface from a callSite
     *
     * @param returnClass
     * @param instance
     * @param sourceMethodName
     * @param args
     * @return
     */
    static def getLambdaFromStaticReflectionMethod(Class<?> functionalInterfaceClass, Class<?> sourceClazz, String sourceMethodName, Class<?>... sourceMethodArgTypes) {
        Method reflectedCall
        String functionalInterfaceMethodName

        switch (functionalInterfaceClass) {
            case Supplier -> functionalInterfaceMethodName = "get"
            case Function -> functionalInterfaceMethodName = "apply"
            case BiFunction -> functionalInterfaceMethodName = "apply"
            case Consumer -> functionalInterfaceMethodName = "accept"
            case Predicate -> functionalInterfaceMethodName = "test"
            case Callable -> functionalInterfaceMethodName = "call"
            case Runnable -> functionalInterfaceMethodName = "run"

            default -> functionalInterfaceMethodName = "apply"
        }

        Class runtimeClazz = sourceClazz
        Class closClazz = Closure.class

        if (sourceMethodArgTypes?.size() > 0 )
            reflectedCall = runtimeClazz.getMethod(sourceMethodName, *sourceMethodArgTypes )
        else
            reflectedCall = runtimeClazz.getMethod(sourceMethodName )

        MethodHandle delegateImplHandle  = lookup.unreflect(reflectedCall)

        /**
         * weird with closure instantiatedMethodType, and samMethodType seem to need form ()<returnType>
         * if using instance of ordinary class you can get form (<source>)<returnType>
         */
        MethodType invokedMethodType = MethodType.methodType(functionalInterfaceClass)
        MethodType samMethodType =  delegateImplHandle.type().erase()
        MethodType instantiatedMethodType = delegateImplHandle.type()

        /**
         * wont work at mo for static functions to be generated
         */
        //now get a callSite for the handle - https://wttech.blog/blog/2020/method-handles-and-lambda-metafactory/
        java.lang.invoke.CallSite callSite = LambdaMetafactory.metafactory(
                lookup,                     //calling Ctx for methods
                functionalInterfaceMethodName,                 //name of the functional interface name to invoke
                invokedMethodType,          // MethodType.methodType(Supplier, Closure ),
                samMethodType,              //MethodType.methodType(Object),              // samMthodType: signature and return type of method to be implemented after type erasure
                delegateImplHandle,         //implMethod handle that does the work - the handle for closure call()
                instantiatedMethodType      //instantiatedMethodType: signature and return type that should be forced dynamically at invocation.
        )

        MethodHandle factory = callSite.getTarget()

        return ( factory.invokeWithArguments() ).asType(functionalInterfaceClass)
    }
}

和一些测试来证明这个工作与我用来交叉检查我用于直接使用 LambdaFactory 的 MethodTypes 的原始访问一起工作。这并没有严格测试所有选项,但测试的示例案例应该可以工作。

class ClassUtilsTest {

    @Test
    void generateSupplierFromClosure () {

        Closure myClos = {"hello"}

        Supplier supplier = ClassUtils.getLambdaFromReflectionMethod(Supplier, myClos, 'call')
        supplier.get() == "hello"

    }

    @Test
    void generateSupplierFromBeanClassInstance () {

        ExampleBeanClass bean = new ExampleBeanClass()

        Supplier supplier = ClassUtils.getLambdaFromReflectionMethod(Supplier, bean, 'getValue')
        supplier.get() == "hello from getter"

    }

    /**
     * slightly unnatural but you can get a functional interface for a getter,
     * when you invoke just invoke with empty args list -
     * bit using Supplier interface feels better fit
     */
    @Test
    void generateFunctionFromBeanClassInstance () {

        ExampleBeanClass bean = new ExampleBeanClass()

        Function function = ClassUtils.getLambdaFromReflectionMethod(Function, bean, 'getValue')
        function.apply() == "hello from getter"

    }

    @Test
    void generateSupplierFromBeanClassStaticMethod  () {

        ExampleBeanClass bean = new ExampleBeanClass()

        Supplier supplier = ClassUtils.getLambdaFromStaticReflectionMethod(Supplier, bean.getClass(), 'getStaticValue')
        supplier.get() == "static string value"

    }


    @Test
    void generatePredicateFromBeanClassInstance () {

        ExampleBeanClass bean = new ExampleBeanClass()

        Predicate predicate = ClassUtils.getLambdaFromReflectionMethod(Predicate, bean, 'test', Object)
        predicate.test(10) == true

    }

    @Test
    void generateSupplierFromBeanClassInstanceViaCallSiteDirect () {

        ExampleBeanClass bean = new ExampleBeanClass()

        MethodHandles.Lookup lookup = MethodHandles.lookup()
        MethodHandle delegateImplHandle = lookup.findVirtual(ExampleBeanClass,'getValue',MethodType.methodType(String))

        MethodType invokedMethodType = MethodType.methodType(Supplier, ExampleBeanClass)
        MethodType sam = MethodType.methodType (Object.class)
        MethodType samMethodTypeNoDrop = delegateImplHandle.type().erase()
        MethodType samMethodType = delegateImplHandle.type().dropParameterTypes(0,1).erase()
        MethodType ins = MethodType.methodType (String.class)
        MethodType instantiatedMethodType = delegateImplHandle.type().dropParameterTypes(0,1)


        java.lang.invoke.CallSite callSite = LambdaMetafactory.metafactory(
                lookup,                     //calling Ctx for methods
                'get',                 //name of the functional interface name to invoke
                invokedMethodType,          // MethodType.methodType(Supplier, Closure ),
                samMethodType,              //MethodType.methodType(Object),              // samMthodType: signature and return type of method to be implemented after type erasure
                delegateImplHandle,         //implMethod handle that does the work - the handle for closure call()
                instantiatedMethodType      //instantiatedMethodType: signature and return type that should be forced dynamically at invocation.
        )

        MethodHandle factory = callSite.getTarget()

        Supplier supplier = factory.bindTo(bean).invokeWithArguments()
        supplier.get() == "hello from getter"
    }

    @Test
    void generateFunctionFromBeanClassInstanceViaCallSiteDirect () {

        ExampleBeanClass bean = new ExampleBeanClass()

        MethodHandles.Lookup lookup = MethodHandles.lookup()
        MethodHandle delegateImplHandle = lookup.findVirtual(ExampleBeanClass,'getValue',MethodType.methodType(String))

        MethodType invokedMethodType = MethodType.methodType(Function, ExampleBeanClass)
        MethodType sam = MethodType.methodType (Object.class)
        MethodType samMethodTypeNoDrop = delegateImplHandle.type().erase()
        MethodType samMethodType = delegateImplHandle.type().dropParameterTypes(0,1).erase()
        MethodType ins = MethodType.methodType (String.class)
        MethodType instantiatedMethodType = delegateImplHandle.type().dropParameterTypes(0,1)


        java.lang.invoke.CallSite callSite = LambdaMetafactory.metafactory(
                lookup,                     //calling Ctx for methods
                'apply',                 //name of the functional interface name to invoke
                invokedMethodType,          // MethodType.methodType(Supplier, Closure ),
                samMethodType,              //MethodType.methodType(Object),              // samMthodType: signature and return type of method to be implemented after type erasure
                delegateImplHandle,         //implMethod handle that does the work - the handle for closure call()
                instantiatedMethodType      //instantiatedMethodType: signature and return type that should be forced dynamically at invocation.
        )

        MethodHandle factory = callSite.getTarget()

        Function function = factory.bindTo(bean).invokeWithArguments()
        function.apply() == "hello from getter"
    }

    @Test
    void generatePredicateFromBeanClassInstanceViaCallSiteDirect () {

        ExampleBeanClass bean = new ExampleBeanClass()

        MethodHandles.Lookup lookup = MethodHandles.lookup()

        MethodHandle delegateImplHandle = lookup.findVirtual(ExampleBeanClass,'test',MethodType.methodType(boolean.class, Object))

        MethodType invokedMethodType = MethodType.methodType(Predicate, ExampleBeanClass)
        MethodType sam = MethodType.methodType (boolean.class, Object)
        MethodType samMethodType = delegateImplHandle.type().dropParameterTypes(0,1).erase()
        MethodType ins = MethodType.methodType (boolean.class, Object)
        MethodType instantiatedMethodType = delegateImplHandle.type().dropParameterTypes(0,1)


        java.lang.invoke.CallSite callSite = LambdaMetafactory.metafactory(
                lookup,                     //calling Ctx for methods
                'test',                 //name of the functional interface name to invoke
                invokedMethodType,          // MethodType.methodType(Predicate, ExampleBeanClass ),
                samMethodType,              //MethodType.methodType(Object),              // samMthodType: signature and return type of method to be implemented after type erasure
                delegateImplHandle,         //implMethod handle that does the work - the handle for closure call()
                instantiatedMethodType      //instantiatedMethodType: signature and return type that should be forced dynamically at invocation.
        )

        MethodHandle factory = callSite.getTarget()

        Predicate predicate = factory.bindTo(bean).invokeWithArguments()
        predicate.test(10) == true

    }
}
于 2021-12-16T13:05:11.563 回答