2

某些 Java API 对调用者敏感。一个(可悲的未充分记录的 IMO)示例是System.load(),它仅将一些 JNI 代码加载到调用者的代码中ClassLoader

我有一个看起来大致像JniUtils.loadLibrary("nameoflibrary"). 它为当前架构找到合适的库,将其从 JAR 中提取出来,并将其传递给System.load(). 但是我刚刚遇到了一个调用者JniUtils.loadLibraryClassLoader自身不同的情况Jni。这导致库被加载到错误的ClassLoader中,导致UnsatisfiedLinkError一旦调用本机方法。

在不依赖 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());
    }
}

使用Lookup来解析和调用System.load()方法应该保留NeedsJni为调用者

有更好的解决方法吗?

4

2 回答 2

1

根据问题的复杂性,这可能适用也可能不适用。

在没有反射的上下文中,标准 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(我知道这更难看,但有了清晰的文档,它不会那么糟糕)。

于 2015-07-15T00:51:50.503 回答
0

如果类路径不兼容,请考虑定义一个必须提供的抽象类。这提供了“调用者敏感性”,因为调用者必须提供实现,从而提供必要的上下文。

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);
    }
}
于 2015-07-15T11:58:33.400 回答