एक JAR के अंदर एक देशी पुस्तकालय और एक JNI पुस्तकालय को कैसे बांधें?


101

प्रश्न में पुस्तकालय टोक्यो कैबिनेट है

मैं चाहता हूं कि पुनर्वितरण के सिरदर्द से बचने के लिए मूल पुस्तकालय, जेएनआई पुस्तकालय और एक जावा फ़ाइल में सभी जावा एपीआई कक्षाएं हों।

GitHub में इस पर एक प्रयास लगता है , लेकिन

  1. इसमें वास्तविक देशी पुस्तकालय, केवल जेएनआई पुस्तकालय शामिल नहीं है।
  2. यह Leiningen के देशी निर्भरता प्लगइन के लिए विशिष्ट प्रतीत होता है (यह पुनर्वितरण के रूप में काम नहीं करेगा)।

सवाल यह है कि क्या मैं एक जार में सब कुछ बंडल कर सकता हूं और इसे फिर से वितरित कर सकता हूं? यदि हाँ, तो कैसे?

पुनश्च: हाँ, मुझे पता है कि इसमें पोर्टेबिलिटी निहितार्थ हो सकता है।

जवाबों:


54

एक या एक से अधिक प्लेटफार्मों के लिए देशी JNI पुस्तकालयों सहित सभी निर्भरताओं के साथ एक एकल JAR फ़ाइल बनाना संभव है। बुनियादी तंत्र System.load (फ़ाइल) का उपयोग करने के लिए विशिष्ट System.loadLibrary (स्ट्रिंग) के बजाय लाइब्रेरी लोड करने के लिए है जो java.library.path सिस्टम प्रॉपर्टी को खोजता है। यह विधि इंस्टॉलेशन को अधिक सरल बनाती है क्योंकि उपयोगकर्ता को अपने सिस्टम पर JNI लाइब्रेरी को खर्च करने की आवश्यकता नहीं होती है, हालाँकि, सभी प्लेटफ़ॉर्म का समर्थन नहीं किया जा सकता है क्योंकि किसी प्लेटफ़ॉर्म के लिए विशिष्ट लाइब्रेरी एकल JAR फ़ाइल में शामिल नहीं हो सकती है ।

प्रक्रिया इस प्रकार है:

  • मंच के लिए विशिष्ट स्थान पर JAR फ़ाइल में देशी JNI लाइब्रेरी शामिल करें, उदाहरण के लिए NATIVE / $ {os.arch} / $ {os.name} /libname.lib
  • मुख्य वर्ग के एक स्थिर इनिशियलाइज़र में कोड बनाएँ
    • वर्तमान os.arch और os.name को कैल्क करें
    • Class.getResource (स्ट्रिंग) का उपयोग करके पूर्वनिर्धारित स्थान पर JAR फ़ाइल में लाइब्रेरी देखें
    • यदि यह मौजूद है, तो इसे अस्थायी फ़ाइल में निकालें और इसे System.load (फ़ाइल) के साथ लोड करें।

मैंने jzmq, ZeroMQ के जावा बाइंडिंग (बेशर्म प्लग) के लिए ऐसा करने के लिए कार्यक्षमता जोड़ी। यहां कोड पाया जा सकता है । Jzmq कोड एक हाइब्रिड समाधान का उपयोग करता है ताकि यदि कोई एम्बेडेड लाइब्रेरी लोड न हो सके, तो कोड java.library.path के साथ JNI लाइब्रेरी की खोज में वापस आ जाएगा।


1
मुझे यह पसंद है! यह एकीकरण के लिए बहुत अधिक परेशानी से बचाता है, और यदि आप विफल हो जाते हैं, तो आप हमेशा सिस्टम के साथ "पुराने" तरीके से वापस आ सकते हैं। मुझे लगता है कि मैं इसका इस्तेमाल करना शुरू कर दूंगा। धन्यवाद! :)
Matthieu

1
यदि मेरा पुस्तकालय dll किसी अन्य dll पर निर्भरता रखता है, तो मैं क्या करूं? मैं हमेशा UnsatisfiedLinkErrorपूर्व dll को लोड करने के रूप में प्राप्त करता हूं , जो बाद में लोड करना चाहता है, लेकिन इसे नहीं ढूँढ सकता क्योंकि यह जार में छिपा हुआ है। इसके अलावा बाद वाले डीएल को लोड करने से पहले मदद नहीं मिलती है।
फैब

अन्य आश्रितों DLLको पहले से जाना जाना चाहिए और उनके स्थानों को PATHपर्यावरण चर में जोड़ा जाना चाहिए ।
१६:१६ पर त्रयूटीद्रिष्ट

