为什么此 lambda 函数会在错误的 class 加载程序中启动 class 加载?
Why is this lambda function initiating class loading in the wrong classloader?
我正在开发一个具有插件框架的应用程序。它使用自定义 classloader 从加密的 jar 文件加载插件。最初我把自己描绘成使用自定义 classloader 作为 bootstrapped 系统 classloader。通过这种方式它可以工作,但是使用自定义系统有一些缺点 classloader.
我正在尝试返工,以便自定义 classloader 仅用于加载插件。这些插件本质上是分层的,因此需要相同的 class 上下文。为此,插件 classloader CustomClassloader
是一个扩展 ClassLoader
并将 parent classloader 设置为 SystemClassloader 的单例(并委托 class正在按正常模式加载到 parent。
除了在特定情况下我需要创建一个允许在插件中定义的 POJO 布尔字段的通用 ('reflective') 设置的 lambda 函数外,这似乎运行良好。
lambda_set
创建(在系统 classloader 加载的应用程序 jar 中定义):
private BiConsumer<POJO_Interface, Object> lambda_set = null;
Class[] parameter = new Class[1];
parameter[0] = field_clazz; // in this case it is boolean.class
set_method = pojo_class.getMethod(setter.trim(), parameter); // setter method name
set_method.setAccessible(true);
MethodHandles.Lookup lookup = MethodHandles.lookup();
MethodHandle target = lookup.unreflect(set_method);
MethodType func = target.type();
MethodType func1 = func.changeParameterType(0, Object.class);
if(func.parameterCount() >= 2)
func1 = func1.changeParameterType(1, Object.class);
CallSite site = LambdaMetafactory.metafactory(lookup, "accept",
MethodType.methodType(BiConsumer.class), func1, target, func);
MethodHandle factory = site.getTarget();
lambda_set = (BiConsumer) factory.invoke();
当我调用 lambda_set.accept(pojo, value);
时,我得到了 POJO superclass 的 ClassNotFoundException
。每个 POJO 扩展它自己的 parent 抽象 class,实现 POJO_Interface
并包含其字段和 getters/setters。当从自定义 bootstrap classloader 加载所有内容时,这个相同的函数工作正常。我已经验证它正在尝试在系统 classloader 中专门加载 POJO 的 parent class 而不是错误的 CustomClassloader
。
我已经验证了 pojo.getClass().getClassLoader() == pojo_class.getClassLoader() == CustomClassloader.class
但是 lambda_set.getClass().getClassLoader() == jdk.internal.loader.ClassLoaders$AppClassLoader
。我不确定这是否是问题所在。
此行为在 JDK8-JDK14 中是相同的。
有什么方法可以让 lambda_set
在需要加载 class 时利用我的 CustomClassloader
?任何其他见解将不胜感激!
我还尝试设置应用程序主线程 ContextClassloader 并验证 lambda_set
是从 ContextClassLoader 是 CustomClassloader
的线程调用的。这会导致上述相同的行为。
static {
Thread.currentThread().setContextClassLoader(new CustomClassloader(ClassLoader.getSystemClassLoader()));
}
public static void main(String[] args) {...}
根据 Holger 的评论:
{...} You are passing the result of MethodHandles.lookup() which encapsulates the context in which the MethodHandles.lookup() expression is contained. The fix is to provide a lookup representing a context which can resolve the type, e.g. encapsulating the target method’s declaring class. – Holger
此外,通过深入挖掘 MethodHandles
源代码,我们可以清楚地看到 Holger 指的是什么:
/**
* Returns a {@link Lookup lookup object} with
* full capabilities to emulate all supported bytecode behaviors of the caller.
{...}
*/
@CallerSensitive
@ForceInline // to ensure Reflection.getCallerClass optimization
public static Lookup lookup() {
return new Lookup(Reflection.getCallerClass());
}
对 Reflection.getCallerClass())
的调用是将上下文从错误的 class 加载程序绑定到 class 的原因。
这促使我寻找替代方案,并且在 MethodHanldes
来源中出现了一个 comments/methods:
/**
* Returns a {@link Lookup lookup} object on a target class to emulate all supported
* bytecode behaviors, including <a href="MethodHandles.Lookup.html#privacc">private access</a>.
{...}
*/
public static Lookup privateLookupIn(Class<?> targetClass, Lookup caller)
throws IllegalAccessException {...}
基于这种理解,我已将问题中的代码更新为以下内容:
private BiConsumer<POJO_Interface, Object> lambda_set = null;
Class[] parameter = new Class[1];
parameter[0] = field_clazz; // in this case it is boolean.class
set_method = pojo_class.getMethod(setter.trim(), parameter); // setter method name
set_method.setAccessible(true);
MethodHandles.Lookup lookup = MethodHandles.lookup();
/////// FIXED //////////
lookup = MethodHandles.privateLookupIn(pojo_class, lookup);
/////// FIXED //////////
MethodHandle target = lookup.unreflect(set_method);
MethodType func = target.type();
MethodType func1 = func.changeParameterType(0, Object.class);
if(func.parameterCount() >= 2)
func1 = func1.changeParameterType(1, Object.class);
CallSite site = LambdaMetafactory.metafactory(lookup, "accept",
MethodType.methodType(BiConsumer.class), func1, target, func);
MethodHandle factory = site.getTarget();
lambda_set = (BiConsumer) factory.invoke();
幸运的是,在我的例子中,一切都在同一个模块名下,所以我能够在调用 MethodHandles.privateLookupIn(Class<?> targetClass, Lookup caller)
.
时利用现有的 lookup
经过更改,此功能现在可以正常工作。再次感谢 Holger 为我指明了正确的方向。
关于更多信息,霍尔格也回答了相关问题:
and (对于Java 8)
我正在开发一个具有插件框架的应用程序。它使用自定义 classloader 从加密的 jar 文件加载插件。最初我把自己描绘成使用自定义 classloader 作为 bootstrapped 系统 classloader。通过这种方式它可以工作,但是使用自定义系统有一些缺点 classloader.
我正在尝试返工,以便自定义 classloader 仅用于加载插件。这些插件本质上是分层的,因此需要相同的 class 上下文。为此,插件 classloader CustomClassloader
是一个扩展 ClassLoader
并将 parent classloader 设置为 SystemClassloader 的单例(并委托 class正在按正常模式加载到 parent。
除了在特定情况下我需要创建一个允许在插件中定义的 POJO 布尔字段的通用 ('reflective') 设置的 lambda 函数外,这似乎运行良好。
lambda_set
创建(在系统 classloader 加载的应用程序 jar 中定义):
private BiConsumer<POJO_Interface, Object> lambda_set = null;
Class[] parameter = new Class[1];
parameter[0] = field_clazz; // in this case it is boolean.class
set_method = pojo_class.getMethod(setter.trim(), parameter); // setter method name
set_method.setAccessible(true);
MethodHandles.Lookup lookup = MethodHandles.lookup();
MethodHandle target = lookup.unreflect(set_method);
MethodType func = target.type();
MethodType func1 = func.changeParameterType(0, Object.class);
if(func.parameterCount() >= 2)
func1 = func1.changeParameterType(1, Object.class);
CallSite site = LambdaMetafactory.metafactory(lookup, "accept",
MethodType.methodType(BiConsumer.class), func1, target, func);
MethodHandle factory = site.getTarget();
lambda_set = (BiConsumer) factory.invoke();
当我调用 lambda_set.accept(pojo, value);
时,我得到了 POJO superclass 的 ClassNotFoundException
。每个 POJO 扩展它自己的 parent 抽象 class,实现 POJO_Interface
并包含其字段和 getters/setters。当从自定义 bootstrap classloader 加载所有内容时,这个相同的函数工作正常。我已经验证它正在尝试在系统 classloader 中专门加载 POJO 的 parent class 而不是错误的 CustomClassloader
。
我已经验证了 pojo.getClass().getClassLoader() == pojo_class.getClassLoader() == CustomClassloader.class
但是 lambda_set.getClass().getClassLoader() == jdk.internal.loader.ClassLoaders$AppClassLoader
。我不确定这是否是问题所在。
此行为在 JDK8-JDK14 中是相同的。
有什么方法可以让 lambda_set
在需要加载 class 时利用我的 CustomClassloader
?任何其他见解将不胜感激!
我还尝试设置应用程序主线程 ContextClassloader 并验证 lambda_set
是从 ContextClassLoader 是 CustomClassloader
的线程调用的。这会导致上述相同的行为。
static {
Thread.currentThread().setContextClassLoader(new CustomClassloader(ClassLoader.getSystemClassLoader()));
}
public static void main(String[] args) {...}
根据 Holger 的评论:
{...} You are passing the result of MethodHandles.lookup() which encapsulates the context in which the MethodHandles.lookup() expression is contained. The fix is to provide a lookup representing a context which can resolve the type, e.g. encapsulating the target method’s declaring class. – Holger
此外,通过深入挖掘 MethodHandles
源代码,我们可以清楚地看到 Holger 指的是什么:
/**
* Returns a {@link Lookup lookup object} with
* full capabilities to emulate all supported bytecode behaviors of the caller.
{...}
*/
@CallerSensitive
@ForceInline // to ensure Reflection.getCallerClass optimization
public static Lookup lookup() {
return new Lookup(Reflection.getCallerClass());
}
对 Reflection.getCallerClass())
的调用是将上下文从错误的 class 加载程序绑定到 class 的原因。
这促使我寻找替代方案,并且在 MethodHanldes
来源中出现了一个 comments/methods:
/**
* Returns a {@link Lookup lookup} object on a target class to emulate all supported
* bytecode behaviors, including <a href="MethodHandles.Lookup.html#privacc">private access</a>.
{...}
*/
public static Lookup privateLookupIn(Class<?> targetClass, Lookup caller)
throws IllegalAccessException {...}
基于这种理解,我已将问题中的代码更新为以下内容:
private BiConsumer<POJO_Interface, Object> lambda_set = null;
Class[] parameter = new Class[1];
parameter[0] = field_clazz; // in this case it is boolean.class
set_method = pojo_class.getMethod(setter.trim(), parameter); // setter method name
set_method.setAccessible(true);
MethodHandles.Lookup lookup = MethodHandles.lookup();
/////// FIXED //////////
lookup = MethodHandles.privateLookupIn(pojo_class, lookup);
/////// FIXED //////////
MethodHandle target = lookup.unreflect(set_method);
MethodType func = target.type();
MethodType func1 = func.changeParameterType(0, Object.class);
if(func.parameterCount() >= 2)
func1 = func1.changeParameterType(1, Object.class);
CallSite site = LambdaMetafactory.metafactory(lookup, "accept",
MethodType.methodType(BiConsumer.class), func1, target, func);
MethodHandle factory = site.getTarget();
lambda_set = (BiConsumer) factory.invoke();
幸运的是,在我的例子中,一切都在同一个模块名下,所以我能够在调用 MethodHandles.privateLookupIn(Class<?> targetClass, Lookup caller)
.
lookup
经过更改,此功能现在可以正常工作。再次感谢 Holger 为我指明了正确的方向。
关于更多信息,霍尔格也回答了相关问题: