好的 - 似乎部分原因是我使用了一个 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
}
}