将so打包到jar中的方法

http://tommwq.tech/blog/2020/11/10/197

在Java 8中, System.loadLibrary 会调用 ClassLoader.loadLibrary0 加载native库,后者从静态成员 usr_paths 中搜索。基于这一点,我们可以将so打包在jar中。在运行时,把so复制到临时目录,然后修改 ClassLoader.usr_paths ,再调用 System.loadLibrary 完成so的加载。

Listing 1: 核心代码

public class SoWrapper {

    private static final Set<String> loadedLibraries = new ConcurrentSkipListSet<String>();
    private File temporaryDirectory = null;

    private static <T> T[] append(T[] array, T element) {
        List<T> list = new ArrayList<>(Arrays.asList(array));
        list.add(element);
        return list.toArray(array);
    }

    public static String getDynamicLibraryPath() {
        return String.format("%s%d",
                OperatingSystem.getShortName(),
                OperatingSystem.is32bit() ? 32 : 64);
    }

    public static String getDynamicLibraryFileName(String dynamicLibraryFile) {
        return getDynamicLibraryPath() + "/" + dynamicLibraryFile;
    }

    public SoWrapper() throws IOException {
        temporaryDirectory = Files.createTempDirectory("tmp").toFile();
        temporaryDirectory.createNewFile();
        temporaryDirectory.deleteOnExit();
    }

    public void loadLibrary(String library) throws IOException, NoSuchFieldException, IllegalAccessException {
        if (loadedLibraries.contains(library)) {
            return;
        }

        String dynamicLibraryFilename = OperatingSystem.getDynamicLibraryPrefix() + library + OperatingSystem.getDynamicLibrarySuffix();
        File dynamicLibraryFile = new File(temporaryDirectory, dynamicLibraryFilename);

        try (InputStream inputStream = getClass().getClassLoader()
                .getResourceAsStream(getDynamicLibraryFileName(dynamicLibraryFilename))) {
            Files.copy(inputStream, dynamicLibraryFile.toPath());
        }

        Field field = ClassLoader.class.getDeclaredField("usr_paths");
        boolean originalAccessible = field.isAccessible();
        field.setAccessible(true);
        String[] usrPaths = (String[]) field.get(null);
        String[] newUsrPaths = append(usrPaths, temporaryDirectory.getAbsolutePath());
        field.set(null, newUsrPaths);
        field.setAccessible(originalAccessible);

        System.loadLibrary(library);

        loadedLibraries.add(library);
    }
}

32位JVM是无法加载64位native库的,通用还有跨平台的问题。因此需要在jar包中保存多个平台不同架构的native库,再加载时根据JVM平台进行选择。这里采用的方法是将库保存在OSOS{ARCH}(比如windows64、linux32)目录下。

Listing 2: 使用示例

SoWrapper soWrapper = new SoWrapper();

soWrapper.loadLibrary("foo");
soWrapper.loadLibrary("bar");

Listing 3: 辅助类

public class OperatingSystem {
    public static String getOperatingSystem() {
        return System.getProperty("os.name");
    }

    public static boolean isWindows() {
        return getOperatingSystem().toLowerCase().contains("windows");
    }

    public static boolean isLinux() {
        return getOperatingSystem().toLowerCase().contains("linux");
    }

    public static boolean isMac() {
        return getOperatingSystem().toLowerCase().contains("mac");
    }

    public static String getArch() {
        return System.getProperty("os.arch");
    }

    public static boolean is32bit() {
        return getArch().contains("86");
    }

    public static boolean is64bit() {
        return getArch().contains("64");
    }

    public static String getShortName() {
        if (isWindows()) {
            return "windows";
        } else if (isLinux()) {
            return "linux";
        } else if (isMac()) {
            return "mac";
        }
        return "";
    }

    public static String getDynamicLibrarySuffix() {
        switch (getShortName()) {
            case "windows":
                return ".dll";
            case "linux":
                return ".so";
            case "mac":
                return ".dylib";
            default:
                return "";
        }
    }

    public static String getDynamicLibraryPrefix() {
        switch (getShortName()) {
            case "linux":
                return "lib";
            default:
                return "";
        }
    }
}
  • 17
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
Maven 本身并不支持将动态链接库(.so 文件)打包JAR 文件。这是因为 JAR 文件是 Java打包格式,主要用于打包 Java 类和资源文件。 然而,您可以使用 Maven 的 `maven-assembly-plugin` 插件来创建一个可执行的 JAR 文件,并将动态链接库与 JAR 文件一起分发。以下是一个示例的 Maven 配置: ```xml <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-assembly-plugin</artifactId> <version>3.3.0</version> <configuration> <descriptorRefs> <descriptorRef>jar-with-dependencies</descriptorRef> </descriptorRefs> <archive> <manifest> <mainClass>com.example.MainClass</mainClass> </manifest> </archive> <attachments> <attachment> <classifier>libs</classifier> <file>path/to/your.so</file> </attachment> </attachments> </configuration> <executions> <execution> <id>make-assembly</id> <phase>package</phase> <goals> <goal>single</goal> </goals> </execution> </executions> </plugin> </plugins> </build> ``` 在上面的配置,我们使用了 `maven-assembly-plugin` 插件的 `jar-with-dependencies` 描述符引用,该插件将会将所有依赖项打包到生成的 JAR 文件(包括动态链接库)。此外,我们还通过 `attachments` 配置项将动态链接库作为附件添加到 JAR 文件,并使用 `classifier` 指定附件的分类。 将上述配置的 `com.example.MainClass` 替换为您的主类名,并将 `path/to/your.so` 替换为您的实际动态链接库文件路径。 执行 `mvn package` 命令时,Maven 将会生成一个带有所有依赖项和动态链接库的可执行 JAR 文件。您可以将此 JAR 文件分发给其他人,他们可以执行该 JAR 文件并使用其的动态链接库。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值