Yerel bir kitaplık ve bir JNI kitaplığı bir JAR içinde nasıl paketlenir?


104

Söz konusu kütüphane Tokyo Kabinesi .

Yeniden dağıtım sorunlarından kaçınmak için yerel kitaplığı, JNI kitaplığını ve tüm Java API sınıflarını tek bir JAR dosyasında bulundurmak istiyorum.

GitHub'da buna bir girişim var gibi görünüyor , ancak

  1. Gerçek yerel kitaplığı içermez, yalnızca JNI kitaplığını içerir.
  2. Leiningen'in yerel bağımlılık eklentisine özgü görünüyor (yeniden dağıtılabilir olarak çalışmayacak).

Soru şu ki, her şeyi tek bir JAR'da toplayıp yeniden dağıtabilir miyim? Varsa nasıl?

Not: Evet, taşınabilirlik etkileri olabileceğinin farkındayım.

Yanıtlar:


54

Bir veya daha fazla platform için yerel JNI kitaplıkları dahil tüm bağımlılıkları içeren tek bir JAR dosyası oluşturmak mümkündür. Temel mekanizma, java.library.path system özelliğini arayan tipik System.loadLibrary (String) yerine kitaplığı yüklemek için System.load (Dosya) kullanmaktır. Bu yöntem, kullanıcının kendi sistemine JNI kitaplığını yüklemesi gerekmediğinden kurulumu çok daha basit hale getirir, bununla birlikte, bir platform için belirli kitaplık tek bir JAR dosyasına dahil edilmeyebileceğinden tüm platformlar desteklenmeyebilir. .

İşlem şu şekildedir:

  • örneğin NATIVE / $ {os.arch} / $ {os.name} /libname.lib gibi platforma özgü bir konumdaki JAR dosyasındaki yerel JNI kitaplıklarını dahil edin
  • ana sınıfın statik başlatıcısında kod oluşturmak için
    • geçerli os.arch ve os.name hesaplarını
    • Class.getResource (String) kullanarak önceden tanımlanmış konumda JAR dosyasındaki kitaplığı arayın
    • varsa, geçici bir dosyaya çıkartın ve System.load (Dosya) ile yükleyin.

ZeroMQ'nun (utanmaz eklenti) Java bağlamaları olan jzmq için bunu yapmak için işlevsellik ekledim. Kod burada bulunabilir . Jzmq kodu karma bir çözüm kullanır, böylece gömülü bir kitaplık yüklenemezse, kod java.library.path boyunca JNI kitaplığını aramaya döner.


1
Onu seviyorum! Entegrasyon için birçok sorundan tasarruf sağlar ve başarısız olması durumunda System.loadLibrary () ile her zaman "eski" yola geri dönebilirsiniz. Sanırım bunu kullanmaya başlayacağım. Teşekkürler! :)
Matthieu

1
Kütüphanemde başka bir dll'ye bağımlılık varsa ne yapmalıyım? Her zaman UnsatisfiedLinkErroreski dll'yi yüklerken örtük olarak ikincisini yüklemek istiyor, ancak kavanozda gizli olduğu için bulamıyorum. Ayrıca ilk dll'yi yüklemek yardımcı olmuyor.
fabb

Diğer bağımlılar DLLönceden bilinmeli ve konumları PATHortam değişkenine eklenmelidir .
eigenfield

Harika çalışıyor! Bununla karşılaşan biri için net değilse, EmbeddedLibraryTools sınıfını projenize bırakın ve buna göre değiştirin.
Jon La Marr

41

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

sorunumu çözen harika bir makale ..

Benim durumumda, kitaplığı başlatmak için aşağıdaki kodu aldım:

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")) kullanın; daha iyi olabilirdi
BlackJoker

