为什么这个lambda函数在错误的classloader中发起类加载?

问题描述 投票:2回答:1

我正在开发一个有插件框架的应用程序。 它使用一个自定义的classloader从加密的jar文件中加载插件。 最初,我把自己画成使用自定义类加载器作为一个引导的系统类加载器。 这样一来,它就工作了,但使用自定义系统类加载器也有一些缺点。

我正试图重新设计,使自定义类加载器只利用来加载插件。 插件具有层次性,因此需要相同的类上下文。 为此,插件类加载器 CustomClassloader 是一个单子,它扩展了 ClassLoader 并将父类加载器设置为SystemClassloader(并按照正常模式将类加载委托给父类)。

除了在一个特殊的情况下,我需要创建一个lambda函数,允许通用('反射')设置一个定义在插件中的POJO布尔字段。

lambda_set 我需要创建一个lambda函数,允许通用('反射')设置一个定义在插件中的POJO布尔字段(定义在一个由系统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); 我得到一个 ClassNotFoundException 为POJOs的超类。 每个POJO都扩展了它自己的父类抽象类,实现了 POJO_Interface 并包含其字段和getterssetters。 当所有的东西都从自定义的bootstrap classloader中加载时,这个函数也能正常工作。 我已经验证了它是试图只在System classloader中加载POJO的父类,而不是在系统类加载器中加载。CustomClassloader 这是错的。

我已经验证了 pojo.getClass().getClassLoader() == pojo_class.getClassLoader() == CustomClassloader.class 然而, lambda_set.getClass().getClassLoader() == jdk.internal.loader.ClassLoaders$AppClassLoader. 我不知道是不是这个问题。

这种行为在JDK8-JDK14中是一样的。

有什么方法可以让我在JDK8-JDK14中的 lambda_set 利用我 CustomClassloader 当它需要加载一个类? 如果有任何其他的见解,我将感激不尽!我也尝试过设置应用程序的主线程ContextClassloader,并验证了它在需要加载类的时候,会有什么变化。

我也试着设置了应用程序主线程ContextClassloader,并验证了该线程中的 lambda_set 被一个线程调用,而这个线程的 ContextClassLoader 是 CustomClassloader. 这导致了与上面描述的相同的行为。

    static {
        Thread.currentThread().setContextClassLoader(new CustomClassloader(ClassLoader.getSystemClassLoader()));
    }

public static void main(String[] args) {...}
java lambda reflection classloader
1个回答
1
投票

基于Holger的评论。

{...} 你传递的是MethodHandles.lookup()的结果,它封装了MethodHandles.lookup()表达式所包含的上下文。修正的方法是提供一个代表上下文的查找,它可以解析类型,例如封装目标方法的声明类。- 霍尔格

而且还挖过 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()) 是将上下文与错误的classloader类绑定在一起的。

这使我不得不寻找一个替代方案,其中一个在 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在不同的ClassLoader上访问类。使用LambdaMetafactory对从其他classloader获得的类实例调用一个参数方法。 (适用于Java 8)

© www.soinside.com 2019 - 2024. All rights reserved.