如何在JAR内捆绑本机库和JNI库?


101

有问题的图书馆是东京内阁

我想要在一个JAR文件中具有本机库,JNI库和所有Java API类,以避免重新分配的麻烦。

似乎在GitHub上有尝试,但是

  1. 它不包括实际的本机库,仅包括JNI库。
  2. 它似乎特定于Leiningen的本机依赖插件(它不能作为可再发行组件使用)。

问题是,我可以将所有内容捆绑在一个JAR中并重新分发吗?如果是,怎么办?

PS:是的,我意识到这可能会带来可移植性。

Answers:


54

可以创建具有所有依赖性的单个JAR文件,包括一个或多个平台的本机JNI库。基本机制是使用System.load(File)加载库,而不是使用典型的System.loadLibrary(String)来搜索java.library.path系统属性。由于用户不必在系统上安装JNI库,因此此方法使安装更加简单,但是这样做可能会不支持所有平台,因为平台的特定库可能未包含在单个JAR文件中。

流程如下:

  • 在平台特定位置的JAR文件中包含本机JNI库,例如在NATIVE / $ {os.arch} / $ {os.name} /libname.lib
  • 在主类的静态初始化中创建代码以
    • 计算当前的os.arch和os.name
    • 使用Class.getResource(String)在预定义位置的JAR文件中查找库
    • 如果存在,请将其提取到临时文件中,并使用System.load(File)加载。

我添加了为jzmq(ZeroMQ(无耻插件)的Java绑定)执行此操作的功能。该代码可以在这里找到。jzmq代码使用混合解决方案,因此,如果无法加载嵌入式库,则该代码将还原为沿java.library.path搜索JNI库。


1
我喜欢它!它为集成省去了很多麻烦,并且万一失败,您可以随时使用System.loadLibrary()恢复到“旧”方式。我想我将开始使用它。谢谢!:)
Matthieu 2013年

1
如果我的库dll依赖于另一个dll,该怎么办?我总是得到一个,UnsatisfiedLinkError因为加载前一个dll隐式要加载后者,但是找不到它,因为它隐藏在jar中。同样先加载后一个dll也无济于事。
fabb 2013年

DLL必须事先知道其他依赖项,并且应将其位置添加到PATH环境变量中。
truthadjustr

很棒!如果不清楚有人遇到过这种情况,请将EmbeddedLibraryTools类放入您的项目中并进行相应的更改。
乔恩·拉马尔

41

https://www.adamheinrich.com/blog/2012/12/how-to-load-native-jni-library-from-jar/

很棒的文章,解决了我的问题..

就我而言,我有以下代码用于初始化库:

static {
    try {
        System.loadLibrary("crypt"); // used for tests. This library in classpath only
    } catch (UnsatisfiedLinkError e) {
        try {
            NativeUtils.loadLibraryFromJar("/natives/crypt.dll"); // during runtime. .DLL within .JAR
        } catch (IOException e1) {
            throw new RuntimeException(e1);
        }
    }
}

26
使用NativeUtils.loadLibraryFromJar(“ / natives /” + System.mapLibraryName(“ crypt”))); 可能会更好
BlackJoker

1
嗨,当我尝试使用NativeUtils类并尝试在libs / armeabi / libmyname.so中放置.so文件时,我收到了类似java.lang.ExceptionInInitializerError之类的异常,原因是:java.io.FileNotFoundException:在JAR中找不到文件/native/libhellojni.so。请让我知道为什么我会收到例外。谢谢
Ganesh

16

看看One-JAR。它将使用专门的类加载器将您的应用程序包装在单个jar文件中,该类加载器可处理“罐中的jar”等问题。

它通过按需将本机库解压缩到一个临时工作文件夹中来处理本机(JNI)库

(免责声明:我从未使用过One-JAR,现在还不需要,只是将它标记为下雨天。)


我不是Java程序员,是否需要包装应用程序本身知道吗?如何与现有的类加载器结合使用?为了澄清,我将在Clojure中使用它,并希望能够将JAR作为库而不是作为应用程序加载。
亚历克斯·B

啊,可能不适合。认为您将无法在jar文件之外分发本机库,并获得将其放在应用程序库路径中的说明。
伊万(Evan)2010年

8

1)将本机库作为资源包含在您的JAR中。例如 使用Maven或Gradle以及标准项目布局,将本机库放入main/resources目录中。

2)在与此库相关的Java类的静态初始化程序中的某个地方,将代码如下所示:

String libName = "myNativeLib.so"; // The name of the file in resources/ dir
URL url = MyClass.class.getResource("/" + libName);
File tmpDir = Files.createTempDirectory("my-native-lib").toFile();
tmpDir.deleteOnExit();
File nativeLibTmpFile = new File(tmpDir, libName);
nativeLibTmpFile.deleteOnExit();
try (InputStream in = url.openStream()) {
    Files.copy(in, nativeLibTmpFile.toPath());
}
System.load(nativeLibTmpFile.getAbsolutePath());