1
Merhaba, NativeUtils sınıfını kullanmaya ve .so dosyasını libs / armeabi / libmyname.so içine yerleştirmeye çalıştığımda, java.lang.ExceptionInInInitializerError gibi istisnayı alıyorum, Neden: java.io.FileNotFoundException: /Native/libhellojni.so dosyası JAR içinde bulunamadı. Lütfen neden istisna aldığımı bana bildirin. Teşekkür ederim
Ganesh

16

One-JAR'a bir göz atın . Uygulamanızı, diğer şeylerin yanı sıra "kavanozların içindeki kavanozları" işleyen özel bir sınıf yükleyiciye sahip tek bir jar dosyasına saracaktır.

Bu yerli (JNI) kütüphaneleri kolları gerektiği gibi geçici bir çalışma klasöre açma yoluyla.

(Feragatname: One-JAR'ı hiç kullanmadım, henüz buna ihtiyaç duymadım, sadece yağmurlu bir gün için yer imlerine ekledim.)


Java programcısı değilim , uygulamanın kendisini paketlemem gerekip gerekmediğine dair bir fikriniz var mı? Bu, mevcut bir sınıf yükleyiciyle birlikte nasıl çalışır? Açıklığa kavuşturmak için, onu Clojure'dan kullanacağım ve bir JAR'ı bir uygulama yerine kitaplık olarak yükleyebilmek istiyorum.
Alex B

Ahhh, muhtemelen daha uygun olmaz. Yerel kitaplıklarınızı jar dosyasının dışına, bunları uygulamanın kitaplık yoluna koyma talimatlarıyla birlikte dağıtmak zorunda kalacağınızı düşünün.
Evan

9

1) Yerel kitaplığı bir Kaynak olarak JAR'ınıza ekleyin. Örneğin. Maven veya Gradle ve standart proje düzeni ile yerel kitaplığımain/resources dizine yerleştirin.

2) Java sınıflarının statik başlatıcılarında, bu kitaplıkla ilgili olarak, kodu aşağıdaki gibi yerleştirin:

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());

Bu çözüm, wildfly gibi bir uygulama sunucusuna bir şey dağıtmak istediğinizde de işe yarar VE en iyi yanı, uygulamayı düzgün bir şekilde kaldırabilmesidir!
Karma

Bu, 'yerel kitaplık zaten' yüklenmiş istisnayı önlemek için en iyi çözümdür.
Krithika Vittal


1

Muhtemelen yerel kitaplığı yerel dosya sistemine açmanız gerekecektir. Bildiğim kadarıyla yerel yüklemeyi yapan kod parçası dosya sistemine bakıyor.

Bu kod başlamanıza yardımcı olmalı (Bir süredir bakmadım ve farklı bir amaç için ama hile yapmalıyım ve şu anda oldukça meşgulüm, ancak sorularınız varsa bir yorum bırakın ve elimden geldiğince çabuk cevaplayacağım).

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
Eğer bazı özel kaynak unjar gerekirse, ben bir göz tavsiye github.com/zeroturnaround/zt-zip projesi
Neeme Praks

zt-zip, iyi bir API'ye benziyor.
TofuBeer

0

Kotlin için Çözüm:

  • build.gradle.dsl: kotlin çalışma zamanını (kotlin-stdlib-1.4.0.jar) ve yerel kitaplığı (librust_kotlin.dylib) JAR'a kopyala

    tasks.withType<Jar> {
        manifest {
            attributes("Main-Class" to "MainKt")
        }
    
        val libs = setOf("kotlin-stdlib-1.4.0.jar")
    
        from(configurations.runtimeClasspath.get()
            .filter { it.name in libs }
            .map { zipTree(it) })
    
        from("librust_kotlin.dylib")
    }
  • main yöntem: kitaplığı, mutlak yolu kullanarak yüklemek için geçici bir dosyaya kopyalayın

     with(createTempFile()) {
         deleteOnExit()
         val bytes = My::class.java.getResource("librust_kotlin.dylib")
             .readBytes()
    
         outputStream().write(bytes)
         System.load(path)
     }
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.