Как связать родную библиотеку и библиотеку JNI внутри JAR?
В этой библиотеке находится Токийский кабинет.
Я хочу иметь встроенную библиотеку, библиотеку JNI и все классы Java API в одном файле JAR, чтобы избежать головных болей перераспределения.
Кажется, что попытка этого в GitHub, но
- Он не включает в себя собственно родную библиотеку, а только библиотеку JNI.
- Это похоже на Leiningen плагин родных зависимостей (он не будет работать как распространяемый).
Вопрос в том, могу ли я объединить все в один JAR и перераспределить его? Если да, то как?
P.S.: Да, я понимаю, что это может иметь последствия переносимости.
Ответы
Ответ 1
Можно создать один файл JAR со всеми зависимостями, включая собственные библиотеки JNI для одной или нескольких платформ. Основной механизм заключается в использовании System.load(File) для загрузки библиотеки вместо типичного System.loadLibrary(String), который ищет системное свойство java.library.path. Этот метод значительно упрощает установку, поскольку пользователю не нужно устанавливать библиотеку JNI в своей системе, однако за счет этого все платформы могут не поддерживаться, поскольку конкретная библиотека для платформы может не включаться в один файл JAR.,
Процесс выглядит следующим образом:
- включите собственные библиотеки JNI в файл JAR в определенном для платформы месте, например, в NATIVE/$ {os.arch}/$ {os.name}/libname.lib
- создать код в статическом инициализаторе основного класса для
- рассчитать текущие os.arch и os.name
- найдите библиотеку в файле JAR в предопределенном месте, используя Class.getResource(String)
- если он существует, извлеките его во временный файл и загрузите его с помощью System.load(File).
Я добавил функциональность, чтобы сделать это для jzmq, Java-привязок ZeroMQ (бесстыдный плагин). Код можно найти здесь. Код jzmq использует гибридное решение, так что если встроенная библиотека не может быть загружена, код вернется к поиску библиотеки JNI вдоль java.library.path.
Ответ 2
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);
}
}
}
Ответ 3
Взгляните на One-JAR. Он упакует ваше приложение в один файл jar с помощью специализированного загрузчика классов, который, среди прочего, обрабатывает "банки в банках".
Он обрабатывает собственные библиотеки (JNI), распаковывая их во временную рабочую папку по мере необходимости.
(Отказ от ответственности: я никогда не использовал One-JAR, пока не нужно было, просто он был отмечен за черный день.)
Ответ 4
JarClassLoader - это загрузчик классов для загрузки классов, собственных библиотек и ресурсов из одного JAR-монстра и из JAR внутри монстра JAR.
Ответ 5
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());
Ответ 6
Вам, вероятно, придется развязать собственную библиотеку к локальной файловой системе. Насколько я знаю, бит кода, который выполняет загрузку на родной загрузке, смотрит на файловую систему.
Этот код должен помочь вам начать работу (я не смотрел на него некоторое время, и это для другой цели, но должно сделать трюк, и я довольно занят на данный момент, но если у вас есть вопросы, просто оставьте комментарий, и я отвечу, как только смогу).
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();
}
}
}
}