我正在开发一个具有插件框架的应用程序。它使用自定义类加载器从加密的 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 ClassLoader和 Use LambdaMetafactory to invoke one-arg method on class instance obtained from other classloader (对于 Java 8)
关于java - 为什么这个 lambda 函数在错误的类加载器中启动类加载?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/61735008/