2015-08-30 12 views
11

Sto provando a leggere il mio file META-INF/MANIFEST.MF dalla mia app Web Spring Boot (contenuta in un file jar).Come leggere il mio file META-INF/MANIFEST.MF in un'app di Spring Boot?

sto cercando il seguente codice:

 InputStream is = getClass().getResourceAsStream("/META-INF/MANIFEST.MF"); 

     Properties prop = new Properties(); 
     prop.load(is); 

Ma a quanto pare c'è qualcosa dietro le quinte in primavera Boot che un manifest.mf diverso viene caricato (e non la mia si trova nella cartella META-INF) .

Qualcuno sa come posso leggere la mia app manifest nella mia app Spring Boot?

UPDATE: Dopo alcune ricerche ho notato che con il solito modo di leggere un file MANIFEST.MF, in un'applicazione di primavera di avvio questo è il vaso che si accede

org.springframework.boot.loader.jar.JarFile 

risposta

3

Praticamente tutto vaso i file vengono forniti con un file manifest, quindi il codice restituisce prima file può trovare nel classpath.

Perché dovresti comunque il manifest? È un file usato da Java. Inserisci qualsiasi valore personalizzato di cui hai bisogno da qualche altra parte, come in un file .properties accanto al tuo file .class.

Update 2

Come menzionato in un commento qui sotto, non nella questione, il vero obiettivo è le informazioni sulla versione del manifesto. Java fornisce già tali informazioni con la classe java.lang.Package.

Non provare a leggere il manifest da solo, anche se lo si potesse trovare. Aggiornamento

Si noti che il file manifesto è non un file Properties. La sua struttura è molto più complessa di quella.

Vedere l'esempio nella documentazione java dello JAR File Specification.

Manifest-Version: 1.0 
Created-By: 1.7.0 (Sun Microsystems Inc.) 

Name: common/class1.class 
SHA-256-Digest: (base64 representation of SHA-256 digest) 

Name: common/class2.class 
SHA1-Digest: (base64 representation of SHA1 digest) 
SHA-256-Digest: (base64 representation of SHA-256 digest) 

Come si può vedere, Name e SHA-256-Digest si verifica più di una volta. La classe Properties non può gestirlo, dal momento che è solo un Map e le chiavi devono essere uniche.

+0

Funziona correttamente con altre app Java, ma apparentemente Spring Boot ha un approccio diverso. Ho bisogno di leggere il file manifest perché è proprio lì che Maven include informazioni su numero di versione, numero di build, fornitore di implementazione, ecc. E voglio mostrarlo all'utente. –

+1

@RicardoMemoria Date un'occhiata a [java.lang.Package] (https://docs.oracle.com/javase/7/docs/api/java/lang/Package.html) per tali informazioni. Come ho detto, ci sono * molti * file manifest nel tuo classpath. Riceverai * il tuo * file solo se hai la certezza che il tuo barattolo è il primo. È molto raramente una garanzia che puoi ottenere. – Andreas

+0

Grazie ... Sembra che l'utilizzo di java.lang.Package risolverà il mio problema. Ma tu dici di non provare a leggere me stesso il manifesto.Probabilmente la classe Properties non è il modo migliore (anche se ha funzionato per ciò di cui avevo bisogno), ma esiste una classe manifest in Java che può essere utilizzata specificamente per gestire il contenuto del file manifest. C'è qualche ragione specifica per cui non dovremmo provare a leggere noi stessi il manifest? –

1
public Properties readManifest() throws IOException { 
    Object inputStream = this.getClass().getProtectionDomain().getCodeSource().getLocation().getContent(); 
    JarInputStream jarInputStream = new JarInputStream((InputStream) inputStream); 
    Manifest manifest = jarInputStream.getManifest(); 
    Attributes attributes = manifest.getMainAttributes(); 
    Properties properties = new Properties(); 
    properties.putAll(attributes); 
    return properties; 
} 
+0

Ottengo questo errore java.lang.ClassCastException: org.springframework.boot.loader.jar.JarFile non può essere lanciato su java.io.InputStream. Apparentemente sta accedendo ad una libreria di avvio di primavera in altri per leggere il mio file jar. –

+0

Puoi valutare questa espressione: this.getClass(). GetProtectionDomain(). GetCodeSource(). GetLocation() – outdev

+0

Errore per me anche con la stessa eccezione, la chiamata getLocation() restituisce URL jar: file:/Users/chrisinmtown/git /common-code/cmn-data/target/cmn-data-1.0.0-SNAPSHOT.jar!/BOOT-INF/classes!/ – chrisinmtown

4

E 'semplice basta aggiungere questa

InputStream is = this.getClass().getClassLoader().getResourceAsStream("META-INF/MANIFEST.MF"); 

    Properties prop = new Properties(); 
    try { 
     prop.load(is); 
    } catch (IOException ex) { 
     Logger.getLogger(IndexController.class.getName()).log(Level.SEVERE, null, ex); 
    } 

Lavorare per me.

Nota:

getClass().getClassLoader() è importante

e

"META-INF/MANIFEST.MF" non "/META-INF/MANIFEST.MF"

Grazie Aleksandar

+5

