Java 如何方便地包装调用方敏感API?

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,一

某些Java API对调用方敏感。一个(文档不足的IMO)示例是
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); } }