【问题标题】:How to influence search path of System.loadLibrary() through Java code?如何通过 Java 代码影响 System.loadLibrary() 的搜索路径?
【发布时间】:2021-04-01 20:52:08
【问题描述】:

在一个 Java 项目中,我正在使用一个第三方库,它通过以下方式加载一些本机库

System.loadLibrary("libName");

我希望能够从我的应用程序中影响此方法的搜索路径,以便用户无需在命令行上指定正确的 java.library.path 值(此值取决于当前的操作系统和架构)。例如,在 Windows 上我想将其设置为“lib/native/windows”,在 Linux 32 位上设置为“lib/native/linux32”等。

我试过了

System.setProperty("java.library.path", ...)

但这被忽略了,显然是因为 JVM 在我的代码运行之前只读取了一次这个属性。

我还尝试在使用依赖于它的 Java 库之前加载本机库

System.load("fullPath/lib")

这个调用成功了,但是当使用 System.loadLibrary() 再次加载原生库时,仍然会出现 UnsatisfiedLinkError。

我找到的唯一方法如下:

  • 添加抽象外部库的整个 API 的接口。
  • 在其余代码中仅使用这些接口。
  • 添加实现接口并委托给库的类。
  • 编写自己的类加载器,即
    • 覆盖 findLibary() 以便在正确的路径中找到本机库
    • 覆盖 loadClass() 并自行加载外部库和包装层的所有类,而不是像默认的 ClassLoader 那样尝试委托给其父级
  • 确保使用普通的 ClassLoader 加载接口,并使用我自己的 ClassLoader 加载包装类和外部库。

这行得通,但我觉得它很复杂,而且很费力,因为我需要添加所有这些接口。有没有更简单的方法?

【问题讨论】:

  • 是否与System.load("fullPath/lib/native/windows/libName.dll") 一起使用?你为什么用System.loadLibrary(..)重新加载它?
  • @dacwe 是的,它确实有效。但是第三方库尝试用 System.loadLibrary(..) 再次加载,失败了。

标签: java java-native-interface


【解决方案1】:

我需要更改单元测试的 dll 路径。我尝试了以下 hack 并且成功了:

System.setProperty( "java.library.path", "/path/to/libs" ); 
Field fieldSysPath = ClassLoader.class.getDeclaredField( "sys_paths" );
fieldSysPath.setAccessible( true );
fieldSysPath.set( null, null );

有关说明,请参阅original link

【讨论】:

  • 可能是最实用的单元测试方式,但我不想在生产代码中使用它。
  • Phillipp,当然它也适用于 IDE。为此,发明了“运行时配置”。只需创建一个配置,并正确设置环境变量,将其置于版本控制之下,以便项目中的其他人也能得到它。
  • 这种方法现在在 openjdk 上运行时会导致 Classloader.loadLibrary 中出现 NullPointerException。
【解决方案2】:
  1. 没有经过批准的方法可以更改正在运行的 JVM 的库路径。
  2. 您不能多次加载本机库...并且您不能卸载本机库以便再次加载它:http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4171986

根据您上面的 cmets(特别是 3rd 方库的行为),我认为您最好的选择是在启动 JVM 时获取库路径。

请注意,有一种更改库路径的 hacky 方法(请参阅https://stackoverflow.com/a/24258955/139985),但它涉及令人讨厌的反射,并且据报道它不适用于所有 Java 版本。当然,它依赖于 ClassLoader 未记录的私有实现细节,可能从一个版本更改为下一个版本。

【讨论】:

    【解决方案3】:

    最近刚遇到这个问题并使用 OpenJDK,其中抛出了 NullPointerException(正如 @0-0 在他的 comment 中提到的 Samil 的回答)。以下适用于 OpenJDK,应该也适用于 Oracle JDK。

    (选项 1)替换 java.library.path

    System.setProperty("java.library.path", newPath);
    Field field = ClassLoader.class.getDeclaredField("sys_paths");
    field.setAccessible(true);
    field.set(ClassLoader.getSystemClassLoader(), new String[]{newPath});
    

    (选项 2)添加到现有的 java.library.path

    String libPath = System.getProperty("java.library.path");
    String newPath;
    
    if (libPath == null || libPath.isEmpty()) {
        newPath = path;
    } else {
        newPath = path + File.pathSeparator + libPath;
    }
    
    System.setProperty("java.library.path", newPath);
    
    Field field = ClassLoader.class.getDeclaredField("sys_paths");
    field.setAccessible(true);
        
    // Create override for sys_paths
    ClassLoader classLoader = ClassLoader.getSystemClassLoader(); 
    List<String> newSysPaths = new ArrayList<>();
    newSysPaths.add(path);  
    newSysPaths.addAll(Arrays.asList((String[])field.get(classLoader)));
               
    field.set(classLoader, newSysPaths.toArray(new String[newSysPaths.size()]));
    

    【讨论】:

      【解决方案4】:

      我尝试在我的 Mac 上为 Java 应用程序加载本机 Growl 库,其中该库位于我的应用程序类路径的根目录中:

      System.load(GrowlUtils.class.getResource("/libgrowl.jnilib").getFile().toString());
      

      【讨论】:

      • 我可以在自己的代码中加载库,这没问题。问题是第三方代码用System.loadLibrary("libName");加载库,所以我需要一种方法来影响这个方法的行为,而不是另一种加载库的方法。
      • 您可以创建一个使用不同调用的类的版本,并将该版本放在类路径中更高的位置,以便首先获取它
      • 有趣的想法,但当然非常丑陋,因为您需要创建该类(特别是如果它在仅二进制库中)并且您需要在库更新时对其进行更新。
      • 我承认这是一个肮脏的 hack 并且在升级时可能会导致一些额外的工作,但是我们已经成功地使用这种方法修复/更改了 Liferay 中我们无法触及的东西。然而,我们确实总是需要有可用的原始源,并且不需要从反编译的类开始。
      【解决方案5】:

      有没有更简单的方法?

      是的,提供批处理/脚本文件来启动应用程序。然后您可以在批处理/shell 文件中设置正确的路径,甚至可以从环境变量中读取值。比从应用程序内部尝试要容易得多。

      【讨论】:

      • 是的,我们目前就是这样做的。但这不适用于从 IDE 中启动它或将其用作 Eclipse 插件的用户。
      【解决方案6】:

      虽然技术上正确,但这些答案具有误导性。在 Windows 上设置环境变量 PATH 或在 unix 上设置 LD_LIBRARY_PATH 将改变 jvm 查找库的位置: What is LD_LIBRARY_PATH and how to use it?

      在 Linux 上: 导出 LD_LIBRARY_PATH=/usr/.../ 然后: java ....

      【讨论】:

      • 我认为你错过了问题的重点。这是关于更改 已经启动的 JVM 的库路径。更改环境变量对此无济于事。为时已晚。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-10-20
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-06-15
      相关资源
      最近更新 更多