Funziona in altre applicazioni Java ma non in un'applicazione Spring Boot. In un'app di Spring Boot, MANIFEST.MF viene dal primo jar trovato nell'elenco di tutti i jar utilizzati nell'applicazione (inclusa l'app stessa) e, utilizzando l'esempio, il manifest che è stato letto proviene da una lib Java standard. –

+0

Ciao, questo sicuramente funziona per me in entrambi i casi. Dovrebbe funzionare con te. Forse hai dimenticato il plugin di maven-war o il plugin di maven-jar. Se vuoi, posso condividere il codice con te. – abosancic

+0

Ciao. Sì, funziona in una normale app Java, ma Spring Boot introduce un altro ClassLoader che impedisce al codice sopra di funzionare come previsto. In realtà, quando si chiama getResourceXX, spring boot cercherà la prima risorsa che corrisponde al nome in tutti i jar del percorso della classe. –

1

Dopo aver testato e cercato i documenti di Spring, ho trovato un modo per leggere il file manifest.

Prima di tutto, Spring Boot implementa il proprio ClassLoader che cambia il modo in cui vengono caricate le risorse. Quando chiami getResource(), Spring Boot caricherà la prima risorsa che corrisponde al nome di risorsa specificato nell'elenco di tutti i JAR disponibili nel percorso di classe e il tuo contenitore di applicazioni non è la prima scelta.

Così, quando l'emissione di un comando come:

getClassLoader().getResourceAsStream("/META-INF/MANIFEST.MF"); 

Il primo file MANIFEST.MF si trovano in qualunque vaso del percorso di classe viene restituito. Nel mio caso, proveniva da una libreria jar JDK.

SOLUZIONE:

sono riuscito a ottenere un elenco di tutti i Jars caricate dal app che contiene la risorsa "/META-INF/MANIFEST.MF" e controllato se la risorsa è venuto dal mio vaso di applicazione. Se è così, leggere il suo file MANIFEST.MF e tornare alla app, come quella:

private Manifest getManifest() { 
    // get the full name of the application manifest file 
    String appManifestFileName = this.getClass().getProtectionDomain().getCodeSource().getLocation().toString() + JarFile.MANIFEST_NAME; 

    Enumeration resEnum; 
    try { 
     // get a list of all manifest files found in the jars loaded by the app 
     resEnum = Thread.currentThread().getContextClassLoader().getResources(JarFile.MANIFEST_NAME); 
     while (resEnum.hasMoreElements()) { 
      try { 
       URL url = (URL)resEnum.nextElement(); 
       // is the app manifest file? 
       if (url.toString().equals(appManifestFileName)) { 
        // open the manifest 
        InputStream is = url.openStream(); 
        if (is != null) { 
         // read the manifest and return it to the application 
         Manifest manifest = new Manifest(is); 
         return manifest; 
        } 
       } 
      } 
      catch (Exception e) { 
       // Silently ignore wrong manifests on classpath? 
      } 
     } 
    } catch (IOException e1) { 
     // Silently ignore wrong manifests on classpath? 
    } 
    return null; 
} 

Questo metodo restituirà tutti i dati da un file MANIFEST.MF all'interno di un oggetto Manifest.

ho preso in prestito parte della soluzione da reading MANIFEST.MF file from jar file using JAVA

+2

Questo sembra abbastanza codice da suggerire che la vera risposta è "non farlo". –

+0

Perché non apri direttamente l'URL invece di eseguire il ciclo di tutti gli elementi e controllare se corrisponde a appManifestFileName? – Pawan

+0

A causa di una funzione SpringBoot. La tua applicazione viene eseguita internamente da un programma di avvio, quindi se provi a leggere direttamente la risorsa URL, indirizzerà al launcher, invece del tuo jar. Alla fine della giornata, il tuo barattolo è solo un altro barattolo utilizzato dal programma di avvio. –

12

uso java.lang.Package di leggere l'attributo Implementation-Version in avvio molla dal manifesto.

String version = Application.class.getPackage().getImplementationVersion(); 

L'attributo Implementation-Version deve essere configurato in build.gradle

jar { 
    baseName = "my-app" 
    version = "0.0.1" 
    manifest { 
     attributes("Implementation-Version": version) 
    } 
} 
+0

Funziona benissimo! –

+0

Solo un avviso: come il problema di spring-boot [6619] (https://github.com/spring-projects/spring-boot/issues/6619) spiegava, in questo modo funzionava solo nell'esecuzione di un jar/war confezionato. 'gradle bootRun' non funzionerà. – btpka3

+0

Funziona perfettamente sul server integrato di Jetty. per creare il jar Jetty uber con il plug-in Maven, aggiungere le voci manifest per Implementation-Version. ' $ {project.version} _ $ {dev.build.timestamp} _branch: $ {scmBranch} _rev: $ {buildNumber}' Quindi dal tuo codice puoi ottenere facilmente il valore da 'String version = Application.class.getPackage(). getImplementationVersion();' – user3774109

1

ce l'ho sfruttando risoluzione risorsa Primavera:

@Service 
public class ManifestService { 

    protected String ciBuild; 

    public String getCiBuild() { return ciBuild; } 

    @Value("${manifest.basename:/META-INF/MANIFEST.MF}") 
    protected void setManifestBasename(Resource resource) { 
     if (!resource.exists()) return; 
     try (final InputStream stream = resource.getInputStream()) { 
      final Manifest manifest = new Manifest(stream); 
      ciBuild = manifest.getMainAttributes().getValue("CI-Build"); 
     } 
     catch (IOException e) { 
      throw new RuntimeException(e); 
     } 
    } 

} 

Qui stiamo ottenendo CI-Build, e si può facilmente estendere la esempio per caricare altri attributi.

+0

resource.exists() restituisce sempre false nei miei test, sto usando la versione di avvio di primavera 1.5.3.RELEASE. L'eccezione è java.io.FileNotFoundException: la risorsa ServletContext [/META-INF/MANIFEST.MF] non può essere risolta nell'URL. Ma il barattolo riconfezionato ha il mio manifest sul percorso /META-INF/MANIFEST.MF, quindi IDK. – chrisinmtown