如果您想要将某些内容部署到应用服务器(例如wildfly),并且最好的部分是它能够正确卸载应用程序,则该解决方案也可以使用!
哈希

这是避免“本地库已加载”异常的最佳解决方案。
克里斯蒂卡·维塔尔

5

JarClassLoader是一个类加载器,用于从单个怪物JAR和怪物JAR内部的JAR加载类,本机库和资源。


1

您可能必须将本机库解压缩到本地文件系统。据我所知,执行本机加载的部分代码是在文件系统上进行的。

这段代码应该可以帮助您入门(我已经有一段时间没看过了,它是出于不同的目的,但是应该可以解决问题,目前我很忙,但是如果您有任何疑问,请留下评论)我会尽快回答)。

import java.io.Closeable;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLDecoder;
import java.security.CodeSource;
import java.security.ProtectionDomain;
import java.util.zip.ZipEntry;
import java.util.zip.ZipException;
import java.util.zip.ZipFile;


public class FileUtils
{
    public static String getFileName(final Class<?>  owner,
                                     final String    name)
        throws URISyntaxException,
               ZipException,
               IOException
    {
        String    fileName;
        final URI uri;

        try
        {
            final String external;
            final String decoded;
            final int    pos;

            uri      = getResourceAsURI(owner.getPackage().getName().replaceAll("\\.", "/") + "/" + name, owner);
            external = uri.toURL().toExternalForm();
            decoded  = external; // URLDecoder.decode(external, "UTF-8");
            pos      = decoded.indexOf(":/");
            fileName = decoded.substring(pos + 1);
        }
        catch(final FileNotFoundException ex)
        {
            fileName = null;
        }

        if(fileName == null || !(new File(fileName).exists()))
        {
            fileName = getFileNameX(owner, name);
        }

        return (fileName);
    }

    private static String getFileNameX(final Class<?> clazz, final String name)
        throws UnsupportedEncodingException
    {
        final URL    url;
        final String fileName;

        url = clazz.getResource(name);

        if(url == null)
        {
            fileName = name;
        }
        else
        {
            final String decoded;
            final int    pos;

            decoded  = URLDecoder.decode(url.toExternalForm(), "UTF-8");
            pos      = decoded.indexOf(":/");
            fileName = decoded.substring(pos + 1);
        }

        return (fileName);
    }

    private static URI getResourceAsURI(final String    resourceName,
                                       final Class<?> clazz)
        throws URISyntaxException,
               ZipException,
               IOException
    {
        final URI uri;
        final URI resourceURI;

        uri         = getJarURI(clazz);
        resourceURI = getFile(uri, resourceName);

        return (resourceURI);
    }

    private static URI getJarURI(final Class<?> clazz)
        throws URISyntaxException
    {
        final ProtectionDomain domain;
        final CodeSource       source;
        final URL              url;
        final URI              uri;

        domain = clazz.getProtectionDomain();
        source = domain.getCodeSource();
        url    = source.getLocation();
        uri    = url.toURI();

        return (uri);
    }

    private static URI getFile(final URI    where,
                               final String fileName)
        throws ZipException,
               IOException
    {
        final File location;
        final URI  fileURI;

        location = new File(where);

        // not in a JAR, just return the path on disk
        if(location.isDirectory())
        {
            fileURI = URI.create(where.toString() + fileName);
        }
        else
        {
            final ZipFile zipFile;

            zipFile = new ZipFile(location);

            try
            {
                fileURI = extract(zipFile, fileName);
            }
            finally
            {
                zipFile.close();
            }
        }

        return (fileURI);
    }

    private static URI extract(final ZipFile zipFile,
                               final String  fileName)
        throws IOException
    {
        final File         tempFile;
        final ZipEntry     entry;
        final InputStream  zipStream;
        OutputStream       fileStream;

        tempFile = File.createTempFile(fileName.replace("/", ""), Long.toString(System.currentTimeMillis()));
        tempFile.deleteOnExit();
        entry    = zipFile.getEntry(fileName);

        if(entry == null)
        {
            throw new FileNotFoundException("cannot find file: " + fileName + " in archive: " + zipFile.getName());
        }

        zipStream  = zipFile.getInputStream(entry);
        fileStream = null;

        try
        {
            final byte[] buf;
            int          i;

            fileStream = new FileOutputStream(tempFile);
            buf        = new byte[1024];
            i          = 0;

            while((i = zipStream.read(buf)) != -1)
            {
                fileStream.write(buf, 0, i);
            }
        }
        finally
        {
            close(zipStream);
            close(fileStream);
        }

        return (tempFile.toURI());
    }

    private static void close(final Closeable stream)
    {
        if(stream != null)
        {
            try
            {
                stream.close();
            }
            catch(final IOException ex)
            {
                ex.printStackTrace();
            }
        }
    }
}

1
如果您需要解压某些特定资源,建议您查看github.com/zeroturnaround/zt-zip项目
Neeme Praks

zt-zip看起来像一个不错的API。
TofuBeer 2012年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.