Leggendo il mio Jar's Manifest


135

Devo leggere il Manifestfile, che ha consegnato la mia classe, ma quando uso:

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

Ottengo il MANIFESTprimo .jarcaricato in Java Runtime.
La mia app verrà eseguita da un'applet o da un webstart,
quindi credo che non avrò accesso al mio .jarfile.

In realtà voglio leggere l' Export-packageattributo da .jarcui è stato avviato Felix OSGi, quindi posso esporre quei pacchetti a Felix. Qualche idea?


3
Penso che la risposta FrameworkUtil.getBundle () di seguito sia la migliore. Risponde a ciò che realmente vuoi fare (ottieni le esportazioni del pacchetto) piuttosto che a ciò che hai chiesto (leggi il manifest).
Chris Dolan,

Risposte:


117

Puoi fare una delle due cose:

  1. Chiama getResources()e scorre la raccolta restituita di URL, leggendoli come manifest fino a quando non trovi il tuo:

    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. Puoi provare a verificare se getClass().getClassLoader()è un'istanza di java.net.URLClassLoader. La maggior parte dei classloader Sun sono inclusi AppletClassLoader. Puoi quindi lanciarlo e chiamare ciò findResource()che è noto - almeno per le applet - per restituire direttamente il manifest necessario:

    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
Perfetto! Non ho mai saputo che potessi scorrere le risorse con lo stesso nome.
Houtman,

Come fai a sapere che il classloader è a conoscenza di un solo file .jar? (vero in molti casi suppongo) Preferirei di gran lunga usare qualcosa associato direttamente alla classe in questione.
Jason S,

7
è buona norma creare risposte separate per ognuna, anziché includere le 2 correzioni in una risposta. Risposte separate possono essere votate in modo indipendente.
Alba Mendez,

solo una nota: avevo bisogno di qualcosa di simile ma sono dentro una GUERRA su JBoss, quindi il secondo approccio non ha funzionato per me. Ho finito con una variante di stackoverflow.com/a/1283496/160799
Gregor

1
La prima opzione non ha funzionato per me. Ho ottenuto i manifesti dei miei 62 vasetti di dipendenza, ma non quello in cui è stata definita la classe attuale ...
Jolta,

120

Puoi prima trovare l'URL per la tua classe. Se è un JAR, allora carichi il manifest da lì. Per esempio,

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

Mi piace questa soluzione in quanto ottiene direttamente il tuo manifest piuttosto che doverlo cercare.
Jay,

1
può essere migliorato un po 'rimuovendo il controllo delle condizioniclassPath.replace("org/example/MyClass.class", "META-INF/MANIFEST.MF"
Jay

2
Chi chiude il flusso?
ceving

1
Questo non funziona nelle classi interne, poiché getSimpleNamerimuove il nome della classe esterna. Questo lavoro per classi interne: clazz.getName().replace (".", "/") + ".class".
ceving

3
Devi chiudere il flusso, il costruttore manifest non lo fa.
BrianT.

21

È possibile utilizzare Manifestsda jcabi-manifesta e leggere qualsiasi attributo da uno qualsiasi dei file MANIFEST.MF disponibili con una sola riga:

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

L'unica dipendenza di cui hai bisogno è:

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

Inoltre, vedi questo post del blog per maggiori dettagli: http://www.yegor256.com/2014/07/03/how-to-read-manifest-mf.html


Biblioteche molto belle. C'è un modo per controllare il livello di registro?
assylias,

1
Tutte le librerie jcabi accedono tramite SLF4J. Puoi spedire i messaggi di log usando qualsiasi funzione tu voglia, ad esempio log4j o logback
yegor256

se si utilizza logback.xml la riga che è necessario aggiungere è simile a<logger name="com.jcabi.manifests" level="OFF"/>
driftcatcher

1
Più manifest dello stesso classloader si sovrappongono e si sovrascrivono a vicenda
guai,

13

