Come raggruppare una libreria nativa e una libreria JNI all'interno di un JAR?


101

La biblioteca in questione è Tokyo Cabinet .

Voglio avere la libreria nativa, la libreria JNI e tutte le classi API Java in un file JAR per evitare problemi di ridistribuzione.

Sembra che ci sia un tentativo in questo caso su GitHub , ma

  1. Non include la libreria nativa effettiva, solo la libreria JNI.
  2. Sembra essere specifico per il plug-in delle dipendenze native di Leiningen (non funzionerà come ridistribuibile).

La domanda è: posso raggruppare tutto in un JAR e ridistribuirlo? Se sì, come?

PS: Sì, mi rendo conto che potrebbe avere implicazioni sulla portabilità.

Risposte:


54

È possibile creare un singolo file JAR con tutte le dipendenze comprese le librerie JNI native per una o più piattaforme. Il meccanismo di base è utilizzare System.load (File) per caricare la libreria invece del tipico System.loadLibrary (String) che ricerca la proprietà di sistema java.library.path. Questo metodo rende l'installazione molto più semplice in quanto l'utente non deve installare la libreria JNI sul proprio sistema, a scapito, tuttavia, che tutte le piattaforme potrebbero non essere supportate in quanto la libreria specifica per una piattaforma potrebbe non essere inclusa nel singolo file JAR .

Il processo è il seguente:

  • includere le librerie JNI native nel file JAR in una posizione specifica per la piattaforma, ad esempio in NATIVE / $ {os.arch} / $ {os.name} /libname.lib
  • creare codice in un inizializzatore statico della classe principale per
    • calcola os.arch e os.name correnti
    • cerca la libreria nel file JAR nella posizione predefinita utilizzando Class.getResource (String)
    • se esiste, estrarlo in un file temporaneo e caricarlo con System.load (File).

Ho aggiunto funzionalità per farlo per jzmq, i collegamenti Java di ZeroMQ (plug senza vergogna). Il codice può essere trovato qui . Il codice jzmq utilizza una soluzione ibrida in modo che se una libreria incorporata non può essere caricata, il codice tornerà alla ricerca della libreria JNI lungo java.library.path.


1
Lo adoro! Risparmia molti problemi per l'integrazione e puoi sempre tornare al "vecchio" modo con System.loadLibrary () nel caso in cui fallisse. Penso che inizierò a usarlo. Grazie! :)
Matthieu

1
Cosa devo fare se la mia libreria dll ha una dipendenza da un'altra dll? Ottengo sempre un messaggio UnsatisfiedLinkErrormentre caricando la prima dll vuole implicitamente caricare la seconda, ma non riesco a trovarla perché è nascosta nel vaso. Anche il caricamento di quest'ultima dll prima non aiuta.
fabb

Gli altri dipendenti DLLdevono essere conosciuti in anticipo e le loro posizioni devono essere aggiunte nella PATHvariabile di ambiente.
truthadjustr

Funziona alla grande! Se non è chiaro a qualcuno che si imbatte in questo, rilascia la classe EmbeddedLibraryTools nel tuo progetto e modificala di conseguenza.
Jon La Marr

41

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

è un ottimo articolo, che risolve il mio problema ..

Nel mio caso ho il seguente codice per inizializzare la libreria:

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
use NativeUtils.loadLibraryFromJar ("/ natives /" + System.mapLibraryName ("crypt")); potrebbe essere migliore
BlackJoker

1
Salve, quando provo a utilizzare la classe NativeUtils e provo a inserire il file .so all'interno di libs / armeabi / libmyname.so, ricevo l'eccezione come java.lang.ExceptionInInitializerError, causata da: java.io.FileNotFoundException: Il file /native/libhellojni.so non è stato trovato all'interno di JAR. Per favore fatemi sapere perché ricevo l'eccezione. Grazie
Ganesh

16

Dai un'occhiata a One-JAR . Avvolgerà la tua applicazione in un singolo file jar con un caricatore di classi specializzato che gestisce "barattoli dentro barattoli" tra le altre cose.

Esso gestisce le librerie native (JNI) per estrarli in una cartella di lavoro temporanea, come richiesto.

(Dichiarazione di non responsabilità: non ho mai usato One-JAR, non ne avevo ancora bisogno, l'ho appena aggiunto ai segnalibri per una giornata di pioggia.)


Io non sono un programmatore Java, qualsiasi idea se mi hanno per avvolgere l'applicazione stessa? Come funzionerebbe in combinazione con un classloader esistente? Per chiarire, lo userò da Clojure e voglio essere in grado di caricare un JAR come libreria, piuttosto che come applicazione.
Alex B

Ahhh, probabilmente non sarebbe adatto a. Pensa che sarai bloccato con la distribuzione delle tue librerie native al di fuori del file jar, con le istruzioni per inserirle nel percorso della libreria dell'applicazione.
Evan

8

1) Includi la libreria nativa nel tuo JAR come risorsa. Per esempio. con Maven o Gradle e il layout di progetto standard, inserisci la libreria nativa inmain/resources directory.

2) Da qualche parte negli inizializzatori statici delle classi Java, relative a questa libreria, inserisci il codice come segue:

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

Questa soluzione funziona anche nel caso in cui desideri distribuire qualcosa su un server delle applicazioni come wildfly E la parte migliore è che è in grado di scaricare correttamente l'applicazione!
Hash

Questa è la soluzione migliore per evitare l'eccezione "libreria nativa già" caricata.
Krithika Vittal,

5

JarClassLoader è un caricatore di classi per caricare classi, librerie native e risorse da un singolo JAR mostro e da JAR all'interno del JAR mostro.


1

Probabilmente dovrai aprire la libreria nativa nel file system locale. Per quanto ne so, la parte di codice che esegue il caricamento nativo guarda al file system.

Questo codice dovrebbe aiutarti a iniziare (non lo guardo da un po 'ed è per uno scopo diverso ma dovrebbe fare il trucco, e al momento sono piuttosto impegnato, ma se hai domande lascia un commento e ti risponderò appena posso).

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
Se hai bisogno di aprire una risorsa specifica, ti consiglio di dare un'occhiata a github.com/zeroturnaround/zt-zip project
Neeme Praks

zt-zip sembra un'API decente.
TofuBeer
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.