某些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/

10-10 21:53
查看更多