中文 English

Java Spring Boot JNI Native Library Loader

Published: 2021-03-27
Java springboot spring JNI SO DLL JNILIB DYLIB MacOS Linux Windows

Because Java needs to stay cross-platform, I wrote a cross-platform JNI native library loader.

Simple Implementation

  1. Determine whether the current system is Windows, Linux, or MacOS from the os.name environment property.
  2. If it is Linux, further determine whether it is CentOS or Debian.
  3. Read the library files inside the jar package.
  4. Filter the matching platform library files by file suffix: dll, so, jnilib, and dylib.
  5. Copy the current platform’s library file to the system temporary directory java.io.tmpdir.
  6. Load the library using System.load.

Full Code

import cn.hutool.core.io.IoUtil;
import cn.hutool.core.util.StrUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;

import java.io.*;

public class NativeLibLoader {
    private static final Logger logger = LoggerFactory.getLogger(NativeLibLoader.class);

    public static void load(String[] snks) {
        String currentOS = System.getProperty("os.name");
        if (currentOS.contains("Windows")) {
            linuxPrefix(new String[]{"dir"});
            for (String snk : snks) {
                if (StrUtil.endWithIgnoreCase(snk, "dll")) {
                    loadFile(snk, snk);
                }
            }
        } else if (currentOS.contains("Linux")) {
            String linuxPrefix = linuxPrefix(new String[]{"/bin/bash", "-c", "cat /etc/*-release"});
            for (String snk : snks) {
                if (StrUtil.endWithIgnoreCase(snk, "so")) {
                    loadFile(linuxPrefix + "-" + snk, snk);
                }
            }
        } else if (currentOS.contains("Mac OS X")) {
            linuxPrefix(new String[]{"ls"});
            for (String snk : snks) {
                if (StrUtil.endWithIgnoreCase(snk, "jnilib") || StrUtil.endWithIgnoreCase(snk, "dylib")) {
                    loadFile(snk, snk);
                }
            }
        } else {
            logger.info("NativeLibLoader : not supported " + currentOS);
        }
    }

    private static String linuxPrefix(String[] args) {
        StringBuilder sbRead = new StringBuilder();
        StringBuilder sbErr = new StringBuilder();
        try {
            // Start another process to execute the command
            Process pro = Runtime.getRuntime().exec(args);
            pro.waitFor();
            try (BufferedReader read = new BufferedReader(new InputStreamReader(pro.getInputStream()));
                 BufferedReader err = new BufferedReader(new InputStreamReader(pro.getErrorStream()))) {
                String line;
                while ((line = read.readLine()) != null) {
                    logger.info(line);
                    sbRead.append(line);
                }

                while ((line = err.readLine()) != null) {
                    logger.error(line);
                    sbErr.append(line);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

        String releaseInfo = sbRead.toString();
        if (releaseInfo.contains("Debian")) {
            return "debian";
        }
        return "centos";
    }

    public static void loadFile(String src, String snk) {
        String srcAndPath = "classpath:" + src;
        try {
            logger.info("NativeLibLoader : copy " + srcAndPath + " to " + snk);
            File file = copyResourceToTempDirFile(srcAndPath, snk);
            String filePath = file.getAbsolutePath();
            System.load(filePath);
            logger.info("NativeLibLoader : load " + filePath + " successful");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private static File copyResourceToTempDirFile(String src, String snk) throws IOException {
        File tempDir = new File(System.getProperty("java.io.tmpdir"));
        File tempDirFile = new File(tempDir, snk);

        PathMatchingResourcePatternResolver patternResolver = new PathMatchingResourcePatternResolver();
        Resource[] resources = patternResolver.getResources(src);

        if (resources.length == 0) {
            return null;
        }

        try (InputStream input = resources[0].getInputStream();
             OutputStream output = new FileOutputStream(tempDirFile)) {
            IoUtil.copy(input, output);
            tempDirFile.deleteOnExit();
            return tempDirFile;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}

How to Load

NativeLibLoader.load(new String[]{"jniortools.dll", "libortools.so", "libjniortools.so", "libortools.dylib", "libjniortools.jnilib"});