Java 如何方便地包装调用方敏感API?
某些Java API对调用方敏感。一个(文档不足的IMO)示例是Java 如何方便地包装调用方敏感API?,java,reflection,methodhandle,Java,Reflection,Methodhandle,某些Java API对调用方敏感。一个(文档不足的IMO)示例是System.load(),它只将一些JNI代码加载到调用方的类加载器中 我有一个包装器,看起来大致类似于JniUtils.loadLibrary(“nameoflibrary”)。它为当前体系结构找到合适的库,从JAR中提取它,并将它传递给System.load()。但是我遇到了一个例子,JniUtils.loadLibrary的调用方与Jni本身不在同一个ClassLoader中。这导致库被加载到错误的ClassLoader,一
System.load()
,它只将一些JNI代码加载到调用方的类加载器中
我有一个包装器,看起来大致类似于JniUtils.loadLibrary(“nameoflibrary”)
。它为当前体系结构找到合适的库,从JAR中提取它,并将它传递给System.load()
。但是我遇到了一个例子,JniUtils.loadLibrary
的调用方与Jni
本身不在同一个ClassLoader
中。这导致库被加载到错误的ClassLoader
,一旦调用本机方法,就会导致未满足的linkerror
如果不依赖于JVM内部,比如sun.reflect.Reflection.getCallerClass()
,有没有办法解决这个问题?我当前的想法是如下更改包装器:
public class JniUtils {
public static void loadLibrary(String libraryName, MethodHandles.Lookup lookup);
}
public class NeedsJni {
static {
JniUtils.loadLibrary("nameoflibrary", MethodHandles.lookup());
}
}
可以这样称呼:
public class JniUtils {
public static void loadLibrary(String libraryName, MethodHandles.Lookup lookup);
}
public class NeedsJni {
static {
JniUtils.loadLibrary("nameoflibrary", MethodHandles.lookup());
}
}
使用Lookup
解析并调用System.load()
方法
是否有更好的解决方法?根据问题的复杂性,这可能适用,也可能不适用
在没有反射的上下文中,标准java代码很难复制调用方敏感度,更难将其“模拟”为调用方敏感函数。即使这样做了,在我看来,代码将是非常晦涩难懂的,或者会处理我认为不必要的深奥的语言特征。
这里的基本问题是System.load()
对调用方敏感,您试图通过在调用System.load()
之前执行一系列其他任务来构建自己的“增强型”System.load()
。为什么不将System.load()
准确地放在启动时的位置
与其尝试替换System.load()
的功能,不如用JniUtils
类来补充它。编写一个JniUtils.fetchLibrary()
,它返回一个字符串,原始调用者可以从中加载。更好的是,返回一个自定义对象库
(或其他等效名称),其中包含一个方法,该方法允许检索应传递给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.
}
}
}
公共类JniUtils{
private static final HashMap cachedLibs=new HashMap();
公共静态JniLibrary获取库(字符串libname){
//检查库的缓存
if(cachedLibs.containsKey(libname)){
返回cachedLibs.get(libname);
}否则{
JniLibrary lib=预加载库(libname);
if(lib!=null){
cachedLibs.put(libname,lib);
}
返回lib;
}
}
/**
*准备和生成库实例的内部逻辑
*
*@成功返回JNI库,失败返回null。
*/
私有静态JniLibrary库(字符串libname){
//查找库
//提取
//获取路径
//构造JniLibrary实例
//视情况返回库
}
/**
*表示可加载JniLibrary的类
*/
公共类图书馆{
公共字符串getLibraryPath();
//其他可能有用的方法
}
}
公共类需求{
静止的{
JniLibrary lib=JniUtils.fetchLibrary(“图书馆名称”);
if(lib!=null){
System.load(lib.getLibraryPath());//调用方敏感度
}否则{
//嗯……这很尴尬。
}
}
}
这不仅解决了调用方敏感性问题,额外的缓存还防止了额外的提取/体系结构查找和最终失败(因为您提取到的文件可能已被使用),从而允许从不同的类加载器下的不同类多次调用System.load()
这种方法的反例是,如果在自定义的loadLibrary()
方法中有重要的代码必须在System.load()
之后立即执行(突然,您希望java会有某种“OnLibraryLoad”事件)。在这种情况下,也许可以添加一个方法来运行主JniUtils
类或返回的库类中的加载后代码(我知道它更难看,但有清晰的文档,它不会那么糟糕)。考虑定义一个抽象类,如果类路径不兼容,则必须提供该类。这提供了“调用方敏感度”,因为调用方必须提供实现,从而提供必要的上下文
public class JniUtils {
public static void loadLibrary(String libraryName, Delegate delegate) {
//use delegate's provided classloader to find the native library from within the jar and extract
//ask the delegate to load the extracted library
}
public static abstract class Delegate {
/**
* @return The specific ClassLoader instance to use to find the native resource. If returning null, the ClassLoader of JniUtils will be used.
*/
public ClassLoader getLibraryClassLoader() {
return getClass().getClassLoader();
}
/**
* <p>
* The callback method which will be called once the native library's name has been
* resolved. This MUST be implemented by the subclass, so that it resolves as the "caller"
* class for {@link System#loadLibrary(String)}.
* </p>
*
* @param libraryName The name of the library to load.
*/
public abstract void loadLibrary(String libraryName);
}
}
公共类JniUtils{
公共静态void加载库(字符串库名称、委托){
//使用委托提供的类加载器从jar中查找本机库并提取
//要求学员加载提取的库
}
公共静态抽象类委托{
/**
*@返回用于查找本机资源的特定类加载器实例。如果返回null,将使用JniUtils的类加载器。
*/
公共类加载器getLibraryClassLoader(){
返回getClass().getClassLoader();
}
/**
*
*调用本机库名称后将调用的回调方法
*已解析。这必须由子类实现,以便它解析为“调用者”
*{@link System#loadLibrary(String)}的类。
*
*
*@param libraryName要加载的库的名称。
*/
公共抽象void loadLibrary(字符串libraryName);
}
}