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 ""; } } }