JAR 내에 네이티브 라이브러리와 JNI 라이브러리를 번들링하는 방법은 무엇입니까?


101

문제의 도서관은 도쿄 내각 이다.

재배포 문제를 피하기 위해 네이티브 라이브러리, JNI 라이브러리 및 모든 Java API 클래스를 하나의 JAR 파일에 포함하고 싶습니다.

GitHub에서 시도한 것 같지만

  1. 실제 네이티브 라이브러리는 포함되지 않고 JNI 라이브러리 만 포함됩니다.
  2. Leiningen 의 기본 종속성 플러그인에 특정한 것 같습니다 (재배포 가능하지 않음).

문제는 모든 것을 하나의 JAR로 묶어서 재배포 할 수 있습니까? 그렇다면 어떻게?

PS : 예, 이식성에 영향을 미칠 수 있다는 것을 알고 있습니다.

답변:


54

하나 이상의 플랫폼에 대한 기본 JNI 라이브러리를 포함하여 모든 종속성이있는 단일 JAR 파일을 생성 할 수 있습니다. 기본 메커니즘은 java.library.path 시스템 속성을 검색하는 일반적인 System.loadLibrary (String) 대신 System.load (File)을 사용하여 라이브러리를로드하는 것입니다. 이 방법을 사용하면 사용자가 시스템에 JNI 라이브러리를 설치할 필요가 없기 때문에 설치가 훨씬 간단 해집니다. 그러나 플랫폼의 특정 라이브러리가 단일 JAR 파일에 포함되지 않을 수 있으므로 모든 플랫폼이 지원되지 않을 수 있습니다. .

과정은 다음과 같습니다.

  • 플랫폼 별 위치 (예 : NATIVE / $ {os.arch} / $ {os.name} /libname.lib)의 JAR 파일에 네이티브 JNI 라이브러리를 포함합니다.
  • 메인 클래스의 정적 초 기자에 코드를 생성하여
    • 현재 os.arch 및 os.name 계산
    • Class.getResource (String)를 사용하여 미리 정의 된 위치의 JAR 파일에서 라이브러리를 찾습니다.
    • 존재하는 경우 임시 파일로 추출하고 System.load (File)을 사용하여로드합니다.

ZeroMQ (Shameless Plug)의 Java 바인딩 인 jzmq에 대한 기능을 추가했습니다. 코드는 여기 에서 찾을 수 있습니다 . jzmq 코드는 하이브리드 솔루션을 사용하므로 임베디드 라이브러리를로드 할 수없는 경우 코드는 java.library.path를 따라 JNI 라이브러리를 검색하는 것으로 되돌아갑니다.


1
나는 그것을 사랑한다! 통합에 대한 많은 수고를 덜어주고 실패한 경우 System.loadLibrary ()를 사용하여 "이전"방식으로 항상 되돌릴 수 있습니다. 나는 그것을 사용하기 시작할 것이라고 생각합니다. 감사! :)
— Matthieu 2013 년

1
내 라이브러리 dll이 다른 dll에 종속 된 경우 어떻게해야합니까? 나는 항상 UnsatisfiedLinkError이전 dll을로드하면 암시 적으로 후자를로드하고 싶지만 항아리에 숨겨져 있으므로 찾을 수 없습니다. 또한 후자의 dll을 먼저로드해도 도움이되지 않습니다.
— fabb

다른 종속 항목 DLL은 미리 알고 있어야하며 해당 위치는 PATH환경 변수에 추가되어야 합니다.
— truthadjustr

잘 작동합니다! 이 문제를 접하는 누군가에게 명확하지 않은 경우 EmbeddedLibraryTools 클래스를 프로젝트에 드롭하고 그에 따라 변경하십시오.
— Jon La Marr

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 파일을 찾을 수 없습니다. 예외가 발생하는 이유를 알려주십시오. 주셔서 감사합니다
— 가네

16

One-JAR 살펴보기 . 특히 "jars 내의 jar"를 처리하는 특수 클래스 로더를 사용하여 단일 jar 파일로 애플리케이션을 래핑합니다.

그것은 원시 (JNI) 라이브러리를 처리 필요에 따라 임시 작업 폴더에 압축을 풉니 다.

(면책 조항 : 저는 One-JAR를 사용한 적이 없으며 아직 필요하지 않았으며 비오는 날을 위해 책갈피에 추가했습니다.)


나는 자바 프로그래머가 아니에요, 어떤 생각이 나는 경우 가 응용 프로그램 자체를 포장? 이것이 기존 클래스 로더와 결합하여 어떻게 작동합니까? 명확히하기 위해 Clojure에서 사용하고 JAR을 애플리케이션이 아닌 라이브러리로로드 할 수 있기를 원합니다.
— Alex B

Ahhh, 아마 적합하지 않을 것입니다. 애플리케이션의 라이브러리 경로에 넣으라는 지침과 함께 jar 파일 외부에 네이티브 라이브러리를 배포하는 데 어려움을 겪을 것이라고 생각하십시오.
— Evan

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와 같은 애플리케이션 서버에 무언가를 배포하려는 경우에도 작동하며 가장 좋은 점은 애플리케이션을 올바르게 언로드 할 수 있다는 것입니다!
— 해시

이것은 '네이티브 라이브러리가 이미'로드 된 예외를 방지하는 가장 좋은 솔루션입니다.
— Krithika Vittal

5

JarClassLoader 는 단일 몬스터 JAR ​​및 몬스터 JAR ​​내부의 JAR에서 클래스, 네이티브 라이브러리 및 리소스를로드하는 클래스 로더입니다.


1

로컬 파일 시스템에 네이티브 라이브러리를 unjar해야 할 것입니다. 내가 아는 한 네이티브 로딩을 수행하는 코드는 파일 시스템을 봅니다.

이 코드는 시작하는 데 도움이 될 것입니다 (한동안 보지 않았고 다른 목적을위한 것이지만 트릭을 수행해야합니다. 현재 매우 바쁘지만 질문이 있으면 댓글을 남겨주세요. 가능한 한 빨리 답변하겠습니다.)

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
당사 사이트를 사용함과 동시에 당사의 쿠키 정책과 개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.