java - 为什么这个 lambda 函数在错误的类加载器中启动类加载?

标签 java lambda reflection classloader

我正在开发一个具有插件框架的应用程序。它使用自定义类加载器从加密的 jar 文件中加载插件。最初,我将自己描绘成使用自定义类加载器作为引导系统类加载器。以这种方式它可以工作,但是使用自定义系统类加载器有一些缺点。

我正在尝试返工,以便自定义类加载器仅用于加载插件。插件本质上是分层的,因此需要相同的类上下文。为此,插件类加载器 CustomClassloader是一个扩展 ClassLoader 的单例并将父类加载器设置为 SystemClassloader(并将类加载委托(delegate)给父类,就像正常模式一样)。

这似乎运行良好,除非在特殊情况下我需要创建一个 lambda 函数,该函数允许在插件中定义的 POJO boolean 字段的通用(“反射”)设置。
lambda_set创建(在系统类加载器加载的应用程序 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);我得到一个 ClassNotFoundException对于 POJO 父类(super class)。每个 POJO 都扩展了它自己的父抽象类,该抽象类实现了 POJO_Interface并包含其字段和 getter/setter。当从自定义引导类加载器加载所有内容时,同样的功能也可以正常工作。我已经验证它正在尝试在 System 类加载器中专门加载 POJO 的父类,而不是 CustomClassloader。这是错误的。

我已验证 pojo.getClass().getClassLoader() == pojo_class.getClassLoader() == CustomClassloader.class但是,lambda_set.getClass().getClassLoader() == jdk.internal.loader.ClassLoaders$AppClassLoader .我不确定这是否是问题所在。

此行为在 JDK8-JDK14 中是相同的。

有没有办法可以制作 lambda_set利用我的CustomClassloader什么时候需要加载一个类?任何其他见解将不胜感激!

我还尝试设置应用程序主线程 ContextClassloader 并验证 lambda_set正在从 ContextClassLoader 为 CustomClassloader 的线程调用.这导致上述相同的行为。
    static {
        Thread.currentThread().setContextClassLoader(new CustomClassloader(ClassLoader.getSystemClassLoader()));
    }

public static void main(String[] args) {...}

最佳答案

根据霍尔格的评论:

{...} 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())是什么将上下文从错误的类加载器绑定(bind)到类。

这导致我寻找替代方案,其中一个出现在 MethodHanldes 中。来源评论/方法:
    /**
     * 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();

幸运的是,在我的例子中,所有东西都在同一个模块名下,所以我能够利用现有的 lookup在调用 MethodHandles.privateLookupIn(Class<?> targetClass, Lookup caller) .

通过更改,此功能现在可以正常工作。再次感谢 Holger 引导我朝着正确的方向前进。

如需更多信息,Holger 还回答了相关问题:

LambdaMetafactory to access class on a different ClassLoaderUse LambdaMetafactory to invoke one-arg method on class instance obtained from other classloader (对于 Java 8)

关于java - 为什么这个 lambda 函数在错误的类加载器中启动类加载?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/61735008/

相关文章:

ruby - 获取当前正在执行的方法的名称

java - eclipse : Element type "Context" must be declared

java - 在支持 bean (SEAM) 中启用 hibernate 过滤器

java - 鼠标拖拽

java - 第二个数字在 1 到 5 范围内(含)是什么意思?

java - 如何使用流在比赛结束后找到一件元素?

c# - 从表达式树中获取 MethodInfo

java - 计算对象图中多个数字的平均值

go - 如何在 go 中获取私有(private) reflect.Value 的内容?

typescript - 如何在 Type Script 中使用反射获取实现某个基类的子类?