如何方便地包装一个对呼叫者敏感的API?

时间:2015-07-14 20:57:26

标签: java reflection methodhandle

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

有更好的解决方法吗?

1 个答案:

答案 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;是那么糟糕。)