मेरे अपने जार के घोषणापत्र पढ़ना


134

मुझे Manifestफ़ाइल पढ़ने की ज़रूरत है , जिसने मेरी कक्षा को वितरित किया, लेकिन जब मैं उपयोग करता हूं:

getClass().getClassLoader().getResources(...)

मैं जावा रनटाइम में MANIFESTपहले .jarलोड से प्राप्त करता हूं ।
मेरा ऐप एक ऐपलेट या एक वेबस्टार्ट से चल
रहा होगा , इसलिए मुझे अपनी खुद की .jarफ़ाइल तक पहुंच नहीं होगी , मुझे लगता है।

मैं वास्तव में उस Export-packageविशेषता को पढ़ना चाहता हूं .jarजिसमें से फेलिक्स ओएसजीआई शुरू किया था, इसलिए मैं उन पैकेजों को फेलिक्स को उजागर कर सकता हूं। कोई विचार?


3
मुझे लगता है कि नीचे FrameworkUtil.getBundle () उत्तर सबसे अच्छा है। यह उत्तर देता है कि आप वास्तव में क्या करना चाहते हैं (बंडल का निर्यात प्राप्त करें) इसके बजाय आपने जो पूछा (प्रकट पढ़ें)।
क्रिस डोलन

जवाबों:


117

आप दो काम कर सकते हैं:

  1. getResources()URL के लौटाए गए संग्रह के माध्यम से कॉल करें और पुनरावृति करें, उन्हें तब तक पढ़ना जब तक आप उन्हें खोज न लें:

    Enumeration<URL> resources = getClass().getClassLoader()
      .getResources("META-INF/MANIFEST.MF");
    while (resources.hasMoreElements()) {
        try {
          Manifest manifest = new Manifest(resources.nextElement().openStream());
          // check that this is your manifest and do what you need or get the next one
          ...
        } catch (IOException E) {
          // handle
        }
    }
    
  2. आप जाँच कर सकते हैं कि क्या getClass().getClassLoader()इसका उदाहरण है java.net.URLClassLoader। अधिकांश सन क्लास लोडर शामिल हैं AppletClassLoader। फिर आप इसे कास्ट कर सकते हैं और कॉल कर सकते हैं findResource()जो कि ज्ञात है - एप्लेट्स के लिए, कम से कम - सीधे प्रकट प्रकट करने के लिए।

    URLClassLoader cl = (URLClassLoader) getClass().getClassLoader();
    try {
      URL url = cl.findResource("META-INF/MANIFEST.MF");
      Manifest manifest = new Manifest(url.openStream());
      // do stuff with it
      ...
    } catch (IOException E) {
      // handle
    }
    

5
उत्तम! मैं कभी नहीं जानता था कि आप एक ही नाम के साथ संसाधनों के माध्यम से पुनरावृति कर सकते हैं।
हॉटमैन

आप कैसे जानते हैं कि क्लास लोडर केवल एक .jar फ़ाइल से ही परिचित है? (सच कई मामलों में मुझे लगता है) मैं बहुत सीधे सवाल में वर्ग के साथ जुड़े कुछ का उपयोग करेगा।
जेसन एस

7
एक उत्तर में 2 फ़िक्स को शामिल करने के बजाय, प्रत्येक के लिए अलग-अलग उत्तर बनाना एक अच्छा अभ्यास है । अलग-अलग उत्तरों को स्वतंत्र रूप से वोट दिया जा सकता है।
अल्बा मेंडेज़

बस एक नोट: मुझे कुछ इसी तरह की जरूरत थी, लेकिन मैं JBoss पर एक WAR के अंदर हूं, इसलिए दूसरा तरीका मेरे लिए काम नहीं आया। मैं stackoverflow.com/a/1283496/160799 के
ग्रेगर

1
पहला विकल्प मेरे काम नहीं आया। मुझे अपने 62 निर्भरता जार के मैनिफ़ेस्ट मिले, लेकिन वह नहीं जहाँ वर्तमान वर्ग को परिभाषित किया गया था ...
जोलता

120