बहुत अच्छा काम करता है! अगर यह किसी के पार आने के लिए स्पष्ट नहीं है, तो अपने प्रोजेक्ट में एंबेडेडलिफ्टटूल क्लास को छोड़ दें और उसके अनुसार इसे बदलें।
जॉन ला मार्र

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 ("क्रिप्ट") का उपयोग करें; बेहतर हो सकता है
BlackJoker

1
नमस्ते, जब मैं मूल निवासी वर्ग का उपयोग करने की कोशिश कर रहा हूं और lib / armeabi / libmyname.so के अंदर .so फ़ाइल को दर्ज करने की कोशिश कर रहा हूं, तो मुझे java.lang.ExceptionInInitialsError, java.io.FileNotFoundException: के कारण अपवाद मिल रहा है। JAR के अंदर फ़ाइल /native/libhellojni.so नहीं मिली। कृपया मुझे बताएं कि मुझे अपवाद क्यों मिल रहा है। धन्यवाद
गणेश

16

वन-जार पर एक नज़र डालें । यह एक विशेष श्रेणी लोडर के साथ एक एकल जार फ़ाइल में आपके आवेदन को लपेट देगा जो अन्य चीजों के बीच "जार के भीतर जार" को संभालती है।

यह देशी (JNI) पुस्तकालयों को आवश्यक रूप से एक अस्थायी काम करने वाले फ़ोल्डर में अनपैक करके संभालता है

(अस्वीकरण: मैंने कभी भी वन-जार का उपयोग नहीं किया है, अभी तक इसकी आवश्यकता नहीं है, बस इसे बरसात के दिन के लिए बुकमार्क किया गया था।)


मैं एक जावा प्रोग्रामर नहीं हूँ, किसी भी विचार अगर मैं है आवेदन ही रैप करने के लिए? यह मौजूदा क्लास लोडर के साथ कैसे काम करेगा? स्पष्ट करने के लिए, मैं क्लूज्योर से इसका उपयोग करने जा रहा हूं और एक आवेदन के बजाय एक पुस्तकालय के रूप में एक जार को लोड करने में सक्षम होना चाहता हूं।
एलेक्स बी

आह, यह शायद उपयुक्त नहीं होगा। लगता है कि आप अपने मूल पुस्तकालय (एस) को जार फ़ाइल के बाहर वितरित करने के साथ अटक जाएंगे, उन्हें आवेदन के पुस्तकालय पथ में डालने के निर्देश के साथ।
इवान

8

1) अपने JAR में एक संसाधन के रूप में मूल पुस्तकालय शामिल करें। ई। जी। मावेन या ग्रैडल और मानक परियोजना लेआउट के साथ, मूल पुस्तकालय को main/resourcesनिर्देशिका में रखा गया है ।

2) कहीं जावा लाइब्रेरी के स्टैटिस्टिकल इनिशियलाइज़र में, इस लाइब्रेरी से संबंधित, निम्नलिखित की तरह कोड डाल दें:

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

यह समाधान उस स्थिति में भी काम करता है जब आप किसी एप्लिकेशन सर्वर को वाइल्डफ़्लाय की तरह कुछ तैनात करना चाहते हैं और सबसे अच्छी बात यह है कि यह एप्लिकेशन को ठीक से लोड करने में सक्षम है!
हैश

यह 'देशी पुस्तकालय पहले से ही लोड किए गए अपवाद से बचने का सबसे अच्छा उपाय है।
कृतिका विट्टल

5

JarClassLoader एक एकल राक्षस JAR और JAR से राक्षस JAR के अंदर कक्षाओं, देशी पुस्तकालयों और संसाधनों को लोड करने के लिए एक क्लास लोडर है।


1

आपको संभवतः स्थानीय लाइब्रेरी को स्थानीय फ़ाइल सिस्टम में अनजान करना होगा। जहां तक ​​मुझे पता है कि कोड का मूल कोड लोड करने वाले फाइल सिस्टम को देखता है।

इस कोड को आरंभ करने में आपकी सहायता करनी चाहिए (मैंने थोड़ी देर में इसे नहीं देखा है, और यह एक अलग उद्देश्य के लिए है, लेकिन यह चाल करना चाहिए, और मैं इस समय बहुत व्यस्त हूं, लेकिन अगर आपके पास प्रश्न हैं तो बस एक टिप्पणी छोड़ दें और मैं जैसे ही जवाब दूंगा)।

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 प्रोजेक्ट पर एक नज़र डालने की सलाह देता हूं
नीम प्रैक्स

zt-zip एक सभ्य एपीआई की तरह दिखता है।
टोफूबीर
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.