Devo ammettere in anticipo che questa risposta non risponde alla domanda originale, quella di poter generalmente accedere al Manifesto. Tuttavia, se ciò che è veramente necessario è leggere uno di una serie di attributi manifest "standard", la seguente soluzione è molto più semplice di quelle postate sopra. Quindi spero che il moderatore lo consenta. Nota che questa soluzione è in Kotlin, non in Java, ma mi aspetterei che una porta su Java sarebbe banale. (Anche se lo ammetto, non conosco l'equivalente Java di ".`package`".

Nel mio caso, volevo leggere l'attributo "Implementazione-Versione", quindi ho iniziato con le soluzioni sopra indicate per ottenere il flusso e quindi leggerlo per ottenere il valore. Mentre questa soluzione ha funzionato, un collega che ha esaminato il mio codice mi ha mostrato un modo più semplice per fare quello che volevo. Nota che questa soluzione è in Kotlin, non in Java.

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

Ancora una volta notare che questo non risponde alla domanda originale, in particolare "Pacchetto esportazione" non sembra essere uno degli attributi supportati. Detto questo, esiste un myPackage.name che restituisce un valore. Forse qualcuno che lo capisce più di quanto possa commentare se restituisce il valore richiesto dal poster originale.


4
In effetti, il porto di Java è semplice:String implementationVersion = MyApplication.class.getPackage().getImplementationVersion();
Ian Robertson,

In realtà questo è quello che stavo cercando. Sono anche felice che Java abbia anche un equivalente.
Aleksander Stelmaczonek,

12

Credo che il modo più appropriato per ottenere il manifest per qualsiasi bundle (incluso il bundle che ha caricato una determinata classe) sia utilizzare l'oggetto Bundle o BundleContext.

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

Si noti che fornisce anche l'oggetto Bundle getEntry(String path) ricerca di risorse contenute in un bundle specifico, anziché la ricerca dell'intero percorso di classe di quel bundle.

In generale, se si desidera informazioni specifiche sul bundle, non fare affidamento su ipotesi sui programmi di caricamento classi, basta usare direttamente le API OSGi.


9

Il codice seguente funziona con più tipi di archivi (jar, war) e più tipi di classloader (jar, url, vfs, ...)

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

può derivare da clz.getResource(resource).toString()barre rovesciate?
bacino

9

Il modo più semplice è usare la classe JarURLConnection:

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

Perché in alcuni casi ...class.getProtectionDomain().getCodeSource().getLocation();dà il percorso con vfs:/, quindi questo dovrebbe essere gestito in aggiunta.


Questo è di gran lunga il modo più semplice e pulito per farlo.
walen

6

È possibile utilizzare getProtectionDomain (). GetCodeSource () in questo modo:

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
getCodeSourcepotrebbe tornare null. Quali sono i criteri che questo funzionerà? La documentazione non spiega questo.
ceving

4
Da dove viene DataUtilitiesimportato? Non sembra essere nel JDK.
Jolta,

2

Perché includi il passaggio getClassLoader? Se dici "this.getClass (). GetResource ()" dovresti ottenere risorse relative alla classe chiamante. Non ho mai usato ClassLoader.getResource (), anche se da una rapida occhiata a Java Docs sembra che questo ti porterà la prima risorsa con quel nome trovata in qualsiasi percorso di classe corrente.


Se la tua classe è denominata "com.mypackage.MyClass", la chiamata class.getResource("myresource.txt")tenterà di caricare da quella risorsa com/mypackage/myresource.txt. Come userai esattamente questo approccio per ottenere il manifest?
ChssPly76,

1
Va bene, devo tornare indietro. Questo è ciò che viene dal non testare. Stavo pensando che potevi dire this.getClass (). GetResource ("../../ META-INF / MANIFEST.MF") (Comunque molti ".." sono necessari dato il nome del tuo pacchetto.) Ma mentre che funziona per i file di classe in una directory per salire su un albero di directory, apparentemente non funziona per i JAR. Non vedo perché no, ma è così. Né funziona this.getClass (). GetResource ("/ META-INF / MANIFEST.MF") - che mi dà il manifest per rt.jar. (Continua ...)
Jay,

Quello che puoi fare è usare getResource per trovare il percorso del tuo file di classe, quindi rimuovere tutto dopo il "!" per ottenere il percorso del vaso, quindi aggiungere "/META-INF/MANIFEST.MF". Come ha suggerito Zhihong, quindi sto votando il suo.
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
        }
      }
    }
  }

Questa risposta utilizza un modo molto complesso e soggetto a errori di caricamento del manifest. la soluzione molto più semplice è usare cl.getResourceAsStream("META-INF/MANIFEST.MF").
Robert,

Hai provato? Quale vaso manifest otterrà se hai più vasetti in classpath? Ci vorrà il primo che non è quello che ti serve. Il mio codice risolve questo problema e funziona davvero.
Alex Konshin,

Non ho criticato il modo in cui utilizzi il classloader per caricare una risorsa specifica. Stavo sottolineando che tutto il codice tra classLoader.getResource(..)ed url.openStream()è totalmente irrilevante e soggetto a errori in quanto cerca di fare lo stesso classLoader.getResourceAsStream(..).
Robert,

No. È diverso. Il mio codice prende manifest dal barattolo specifico in cui si trova la classe anziché dal primo barattolo nel percorso di classe.
Alex Konshin,

Il tuo "codice di caricamento specifico del vaso" equivale alle seguenti due righe:ClassLoader classLoader = cl.getClassLoader(); return new Manifest(classLoader.getResourceAsStream("/META-INF/MANIFEST.MF"));
Robert,

0

Ho usato la soluzione di Anthony Juckel ma in MANIFEST.MF la chiave deve iniziare con le maiuscole.

Quindi il mio file MANIFEST.MF contiene una chiave come:

Mykey: valore

Quindi nell'attivatore o in un'altra classe puoi usare il codice di Anthony per leggere il file MANIFEST.MF e il valore di cui hai bisogno.

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

Ho questa strana soluzione che esegue applicazioni di guerra in un server Jetty incorporato, ma queste app devono anche funzionare su server Tomcat standard e abbiamo alcune proprietà speciali nel modo più efficace.

Il problema era che quando in Tomcat, il manifest poteva essere letto, ma quando nel molo, veniva raccolto un manifest casuale (che mancava le proprietà speciali)

Sulla base della risposta di Alex Konshin, ho trovato la seguente soluzione (l'inputstream viene quindi utilizzato in una classe Manifest):

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;
    }
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.