Es enthält nicht die eigentliche native Bibliothek, sondern nur die JNI-Bibliothek.
Es scheint spezifisch zu sein Leiningen‘s natives Abhängigkeits-Plugin (es funktioniert nicht als Redistributable).
Die Frage ist, kann ich alles in einem JAR bündeln und weiterverteilen? Wenn ja, wie?
PS: Ja, mir ist klar, dass dies Auswirkungen auf die Portabilität haben kann.
kwo
Es ist möglich, eine einzelne JAR-Datei mit allen Abhängigkeiten einschließlich der nativen JNI-Bibliotheken für eine oder mehrere Plattformen zu erstellen. Der grundlegende Mechanismus besteht darin, System.load(File) zum Laden der Bibliothek anstelle des typischen System.loadLibrary(String) zu verwenden, das die Systemeigenschaft java.library.path durchsucht. Diese Methode macht die Installation viel einfacher, da der Benutzer die JNI-Bibliothek nicht auf seinem System installieren muss, jedoch auf Kosten dessen, dass möglicherweise nicht alle Plattformen unterstützt werden, da die spezifische Bibliothek für eine Plattform möglicherweise nicht in der einzelnen JAR-Datei enthalten ist .
Der Ablauf ist wie folgt:
Fügen Sie die nativen JNI-Bibliotheken in die JAR-Datei an einem plattformspezifischen Speicherort ein, beispielsweise unter NATIVE/${os.arch}/${os.name}/libname.lib
Code in einem statischen Initialisierer der Hauptklasse zu erstellen
berechnet die aktuelle os.arch und os.name
Suchen Sie die Bibliothek in der JAR-Datei am vordefinierten Speicherort mit Class.getResource(String)
Wenn es existiert, extrahieren Sie es in eine temporäre Datei und laden Sie es mit System.load(File).
Ich habe dazu Funktionen für jzmq hinzugefügt, die Java-Bindungen von ZeroMQ (schamloses Plug-in). Der Code kann gefunden werden Hier. Der jzmq-Code verwendet eine Hybridlösung, sodass der Code, wenn eine eingebettete Bibliothek nicht geladen werden kann, auf die Suche nach der JNI-Bibliothek entlang java.library.path zurückgreift.
Ich liebe es! Es erspart viel Mühe bei der Integration, und Sie können jederzeit mit System.loadLibrary() zum “alten” Weg zurückkehren, falls es fehlschlägt. Ich denke, ich werde damit anfangen. Danke! 🙂
– Matthias
8. Juni 13 um 19:55 Uhr
Was mache ich, wenn meine Bibliotheks-DLL eine Abhängigkeit zu einer anderen DLL hat? Ich bekomme immer eine UnsatisfiedLinkError da das Laden der ersteren DLL implizit die letztere laden möchte, sie aber nicht finden kann, da sie im JAR versteckt ist. Auch das vorherige Laden der letzteren DLL hilft nicht.
– fabelhaft
30. Juni 13 um 9:29 Uhr
Die anderen Angehörigen DLL müssen im Voraus bekannt sein und ihre Standorte sollten im hinzugefügt werden PATH Umgebungsvariable.
– Eigenfeld
29. Juni 2020 um 10:16 Uhr
Funktioniert super! Wenn es jemandem nicht klar ist, der darauf stößt, ziehen Sie die EmbeddedLibraryTools-Klasse in Ihr Projekt und ändern Sie sie entsprechend.
– Jon LaMarr
1. August 2020 um 22:41 Uhr
@JonLaMarr Gehen Sie diesen Weg nur, wenn Ihr Code GPL-lizenziert ist, wie es jzmq ist.
ist ein großartiger Artikel, der mein Problem löst.
In meinem Fall habe ich den folgenden Code zum Initialisieren der Bibliothek:
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);
}
}
}
Verwenden Sie NativeUtils.loadLibraryFromJar(“/natives/”+System.mapLibraryName(“crypt”)); könnte besser sein
– BlackJoker
19. November 13 um 7:53 Uhr
Hallo, wenn ich versuche, die NativeUtils-Klasse zu verwenden und versuche, die .so-Datei in libs/armeabi/libmyname.so zu laden, erhalte ich die Ausnahme wie java.lang.ExceptionInInitializerError, verursacht durch: java.io.FileNotFoundException: Datei /native/libhellojni.so wurde nicht in JAR gefunden. Bitte lassen Sie mich wissen, warum ich die Ausnahme erhalte. Danke schön
– Ganesh
24. März 15 um 6:24 Uhr
Evan
Schauen Sie sich an Ein-JAR. Es wird Ihre Anwendung in einer einzigen JAR-Datei mit einem spezialisierten Klassenladeprogramm verpacken, das unter anderem „Jars in Jars“ behandelt.
(Haftungsausschluss: Ich habe One-JAR noch nie verwendet, musste es noch nicht, hatte es nur für einen regnerischen Tag mit einem Lesezeichen versehen.)
Ich bin kein Java-Programmierer, keine Ahnung, ob ich verfügen über um die Anwendung selbst zu verpacken? Wie würde dies in Kombination mit einem vorhandenen Classloader funktionieren? Zur Verdeutlichung, ich werde es von Clojure verwenden und möchte in der Lage sein, ein JAR als Bibliothek und nicht als Anwendung zu laden.
– Alex B
31. Mai ’10 um 1:44 Uhr
Ahhh, es wäre wahrscheinlich nicht geeignet als. Denken Sie, dass Sie Ihre native(n) Bibliothek(en) außerhalb der JAR-Datei verteilen müssen, mit Anweisungen, um sie in den Bibliothekspfad der Anwendung zu stellen.
– Evan
2. Juni 10 um 15:26 Uhr
1) Fügen Sie die native Bibliothek als Ressource in Ihr JAR ein. Z.B. mit Maven oder Gradle und dem Standardprojektlayout die native Bibliothek in main/resources Verzeichnis.
2) Irgendwo in den statischen Initialisierern von Java-Klassen, die sich auf diese Bibliothek beziehen, fügen Sie den Code wie folgt ein:
String libName = "myNativeLib.so"; // The name of the file in resources/ dir
URL url = MyClass.class.getResource("https://stackoverflow.com/" + 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());
JarClassLoader ist ein Klassenlader zum Laden von Klassen, nativen Bibliotheken und Ressourcen aus einer einzelnen Monster-JAR und aus JARs innerhalb der Monster-JAR.
TofuBier
Wahrscheinlich müssen Sie die native Bibliothek für das lokale Dateisystem entzerren. Soweit ich weiß, betrachtet der Code, der das native Laden durchführt, das Dateisystem.
Dieser Code sollte Ihnen beim Einstieg helfen (ich habe ihn mir eine Weile nicht angesehen und er dient einem anderen Zweck, sollte aber den Zweck erfüllen, und ich bin im Moment ziemlich beschäftigt, aber wenn Sie Fragen haben, hinterlassen Sie einfach einen Kommentar und ich werde so schnell wie möglich antworten).
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("\.", "https://stackoverflow.com/") + "https://stackoverflow.com/" + 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("https://stackoverflow.com/", ""), 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();
}
}
}
}
Max Farsikow
Lösung für Kotlin:
build.gradle.dsl: Kotlin-Laufzeit (kotlin-stdlib-1.4.0.jar) und native Bibliothek (librust_kotlin.dylib) in JAR kopieren
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 Methode: Bibliothek in eine temporäre Datei kopieren, um sie mit absolutem Pfad zu laden