从 JAR 包中加载本地库文件
在跨平台 Java 项目中,往往需要使用 JNI 调用本地(C/C++)代码。本方案介绍如何将 native media 编译后的各平台动态库(如 Windows 的 DLL、Linux 的 .so、macOS 的 .dylib)打包到 JAR 内部,并在运行时通过工具类将这些库文件提取到用户目录下加载。这样既便于统一管理依赖,又能做到跨平台支持。
1. 项目目录结构
在项目中,将本地库文件放置于 src/main/resources/lib
目录下,并按照平台进行归类,示例如下:
├── lib
│ ├── darwin_arm64
│ │ ├── libnative_media.dylib
│ ├── linux_amd64
│ │ ├── libnative_media.so
│ ├── win_amd64
│ │ ├── avcodec-61.dll
│ │ ├── avformat-61.dll
│ │ ├── avutil-59.dll
│ │ ├── libmp3lame.DLL
│ │ ├── libnative_media.dll
│ │ ├── swresample-5.dll
这样打包后,上述文件会被嵌入到 JAR 内部,在运行时根据当前平台自动提取对应的库文件进行加载。
2. 工具类实现
下面的工具类 LibraryUtils
完成以下功能:
- 判断当前系统平台(Windows、macOS 或 Linux),确定库文件名称及所在的子目录。
- 检查目标目录是否存在,并创建用于存储提取出的库文件。
- 从 JAR 内部资源中将对应平台的库文件复制到用户目录下的指定位置。
- Windows 平台下需要额外加载依赖的 DLL 文件,且加载顺序要求严格。
- 最后通过
System.load
加载主库。
以下是经过润色后的完整代码示例:
package com.litongjava.media.utils;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.StandardCopyOption;
import com.litongjava.media.core.Core;
public class LibraryUtils {
public static final String WIN_AMD64 = "win_amd64";
public static final String DARWIN_ARM64 = "darwin_arm64";
public static final String LINUX_AMD64 = "linux_amd64";
public static void load() {
// Determine the current operating system and platform to identify the library file name and resource directory
String osName = System.getProperty("os.name").toLowerCase();
String userHome = System.getProperty("user.home").toLowerCase();
System.out.println("os name:" + osName + " user.home:" + userHome);
String archName;
String libFileName;
if (osName.contains("win")) {
libFileName = Core.WIN_NATIVE_LIBRARY_NAME;
archName = WIN_AMD64;
} else if (osName.contains("mac")) {
libFileName = Core.MACOS_NATIVE_LIBRARY_NAME;
archName = DARWIN_ARM64;
} else if (osName.contains("nix") || osName.contains("nux") || osName.contains("aix") || osName.contains("linux")) {
libFileName = Core.UNIX_NATIVE_LIBRARY_NAME;
archName = LINUX_AMD64;
} else {
throw new UnsupportedOperationException("Unsupported OS: " + osName);
}
// Create the directory for storing the extracted library file, e.g.: lib/win_amd64/
String dstDir = userHome + File.separator + "lib" + File.separator + archName;
File libFile = new File(dstDir, libFileName);
File parentDir = libFile.getParentFile();
if (!parentDir.exists()) {
parentDir.mkdirs();
}
// Now extract the resource
extractResource("/lib/" + archName + "/" + libFileName, libFile);
// If the OS is Windows, additional dependent DLL files need to be loaded
if (WIN_AMD64.equals(archName)) {
String[] dlls = { "avutil-59.dll", "swresample-5.dll", "libmp3lame.DLL", "avcodec-61.dll", "avformat-61.dll" };
for (String dll : dlls) {
File dllFile = new File(dstDir, dll);
extractResource("/lib/" + archName + "/" + dll, dllFile);
System.load(dllFile.getAbsolutePath());
}
}
// Load the main library file
String absolutePath = libFile.getAbsolutePath();
System.out.println("load " + absolutePath);
System.load(absolutePath);
}
/**
* Copies a resource file from the jar to the specified destination.
*
* @param resourcePath The resource path inside the jar, e.g.: /lib/win_amd64/xxx.dll
* @param destination The destination file
*/
private static void extractResource(String resourcePath, File destination) {
try (InputStream in = LibraryUtils.class.getResourceAsStream(resourcePath)) {
if (in == null) {
throw new RuntimeException("Resource does not exist: " + resourcePath);
}
Files.copy(in, destination.toPath(), StandardCopyOption.REPLACE_EXISTING);
} catch (IOException e) {
throw new RuntimeException("Failed to extract resource: " + resourcePath + " to " + destination.getAbsolutePath(), e);
}
}
}
3. 加载流程说明
确定平台
根据os.name
判断当前操作系统,并选择相应的平台目录(如win_amd64
、darwin_arm64
、linux_amd64
)及库文件名称。对应的库文件名称存放在Core
接口中,例如:- Windows:
libnative_media.dll
- macOS:
libnative_media.dylib
- Linux:
libnative_media.so
- Windows:
创建目录与提取资源
工具类首先创建一个lib
目录,并在其中生成平台对应的子目录。若目标库文件不存在,则通过getResourceAsStream
从 JAR 内部提取该文件,并写入到本地目录中。加载依赖(Windows 特有)
Windows 平台下,由于动态库依赖关系较为复杂,除了主库外,还需要提前加载其他依赖的 DLL 文件,且加载顺序严格(如先加载avutil-59.dll
等)。调用 System.load
提取并加载所有依赖文件后,最后通过System.load
加载主库文件,从而使 JNI 方法能够被正确调用。
4. 部署与使用
打包
将src/main/resources/lib
目录下的所有库文件打包进 JAR 后,运行时工具类会自动将这些库复制到用户目录下指定位置。加载调用
在 Java 项目的入口类或 JNI 相关类中调用LibraryUtils.load()
方法,即可完成本地库的自动提取和加载。建议在所有 native 方法调用前加载本地库。
打包
mvn clean install -DskipTests
打包后的文件 java-native-media-1.0.0.jar 大小是 7.21MB
总结
通过上述方案,您可以将各平台的本地库文件内嵌到 JAR 包中,并在程序启动时自动提取到本地目录,确保所有平台都能正确加载 JNI 依赖。此方法不仅简化了部署流程,也便于跨平台项目的统一管理。如果有进一步的问题或需要扩展其他平台的支持,可在此基础上做相应修改。