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.