某些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。有更好的解决方法吗?
最佳答案
根据问题的复杂程度,这可能适用也可能不适用。
在没有反思的情况下,标准Java代码很难复制调用者敏感度,甚至更难以将其“模拟”为调用者敏感函数。在我看来,即使代码完成了,也将变得难以理解,或者处理了我认为不必要的深层黑暗语言功能。
这里的基本问题是System.load()
对呼叫者敏感,并且您试图通过在自己调用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发生某种“ OnLibraryLoad”事件)。在那种情况下,也许添加一个方法在您的主JniUtils
类或返回的Library类中运行后加载代码(我知道这很丑陋,但是有了明确的文档,情况可能不会那么糟)。关于java - 如何方便地包装对调用者敏感的API?,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/31417219/