आप अपनी कक्षा के लिए URL पहले पा सकते हैं। यदि यह एक जार है, तो आप वहां से प्रकट को लोड करते हैं। उदाहरण के लिए,

Class clazz = MyClass.class;
String className = clazz.getSimpleName() + ".class";
String classPath = clazz.getResource(className).toString();
if (!classPath.startsWith("jar")) {
  // Class not from JAR
  return;
}
String manifestPath = classPath.substring(0, classPath.lastIndexOf("!") + 1) + 
    "/META-INF/MANIFEST.MF";
Manifest manifest = new Manifest(new URL(manifestPath).openStream());
Attributes attr = manifest.getMainAttributes();
String value = attr.getValue("Manifest-Version");

मुझे यह समाधान पसंद है क्योंकि इसकी खोज करने के बजाय इसे सीधे आपकी अपनी अभिव्यक्ति मिलती है।
Jay

1
हालत की जाँच को हटाकर थोड़ा सुधार किया जा सकता हैclassPath.replace("org/example/MyClass.class", "META-INF/MANIFEST.MF"
Jay

2
कौन धारा को बंद करता है?
छत

1
यह आंतरिक कक्षाओं में काम नहीं करता है, क्योंकि getSimpleNameबाहरी वर्ग के नाम को हटा देता है। यह आंतरिक वर्गों के लिए काम करेगा clazz.getName().replace (".", "/") + ".class":।
छत

3
आपको स्ट्रीम को बंद करने की आवश्यकता है, मैनिफ़ेस्ट कंस्ट्रक्टर नहीं करता है।
ब्रायन

21

आप jcabi- मैनिफ़ेस्टManifests से उपयोग कर सकते हैं और किसी भी उपलब्ध MANIFEST.MF फ़ाइलों में से किसी एक विशेषता को केवल एक पंक्ति के साथ पढ़ सकते हैं :

String value = Manifests.read("My-Attribute");

एकमात्र निर्भरता जिसकी आपको आवश्यकता है:

<dependency>
  <groupId>com.jcabi</groupId>
  <artifactId>jcabi-manifests</artifactId>
  <version>0.7.5</version>
</dependency>

इसके अलावा, अधिक जानकारी के लिए इस ब्लॉग पोस्ट को देखें: http://www.yegor256.com/2014/07/03/how-to-read-manifest-mn.html


बहुत अच्छे पुस्तकालय। क्या लॉग स्तर को नियंत्रित करने का कोई तरीका है?
23

1
सभी jcabi libs SLF4J के माध्यम से लॉग इन करते हैं। आप अपनी इच्छानुसार किसी भी सुविधा का उपयोग करके लॉग संदेश भेज सकते हैं, उदाहरण के लिए log4j या logback
yegor256

अगर logback.xml का उपयोग करने के लिए आपको जो लाइन जोड़ने की आवश्यकता है वह है<logger name="com.jcabi.manifests" level="OFF"/>
driftcatcher

1

13

मैं यह स्वीकार करूंगा कि यह उत्तर मूल प्रश्न का उत्तर नहीं देता है, जो कि आमतौर पर मैनिफेस्ट तक पहुंचने में सक्षम होता है। लेकिन अगर वास्तव में आवश्यक है तो कई "मानक" घोषणापत्र विशेषताओं में से एक को पढ़ने के लिए, निम्न समाधान ऊपर पोस्ट किए गए की तुलना में बहुत सरल है। इसलिए मुझे उम्मीद है कि मध्यस्थ इसे अनुमति देगा। ध्यान दें कि यह समाधान कोटलिन में है, न कि जावा पर, लेकिन मुझे उम्मीद है कि जावा के लिए एक पोर्ट तुच्छ होगा। (हालांकि मैं मानता हूँ कि मैं जावा को "। पैकेज" के बराबर नहीं जानता।

मेरे मामले में मैं "कार्यान्वयन-संस्करण" विशेषता पढ़ना चाहता था, इसलिए मैंने स्ट्रीम प्राप्त करने के लिए ऊपर दिए गए समाधानों के साथ शुरुआत की और फिर मूल्य प्राप्त करने के लिए इसे पढ़ा। जब इस समाधान ने काम किया, तो एक सहकर्मी ने मेरे कोड की समीक्षा करते हुए मुझे वह करने का एक आसान तरीका दिखाया जो मैं चाहता था। ध्यान दें कि यह समाधान कोटलिन में है, न कि जावा में।

val myPackage = MyApplication::class.java.`package`
val implementationVersion = myPackage.implementationVersion

एक बार फिर ध्यान दें कि यह मूल प्रश्न का उत्तर नहीं देता है, विशेष रूप से "निर्यात-पैकेज" समर्थित विशेषताओं में से एक नहीं लगता है। उस ने कहा, एक myPackage.name है जो एक मान लौटाता है। शायद कोई व्यक्ति जो मुझे इससे अधिक समझता है, वह टिप्पणी कर सकता है कि मूल पोस्टर अनुरोध कर रहा है या नहीं।


4
वास्तव में, जावा बंदरगाह सीधा है:String implementationVersion = MyApplication.class.getPackage().getImplementationVersion();
इयान रॉबर्टसन

यह तथ्य यह है कि मैं क्या देख रहा था। मुझे यह भी खुशी है कि जावा में भी एक समकक्ष है।
एलेक्जेंडर स्टेलमज़ोनक

12

मेरा मानना ​​है कि किसी भी बंडल के लिए प्रकट होने के लिए सबसे उपयुक्त तरीका है (बंडल सहित जो किसी दिए गए वर्ग को लोड करता है) बंडल या बंडलकनेक्ट ऑब्जेक्ट का उपयोग करना है।

// If you have a BundleContext
Dictionary headers = bundleContext.getBundle().getHeaders();

// If you don't have a context, and are running in 4.2
Bundle bundle = FrameworkUtil.getBundle(this.getClass());
bundle.getHeaders();

ध्यान दें कि बंडल ऑब्जेक्ट getEntry(String path)संपूर्ण बंडल को खोजने के बजाय एक विशिष्ट बंडल के भीतर मौजूद संसाधनों को देखने के लिए प्रदान करता है।

सामान्य तौर पर, यदि आप बंडल-विशिष्ट जानकारी चाहते हैं, तो क्लास लोडर के बारे में मान्यताओं पर भरोसा न करें, बस सीधे ओएसजीआई एपीआई का उपयोग करें।


9

निम्न कोड कई प्रकार के अभिलेखागार (जार, युद्ध) और कई प्रकार के क्लास लोडर (जार, यूआरएल, वीएफएस, ...) के साथ काम करता है।

  public static Manifest getManifest(Class<?> clz) {
    String resource = "/" + clz.getName().replace(".", "/") + ".class";
    String fullPath = clz.getResource(resource).toString();
    String archivePath = fullPath.substring(0, fullPath.length() - resource.length());
    if (archivePath.endsWith("\\WEB-INF\\classes") || archivePath.endsWith("/WEB-INF/classes")) {
      archivePath = archivePath.substring(0, archivePath.length() - "/WEB-INF/classes".length()); // Required for wars
    }

    try (InputStream input = new URL(archivePath + "/META-INF/MANIFEST.MF").openStream()) {
      return new Manifest(input);
    } catch (Exception e) {
      throw new RuntimeException("Loading MANIFEST for class " + clz + " failed!", e);
    }
  }

clz.getResource(resource).toString()बैकस्लैश का परिणाम हो सकता है?
बेसिन

9

सबसे आसान तरीका है ज्यूरलकोनेक्शन क्लास का उपयोग करना:

String className = getClass().getSimpleName() + ".class";
String classPath = getClass().getResource(className).toString();
if (!classPath.startsWith("jar")) {
    return DEFAULT_PROPERTY_VALUE;
}

URL url = new URL(classPath);
JarURLConnection jarConnection = (JarURLConnection) url.openConnection();
Manifest manifest = jarConnection.getManifest();
Attributes attributes = manifest.getMainAttributes();
return attributes.getValue(PROPERTY_NAME);

क्योंकि कुछ मामलों में ...class.getProtectionDomain().getCodeSource().getLocation();साथ रास्ता देता है vfs:/, इसलिए इसे अतिरिक्त रूप से नियंत्रित किया जाना चाहिए।


यह अब तक का सबसे आसान और साफ तरीका है।
११:०४ पर

6

आप getProtectionDomain ()। GetCodeSource () का उपयोग इस तरह कर सकते हैं:

URL url = Menu.class.getProtectionDomain().getCodeSource().getLocation();
File file = DataUtilities.urlToFile(url);
JarFile jar = null;
try {
    jar = new JarFile(file);
    Manifest manifest = jar.getManifest();
    Attributes attributes = manifest.getMainAttributes();
    return attributes.getValue("Built-By");
} finally {
    jar.close();
}

1
getCodeSourceलौट सकते हैं null। क्या मापदंड हैं, कि यह काम करेगा? प्रलेखन इस व्याख्या नहीं करता।
छत

4
कहाँ से DataUtilitiesआयात किया जाता है? यह JDK में नहीं लगता है।
जोलता

2

आप getClassLoader चरण क्यों शामिल हैं? यदि आप कहते हैं "this.getClass ()। GetResource ()" आपको कॉलिंग क्लास के सापेक्ष संसाधन प्राप्त करने चाहिए। मैंने ClassLoader.getResource () का उपयोग कभी नहीं किया है, हालांकि जावा डॉक्स पर एक त्वरित नज़र से ऐसा लगता है कि आपको उस नाम का पहला संसाधन मिलेगा जो किसी भी वर्तमान क्लासपैथ में पाया गया है।


यदि आपकी कक्षा का नाम "com.mypackage.MyClass" है, तो कॉलिंग class.getResource("myresource.txt")उस संसाधन को लोड करने का प्रयास करेगी com/mypackage/myresource.txt। आप वास्तव में प्रकट होने के लिए इस दृष्टिकोण का उपयोग कैसे कर रहे हैं?
ChssPly76

1
ठीक है, मुझे पीछे हटना होगा। यही नहीं परीक्षण की बात आती है। मैं सोच रहा था कि आप यह कह सकते हैं ।getClass ()। getResource ("../../ META-INF / MANIFEST.MF") (हालाँकि कई ".." आवश्यक हैं कि आपके पैकेज का नाम दिया गया है।) लेकिन जबकि। निर्देशिका ट्री में अपने तरीके से काम करने के लिए निर्देशिका में क्लास फ़ाइलों के लिए काम करता है, यह जाहिरा तौर पर JAR के लिए काम नहीं करता है। मैं नहीं देखता कि क्यों नहीं, लेकिन यह है कि यह कैसे है। और न ही यह .getClass ()। GetResource ("/ META-INF / MANIFEST.MF") काम करता है - जो कि मुझे rt.jar के लिए प्रकट होता है। (जारी रखने के लिए ...)
जे

आप क्या कर सकते हैं getResource का उपयोग अपनी खुद की क्लास फ़ाइल के लिए पथ खोजने के लिए, फिर "!" के बाद सब कुछ बंद कर दें। जार को रास्ता पाने के लिए, फिर "/META-INF/MANIFEST.MF" पर जाएं। जैसे ज़ीहोंग ने सुझाव दिया, इसलिए मैं उसका वोट कर रहा हूं।
Jay

1
  public static Manifest getManifest( Class<?> cl ) {
    InputStream inputStream = null;
    try {
      URLClassLoader classLoader = (URLClassLoader)cl.getClassLoader();
      String classFilePath = cl.getName().replace('.','/')+".class";
      URL classUrl = classLoader.getResource(classFilePath);
      if ( classUrl==null ) return null;
      String classUri = classUrl.toString();
      if ( !classUri.startsWith("jar:") ) return null;
      int separatorIndex = classUri.lastIndexOf('!');
      if ( separatorIndex<=0 ) return null;
      String manifestUri = classUri.substring(0,separatorIndex+2)+"META-INF/MANIFEST.MF";
      URL url = new URL(manifestUri);
      inputStream = url.openStream();
      return new Manifest( inputStream );
    } catch ( Throwable e ) {
      // handle errors
      ...
      return null;
    } finally {
      if ( inputStream!=null ) {
        try {
          inputStream.close();
        } catch ( Throwable e ) {
          // ignore
        }
      }
    }
  }

यह उत्तर मैनिफेस्ट को लोड करने के लिए बहुत ही जटिल और त्रुटि वाले तरीके का उपयोग करता है। ज्यादा सरल समाधान का उपयोग करना है cl.getResourceAsStream("META-INF/MANIFEST.MF")
रॉबर्ट

क्या तुमने कोशिश की? अगर आपको क्लासपैथ में कई जार हैं तो क्या जार प्रकट होगा? यह पहले वाला ही लेगा जो आपको चाहिए नहीं। मेरा कोड इस समस्या को हल करता है और यह वास्तव में काम करता है।
एलेक्स कोन्शिन

मैंने इस तरह की आलोचना नहीं की कि आप एक विशिष्ट संसाधन को लोड करने के लिए हमें क्लास लोडर कैसे बनाते हैं। मैं उनका कहना था कि दोनों के बीच सब कोड classLoader.getResource(..)और url.openStream()पूरी तरह से अप्रासंगिक और त्रुटियों की संभावना के रूप में यह रूप में एक ही करने की कोशिश करता है classLoader.getResourceAsStream(..)करता है।
रॉबर्ट

नहीं। यह भिन्न है। मेरा कोड उस विशिष्ट जार से प्रकट होता है जहाँ क्लास क्लास में पहले जार से बजाय क्लास स्थित है।
एलेक्स कोन्शिन

आपका "जार विशिष्ट लोडिंग कोड" निम्नलिखित दो पंक्तियों के बराबर है:ClassLoader classLoader = cl.getClassLoader(); return new Manifest(classLoader.getResourceAsStream("/META-INF/MANIFEST.MF"));
रॉबर्ट

0

मैंने एंथनी जुकल से समाधान का उपयोग किया है, लेकिन मैनिफेस्ट में। कुंजी को अपरकेस से शुरू करना है।

तो मेरी MANIFEST.MF फ़ाइल में एक कुंजी शामिल है:

Mykey: मान

फिर एक्टीवेटर या किसी अन्य वर्ग में आप MANIFEST.MF फाइल को पढ़ने के लिए एंथनी के कोड और उस मान का उपयोग कर सकते हैं जिसकी आपको आवश्यकता है।

// If you have a BundleContext 
Dictionary headers = bundleContext.getBundle().getHeaders();

// If you don't have a context, and are running in 4.2 
Bundle bundle = `FrameworkUtil.getBundle(this.getClass()); 
bundle.getHeaders();

0

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

समस्या यह थी कि टॉमकैट में, प्रकटन को पढ़ा जा सकता था, लेकिन जब जेटी में, एक यादृच्छिक अभिव्यक्ति को उठाया गया था (जो विशेष गुणों से चूक गया था)

एलेक्स कोन्शिन के उत्तर के आधार पर, मैं निम्नलिखित समाधान के साथ आया था (इनपुटस्ट तब एक मैनिफेस्ट क्लास में उपयोग किया जाता है):

private static InputStream getWarManifestInputStreamFromClassJar(Class<?> cl ) {
    InputStream inputStream = null;
    try {
        URLClassLoader classLoader = (URLClassLoader)cl.getClassLoader();
        String classFilePath = cl.getName().replace('.','/')+".class";
        URL classUrl = classLoader.getResource(classFilePath);
        if ( classUrl==null ) return null;
        String classUri = classUrl.toString();
        if ( !classUri.startsWith("jar:") ) return null;
        int separatorIndex = classUri.lastIndexOf('!');
        if ( separatorIndex<=0 ) return null;
        String jarManifestUri = classUri.substring(0,separatorIndex+2);
        String containingWarManifestUri = jarManifestUri.substring(0,jarManifestUri.indexOf("WEB-INF")).replace("jar:file:/","file:///") + MANIFEST_FILE_PATH;
        URL url = new URL(containingWarManifestUri);
        inputStream = url.openStream();
        return inputStream;
    } catch ( Throwable e ) {
        // handle errors
        LOGGER.warn("No manifest file found in war file",e);
        return null;
    }
}
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.