Kendi Jar's Manifest'imi okuyorum


135

ManifestSınıfımı teslim eden dosyayı okumam gerekiyor , ancak şunu kullandığımda:

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

Java Runtime'a MANIFESTilk .jaryüklendiği andan itibaren alıyorum .
Uygulamam bir uygulamadan veya bir web başlatıcısından çalışıyor olacak,
bu yüzden kendi dosyama erişimim olmayacak .jarsanırım.

Aslında Felix OSGi'yi başlatan Export-packageözniteliği okumak istiyorum .jar, böylece bu paketleri Felix'e ifşa edebilirim. Herhangi bir fikir?


3
Aşağıdaki FrameworkUtil.getBundle () yanıtının en iyisi olduğunu düşünüyorum. İstediğinizden (bildirimi okuyun) değil, gerçekte ne yapmak istediğinizi (paketin dışa aktarımlarını alın) yanıtlar .
Chris Dolan

Yanıtlar:


117

İki şeyden birini yapabilirsiniz:

  1. Arayın getResources()ve döndürülen URL koleksiyonunu tekrarlayın, sizinkini bulana kadar bunları manifesto olarak okuyun:

    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()Bir örneği olup olmadığını kontrol etmeyi deneyebilirsiniz java.net.URLClassLoader. Sun sınıf yükleyicilerin çoğu dahil AppletClassLoader. Daha sonra onu çevirebilir ve findResource()bilinen - en azından uygulamalar için - gerekli manifest'i doğrudan döndürmek için çağırabilirsiniz :

    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
Mükemmel! Aynı isimli kaynaklarda yineleme yapabileceğinizi hiç bilmiyordum.
Houtman

Sınıf yükleyicinin yalnızca tek bir .jar dosyasının farkında olduğunu nasıl anlarsınız? (sanırım çoğu durumda doğru) Söz konusu sınıfla doğrudan ilişkili bir şeyi kullanmayı tercih ederim.
Jason S

7
Bir var iyi bir uygulama yapmak için ayrı bir yerine yanıtında 2 düzeltmeleri de dahil olmak üzere, her biri için cevaplar. Ayrı cevaplar bağımsız olarak oylanabilir.
Alba Mendez

sadece bir not: Benzer bir şeye ihtiyacım vardı ama JBoss'ta bir SAVAŞ içindeyim, bu yüzden ikinci yaklaşım benim için işe yaramadı. Sonunda bir stackoverflow.com/a/1283496/160799
Gregor

1
İlk seçenek benim için işe yaramadı. 62 bağımlılık kavanozumun tezahürlerini aldım, ancak mevcut sınıfın tanımlandığı değil ...
Jolta

120

Önce sınıfınızın URL'sini bulabilirsiniz. Bir JAR ise, manifest'i oradan yüklersiniz. Örneğin,

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

Bu çözümü, aramak zorunda kalmak yerine doğrudan kendi tezahürünüzü aldığı için seviyorum.
Jay

