Java 如何在运行时使用LambdaMetafactory访问动态类中的非静态方法
我正在尝试使用LambdaMetafactory替换反射,但我遇到了一个问题。如果我使用特定的类,那么它工作得很好,如下所示:Java 如何在运行时使用LambdaMetafactory访问动态类中的非静态方法,java,reflection,lambda-metafactory,Java,Reflection,Lambda Metafactory,我正在尝试使用LambdaMetafactory替换反射,但我遇到了一个问题。如果我使用特定的类,那么它工作得很好,如下所示: MethodHandles.Lookup lookup = MethodHandles.lookup(); MethodType type = MethodType.methodType(ResponseMsg.class,Map.class); MethodHandle mh = lookup.findVirtual(
MethodHandles.Lookup lookup = MethodHandles.lookup();
MethodType type = MethodType.methodType(ResponseMsg.class,Map.class);
MethodHandle mh = lookup.findVirtual(TestService.class,"testMethod",type);
TestService ins = TestService.getInstance();
MethodHandle factory = LambdaMetafactory.metafactory(
lookup, "apply", MethodType.methodType(Function.class,TestService.class),
type.generic(), mh, type).getTarget();
factory.bindTo(ins);
Function lambda = (Function) factory.invokeExact(ins);
public static Function generateLambda(@NotNull Class<?> cls,@NotNull String method) {
MethodHandles.Lookup lookup = MethodHandles.lookup();
MethodType type = MethodType.methodType(RETURN_TYPE,PARM_TYPE);
try {
MethodHandle mh = lookup.findVirtual(cls,method,type);
Object instance = getInstance(cls);
if(instance == null) {
return null;
}
MethodHandle factory = LambdaMetafactory.metafactory(
lookup, "apply", MethodType.methodType(Function.class,cls),
type.generic(), mh, type).getTarget();
factory.bindTo(cls.cast(instance));
return (Function) factory.invokeExact(cls.cast(instance));
} catch (Throwable e) {
logger.error("get Function fail, cause :" ,e);
return null;
}
}
但是如果我使用Class
替换特定的类,那么它将不起作用,如下所示:
MethodHandles.Lookup lookup = MethodHandles.lookup();
MethodType type = MethodType.methodType(ResponseMsg.class,Map.class);
MethodHandle mh = lookup.findVirtual(TestService.class,"testMethod",type);
TestService ins = TestService.getInstance();
MethodHandle factory = LambdaMetafactory.metafactory(
lookup, "apply", MethodType.methodType(Function.class,TestService.class),
type.generic(), mh, type).getTarget();
factory.bindTo(ins);
Function lambda = (Function) factory.invokeExact(ins);
public static Function generateLambda(@NotNull Class<?> cls,@NotNull String method) {
MethodHandles.Lookup lookup = MethodHandles.lookup();
MethodType type = MethodType.methodType(RETURN_TYPE,PARM_TYPE);
try {
MethodHandle mh = lookup.findVirtual(cls,method,type);
Object instance = getInstance(cls);
if(instance == null) {
return null;
}
MethodHandle factory = LambdaMetafactory.metafactory(
lookup, "apply", MethodType.methodType(Function.class,cls),
type.generic(), mh, type).getTarget();
factory.bindTo(cls.cast(instance));
return (Function) factory.invokeExact(cls.cast(instance));
} catch (Throwable e) {
logger.error("get Function fail, cause :" ,e);
return null;
}
}
第182行是:
return (Function) factory.invokeExact(cls.cast(instance));
我知道用静态的方法可以解决这个问题,但是我想知道在不改变非静态为静态的情况下有没有其他方法可以解决这个问题
下面是getInstance:
private static Object getInstance(@NotNull Class<?> cls) {
try {
Method getInstanceMethod = cls.getDeclaredMethod("getInstance");
return getInstanceMethod.invoke(null);
} catch (Exception e) {
logger.error("get instance fail, cause :" ,e);
return null;
}
}
私有静态对象getInstance(@NotNull Class cls){
试一试{
方法getInstanceMethod=cls.getDeclaredMethod(“getInstance”);
返回getInstanceMethod.invoke(null);
}捕获(例外e){
错误(“获取实例失败,原因:”,e);
返回null;
}
}
在这个方法中,我使用反射在类中查找静态方法getInstance,并返回一个实例,它只是一个简单的单例。问题是您正在使用
factory.bindTo(ins);
Function lambda = (Function) factory.invokeExact(ins);
分别
调用bindTo
将创建一个MethodHandle
,其第一个参数绑定到指定的对象实例,但是,您将忽略新的MethodHandle
。因此,在调用未绑定句柄时,需要再次将实例指定为参数
对于这个调用,编译时类型很重要。在第一个示例中,参数的编译时类型是正确的,因此调用具有正确的签名(TestService)函数
在第二个示例中,实例
的编译时类型是对象
,因此编译到字节码中的签名将是(对象)函数
,这不是完全匹配的。使用cls.cast(…)
没有帮助,因为如果在此处使用类型变量,这将执行运行时检查并断言泛型类型匹配,但两者都与invokeExact
调用的字节码无关
你有两个选择。您可以简单地使用invoke
,这允许在调用期间进行类型转换(牺牲一点性能)
或者更改代码以执行最初的意图,在调用之前绑定第一个参数:
factory = factory.bindTo(instance);
return (Function)factory.invokeExact();
由于对于预绑定的方法句柄,不需要参数,因此您将再次进行精确调用(bindTo
不是签名多态的,因此,将仅在运行时检查参数的类型)
你也可以把它写成一行
return (Function)factory.bindTo(instance).invokeExact();
你能发布你的
getInstance(cls)吗代码>方法代码?我已经发布了方法invokeExact
是一种特殊的方法,它使用多态签名,并且在参数类型很重要的情况下用作调用动态,因此call。invokeExact(“字符串”)
与不同。invokeExact((对象)“字符串”)
,这就是为什么MethodHandles可以快速处理静态类型代码的原因,因为它不需要每次都验证和强制转换所有这些内容?缓存的反射也很快,否则,您可以使用.invokeWithArguments
并修改方法句柄签名以期望不同的类型(但这将使性能降低很小一点)。在动态使用中,使用缓存的方法实例与使用MethodHandle之间没有更大的区别。如果您远离通配符,即公共静态函数generateLambda(@NotNull Class cls,…t instance=getInstance(cls);…
?(可能没有什么原因,因为类型擦除,但这样的代码更安全一些。)非常感谢,您完全解决了我的问题。但是我想说第二个选项必须写在一行中:return(Function)factory.bindTo(instance.invokeExact())第二个选项的关键点是不要忘记在使用两个语句时将bindTo
的结果再次分配给factory
。链接两个调用时,将自动对bindTo
的结果执行invokeExact
调用。
return (Function)factory.bindTo(instance).invokeExact();