某些Java API对呼叫者敏感。一个(可悲的未充分记录的IMO)示例是System.load()
,它将一些JNI代码加载到调用者的ClassLoader
中。
我有一个看起来大致类似JniUtils.loadLibrary("nameoflibrary")
的包装器。它为当前体系结构找到适当的库,从JAR中提取它,并将其传递给System.load()
。但我刚遇到JniUtils.loadLibrary
的来电与ClassLoader
本身Jni
不同的情况。这导致库被加载到错误的ClassLoader
中,一旦调用本机方法就会导致UnsatisfiedLinkError
。
如果不依赖于像sun.reflect.Reflection.getCallerClass()
这样的JVM内部,有没有办法解决这个问题?我目前的想法是改变这样的包装:
public class JniUtils {
public static void loadLibrary(String libraryName, MethodHandles.Lookup lookup);
}
可以像这样调用:
public class NeedsJni {
static {
JniUtils.loadLibrary("nameoflibrary", MethodHandles.lookup());
}
}
使用Lookup
解析并调用System.load()
方法should preserve NeedsJni
as the caller。
有更好的解决方法吗?
答案 0 :(得分:1)
根据问题的复杂程度,这可能适用也可能不适用。
在没有反思的情境下,标准的Java代码难以复制调用者敏感度,甚至更难以模仿"模拟"它对呼叫者敏感的功能。在我看来,即使完成了代码,也会非常模糊,或者会处理我认为不必要的深层语言功能。
您遇到的根本问题是,System.load()
对来电者非常敏感,您正在尝试构建自己的“增强型”#34; System.load()
在自己调用System.load()
之前执行一系列其他任务。为什么不将System.load()
确切地保留在开始时的位置?
不是尝试替换System.load()
的功能,而是将其与JniUtils
类互补。写一个JniUtils.fetchLibrary()
,它返回一个字符串,原始调用者可以在其中加载。更好的是,返回一个自定义对象Library
(或等效的其他名称),其中包含一个允许检索应传递给System.load()
的字符串的方法。由此,对load()
的调用可能来自需要,而您的调用者不敏感代码可以单独进行所有初始化。
这个例子的一些东西会很好:
public class JniUtils {
private static final HashMap<String, JniLibrary> cachedLibs = new HashMap<>();
public static JniLibrary fetchLibrary(String libname){
// Check cache for library
if(cachedLibs.containsKey(libname)){
return cachedLibs.get(libname);
}else{
JniLibrary lib = preloadLibrary(libname);
if(lib != null){
cachedLibs.put(libname, lib);
}
return lib;
}
}
/**
* Internal logic to prepare and generate a library instance
*
* @return JNI library on success, null on failure.
*/
private static JniLibrary preloadLibrary(String libname){
// Find lib
// Extract
// Get path
// Construct JniLibrary instance
// Return library as appropriate
}
/**
* Class representing a loadable JniLibrary
*/
public class JniLibrary{
public String getLibraryPath();
// Other potentially useful methods
}
}
public class NeedsJni {
static {
JniLibrary lib = JniUtils.fetchLibrary("nameoflibrary");
if(lib != null){
System.load(lib.getLibraryPath()); // Caller-sensitivity respected
}else{
// Well.... this is awkward.
}
}
}
这不仅可以解决调用者敏感度问题,而且额外的缓存可以防止额外的提取/体系结构查找和最终失败(因为您提取的文件可能已经被使用),允许多次调用System.load()
来自不同类别下的不同类别。
这种方法的反制是,如果在自定义System.load()
方法中loadLibrary()
之后必须立即执行重要代码(突然间,您希望java会有某种&#34} ; OnLibraryLoad&#34; event)。在这种情况下,也许添加一个方法来在主JniUtils
类或返回的库类中运行后加载代码(我知道它更难看,但是有了清晰的文档,它可以&#39;是那么糟糕。)