1
durum kontrolünü kaldırarak biraz iyileştirilebilirclassPath.replace("org/example/MyClass.class", "META-INF/MANIFEST.MF"
Jay

2
Akışı kim kapatır?
2013

1
Bu, iç sınıflarda çalışmaz çünkü getSimpleNamedış sınıf adını kaldırır. Bu iç sınıflar için çalışacaktır: clazz.getName().replace (".", "/") + ".class".
2013

3
Akışı kapatmanız gerekir, manifest kurucusu kapatmaz.
BrianT.

21

Sen kullanabilirsiniz Manifestsgelen jcabi-manifestolarına ve sadece bir satır ile mevcut MANIFEST.MF dosyalarının herhangi birinden herhangi niteliğini okuyun:

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

İhtiyacınız olan tek bağımlılık şudur:

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

Ayrıca, daha fazla ayrıntı için bu blog gönderisine bakın: http://www.yegor256.com/2014/07/03/how-to-read-manifest-mf.html


Çok güzel kütüphaneler. Günlük seviyesini kontrol etmenin bir yolu var mı?
assylias

1
Tüm jcabi kütüphaneleri SLF4J üzerinden kaydedilir. Günlük mesajlarını istediğiniz herhangi bir tesisi kullanarak gönderebilirsiniz, örneğin log4j veya logback
yegor256

logback.xml kullanıyorsanız, eklemeniz gereken satır şuna benzer<logger name="com.jcabi.manifests" level="OFF"/>
driftcatcher

1
Aynı sınıf yükleyiciden gelen birden çok bildirim çakışıyor ve birbirinin üzerine
yazılıyor

13

Bu cevabın orijinal soruya, genel olarak Manifestoya erişme olanağına cevap vermediğini önceden itiraf edeceğim. Bununla birlikte, gerçekten gerekli olan şey, bir dizi "standart" Manifest özniteliğinden birini okumaksa, aşağıdaki çözüm yukarıda yayınlananlardan çok daha basittir. Umarım moderatör buna izin verir. Bu çözümün Java'da değil Kotlin'de olduğuna dikkat edin, ancak Java'ya bir bağlantı noktasının önemsiz olacağını umuyorum. (".`package" ın Java eşdeğerini bilmediğimi kabul etsem de.

Benim durumumda "Uygulama Sürümü" özelliğini okumak istedim, bu yüzden akışı elde etmek için yukarıda verilen çözümlerle başladım ve ardından değeri elde etmek için okudum. Bu çözüm işe yararken, kodumu inceleyen bir iş arkadaşım bana istediğimi yapmanın daha kolay bir yolunu gösterdi. Bu çözümün Java'da değil Kotlin'de olduğunu unutmayın.

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

Bir kez daha, bunun orijinal soruyu yanıtlamadığına dikkat edin, özellikle "İhracat paketi" desteklenen özelliklerden biri gibi görünmüyor. Bununla birlikte, bir değer döndüren bir myPackage.name var. Belki de bunu benim yorumlayabileceğimden daha fazla anlayan biri, bunun orijinal göndericinin istediği değeri döndürüp döndürmediği konusunda yorum yapabilir.


4
Gerçekten de java bağlantı noktası basit:String implementationVersion = MyApplication.class.getPackage().getImplementationVersion();
Ian Robertson

Aslında aradığım şey buydu. Java'nın da bir eşdeğeri olduğu için mutluyum.
Aleksander Stelmaczonek

12

Herhangi bir paket için bildirim almanın en uygun yolunun (belirli bir sınıfı yükleyen paket dahil) Bundle veya BundleContext nesnesini kullanmak olduğuna inanıyorum.

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

Bundle nesnesinin, aynı zamanda, getEntry(String path)o paketin tüm sınıf yolunda arama yapmak yerine, belirli bir pakette bulunan kaynakları aramayı da sağladığını unutmayın.

Genel olarak, pakete özgü bilgiler istiyorsanız, sınıf yükleyiciler hakkındaki varsayımlara güvenmeyin, doğrudan OSGi API'lerini kullanın.


10

En kolay yol, JarURLConnection sınıfını kullanmaktır:

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

Çünkü bazı durumlarda ...class.getProtectionDomain().getCodeSource().getLocation();yol verir vfs:/, bu yüzden bu ek olarak ele alınmalıdır.


Bu, bunu yapmanın açık ara en kolay ve en temiz yoludur.
walen

9

Aşağıdaki kod, birden çok arşiv türü (jar, savaş) ve birden çok sınıf yükleyici türü (jar, url, vfs, ...) ile çalışır.

  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()ters eğik çizgilerin sonucu olabilir mi?
basin

6

GetProtectionDomain (). GetCodeSource () 'u şu şekilde kullanabilirsiniz:

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
getCodeSourcedönebilir null. Bunun işe yarayacağı kriterler nelerdir? Dokümantasyon bu açıklamaz.
2013

4
Nereden DataUtilitiesithal ediliyor? JDK'da görünmüyor.
Jolta

2

GetClassLoader adımını neden dahil ediyorsunuz? "This.getClass (). GetResource ()" derseniz, kaynakları çağıran sınıfa göre almalısınız. ClassLoader.getResource () 'u hiç kullanmadım, ancak Java Dokümanlarına hızlıca bakıldığında, bu adın mevcut herhangi bir sınıf yolunda bulunan ilk kaynağını size verecek gibi görünüyor.


Sınıfınız "com.mypackage.MyClass" olarak adlandırıldıysa, çağrı class.getResource("myresource.txt")bu kaynağı buradan yüklemeyi deneyecektir com/mypackage/myresource.txt. Tezahürü elde etmek için bu yaklaşımı tam olarak nasıl kullanacaksınız?
ChssPly76

1
Tamam, geri dönmem gerekiyor. Test yapmamanın nedeni budur. This.getClass () diyebileceğini düşünüyordum. GetResource ("../../ META-INF / MANIFEST.MF") (Ancak paket adınız için birçok ".." gerekli.) Ama Bu, bir dizindeki sınıf dosyaları için çalışır ve bir dizin ağacında yukarı çıkmanıza yardımcı olur, görünüşe göre JAR'lar için çalışmıyor. Neden olmadığını anlamıyorum, ama bu böyle. This.getClass (). GetResource ("/ META-INF / MANIFEST.MF") çalışmıyor - bu bana rt.jar için manifest'i veriyor. (Devam edecek ...)
Jay

Yapabileceğiniz şey, kendi sınıf dosyanızın yolunu bulmak için getResource'u kullanmak ve ardından "!" İşaretinden sonraki her şeyi çıkarmaktır. kavanozun yolunu bulmak için "/META-INF/MANIFEST.MF" öğesini ekleyin. Zhihong'un önerdiği gibi, ben de ona oy veriyorum.
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
        }
      }
    }
  }

Bu cevap, Manifest'i yüklemenin çok karmaşık ve hataya açık bir yolunu kullanır. çok daha basit çözüm kullanmaktır cl.getResourceAsStream("META-INF/MANIFEST.MF").
Robert

Onu denedin mi? Sınıf yolunda birden fazla kavanozunuz varsa hangi kavanoz tezahürünü alır? İhtiyacın olan ilkini alacak. Kodum bu sorunu çözüyor ve gerçekten işe yarıyor.
Alex Konshin

Belirli bir kaynağı yüklemek için sınıf yükleyiciyi nasıl kullandığınızı eleştirmedim. Ben arasındaki tüm kod dikkat çekmeye başladı classLoader.getResource(..)ve url.openStream()o aynı yapmaya çalışır gibi tamamen alakasız ve hata eğilimli classLoader.getResourceAsStream(..)yapar.
Robert

Hayır! Bu farklı. Kodum, sınıf yolundaki ilk jar yerine sınıfın bulunduğu belirli bir kavanozdan tezahür alıyor.
Alex Konshin

"Jar özel yükleme kodunuz" aşağıdaki iki satıra eşdeğerdir:ClassLoader classLoader = cl.getClassLoader(); return new Manifest(classLoader.getResourceAsStream("/META-INF/MANIFEST.MF"));
Robert

0

Anthony Juckel'in çözümünü kullandım ama MANIFEST.MF'de anahtarın büyük harfle başlaması gerekiyor.

Yani MANIFEST.MF dosyam aşağıdaki gibi bir anahtar içeriyor:

Anahtarım: değer

Ardından aktivatörde veya başka bir sınıfta, MANIFEST.MF dosyasını ve ihtiyacınız olan değeri okumak için Anthony'nin kodunu kullanabilirsiniz.

// 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

Gömülü Jetty sunucusunda savaş uygulamaları çalıştıran bu tuhaf çözüme sahibim, ancak bu uygulamaların standart Tomcat sunucularında da çalışması gerekiyor ve adam festivalinde bazı özel niteliklerimiz var.

Sorun şu ki, Tomcat'tayken manifesto okunabiliyordu, ancak iskeledeyken rastgele bir manifest alındı ​​(bu, özel özellikleri kaçırdı)

Alex Konshin'in cevabına dayanarak, aşağıdaki çözümü buldum (girdi akışı daha sonra bir Manifest sınıfında kullanılıyor):

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;
    }
}
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.