2009-08-13 27 views
103

Necesito leer el archivo Manifest, que entregó mi clase, pero cuando se utiliza:lectura Manifiesto de mi propia Jar

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

consigo el MANIFEST desde el primer .jar cargado en la ejecución de Java.
Mi aplicación se ejecutará desde un applet o un webstart,
, así que no tendré acceso a mi propio archivo .jar, supongo.

que realmente quiere leer el atributo Export-package del .jar que comenzó la Felix OSGi, por lo que puede exponer esos paquetes a Félix. ¿Algunas ideas?

+3

Creo que la respuesta FrameworkUtil.getBundle() a continuación es la mejor. Responde lo que * realmente * quieres hacer (obtener las exportaciones del paquete) en lugar de lo que pediste (lee el manifiesto). –

+0

https://stackoverflow.com/a/29103019/4985705 es también una buena solución – Max

Respuesta

91

Puede hacer una de dos cosas:

  1. llamada getResources() y recorrer la colección de regresar de URLs, leerlos como se manifiesta hasta que encuentre el suyo:

    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. Usted puede tratar comprobando si getClass().getClassLoader() es una instancia de java.net.URLClassLoader. La mayoría de los cargadores de clases de Sun son, incluido AppletClassLoader. Luego, puede echarlo y llame findResource() que ha sido conocido - para los applets, al menos - para devolver el manifiesto necesaria directamente:

    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 
    } 
    
+4

¡Perfecto! Nunca supe que podrías iterar a través de recursos con el mismo nombre. – Houtman

+0

¿Cómo sabes que el cargador de clases solo tiene conocimiento de un solo archivo .jar? (cierto en muchos casos, supongo) Preferiría usar algo asociado directamente con la clase en cuestión. –

+6

es una ** buena práctica ** hacer ** respuestas ** por cada una, en lugar de incluir las 2 soluciones en una respuesta. Las respuestas separadas se pueden votar de forma independiente. –

2

¿Por qué se incluye la etapa getClassLoader? Si dice "this.getClass(). GetResource()" debería obtener recursos relativos a la clase de llamada. Nunca he usado ClassLoader.getResource(), aunque de una mirada rápida a los Java Docs parece que obtendrá el primer recurso de ese nombre encontrado en cualquier classpath actual.

+0

Si su clase se llama "com.mypackage.MyClass", llamando a 'class.getResource (" myresource.txt ")' intentará cargar ese recurso desde 'com/mypackage/myresource.txt'. ¿Cómo usará exactamente este enfoque para obtener el manifiesto? – ChssPly76

+1

De acuerdo, tengo que dar marcha atrás.Eso es lo que viene de no probar. Estaba pensando que podría decir esto.getClass(). GetResource ("../../ META-INF/MANIFEST.MF") (Sin embargo, muchos ".." son necesarios dado el nombre de su paquete). Pero mientras que funciona para los archivos de clase en un directorio para abrirse camino en un árbol de directorios, aparentemente no funciona para los JAR. No veo por qué no, pero así es como es. Tampoco funciona this.getClass(). GetResource ("/ META-INF/MANIFEST.MF") - eso me da el manifiesto para rt.jar. (Continuará ...) – Jay

+0

Lo que puede hacer es usar getResource para encontrar la ruta a su propio archivo de clase, luego quitar todo después del "!" para obtener la ruta al contenedor, a continuación, agregue "/META-INF/MANIFEST.MF". Como Zhihong sugirió, entonces estoy votando por él. – Jay

93

Primero puede encontrar la URL de su clase. Si es un JAR, entonces carga el manifiesto desde allí. Por ejemplo,

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

Me gusta esta solución ya que obtiene su propio manifiesto directamente en lugar de tener que buscarlo. – Jay

+0

¡Funciona muy bien! ¡Gracias! Esto me ahorró mucha molestia. –

+0

¡Funciona como un amuleto, gracias muchísimo! – Gaurav

11

I creen que la forma más adecuada para conseguir el manifiesto para cualquier paquete (incluyendo el paquete que carga una clase dada) es utilizar el objeto 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(); 

Tenga en cuenta que el objeto paquete también proporciona getEntry(String path) para buscar recursos contenidos en un paquete específico, en lugar de buscar toda la ruta de clases de ese paquete.

En general, si desea información específica del paquete, no confíe en las suposiciones sobre los cargadores de clases, solo use las API OSGi directamente.

18

Puede usar Manifests de jcabi-manifests y leer cualquier atributo de cualquiera de MANIFEST disponible.archivos MF con una sola línea:

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

La única dependencia que necesita es:

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

Asimismo, consulte esta entrada del blog para más detalles: http://www.yegor256.com/2014/07/03/how-to-read-manifest-mf.html

+0

Muy buenas bibliotecas. ¿Hay alguna manera de controlar el nivel de registro? – assylias

+1

Todos los jcabi libs se registran a través de SLF4J. Puede enviar mensajes de registro usando cualquier recurso que desee, por ejemplo log4j o logback – yegor256

+0

si usa logback.xml, la línea que necesita agregar es como '' – hangtwenty

4

Puede utilizar getProtectionDomain(). getCodeSource() como este:

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(); 
} 
+0

'getCodeSource' puede devolver' null'. ¿Cuáles son los criterios, que esto funcionará? La [documentación] (http://docs.oracle.com/javase/7/docs/api/java/security/ProtectionDomain.html#getCodeSource%28%29) no explica esto. – ceving

+1

¿Dónde se importa 'DataUtilities'? No parece estar en el JDK. – Jolta

0

He utilizado la solución de Antho ny Juckel pero en MANIFEST.MF la clave tiene que comenzar con mayúsculas.

Así que mi archivo MANIFEST.MF contienen una llave como:

MyKey: valor

Luego, en el activador o de otra clase que puede utilizar el código de Anthony para leer el archivo MANIFEST.MF y el valor que necesitas

// 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
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 
     } 
     } 
    } 
    } 
+0

Esta respuesta usa una forma muy compleja y propensa a errores de cargar el Manifiesto. la solución mucho más simple es usar 'cl.getResourceAsStream (" META-INF/MANIFEST.MF ")'. – Robert

+0

¿Lo has probado? ¿Qué jar manifiesto se obtendrá si tienes múltiples jar en classpath? Tomará el primero que no es lo que necesitas. Mi código resuelve este problema y realmente funciona. –

+0

No critiqué la forma en que usamos el cargador de clases para cargar un recurso específico. Estaba señalando que todo el código entre 'classLoader.getResource (..)' y 'url.openStream()' es totalmente irrelevante y propenso a errores, ya que trata de hacer lo mismo que 'classLoader.getResourceAsStream (..)' hace . – Robert

7

El siguiente código funciona con múltiples tipos de archivos (JAR), guerra y varios tipos de cargadores de clases (tarro, 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); 
    } 
    } 
+0

puede resultar de 'clz.getResource (resource) .toString()' tener barras invertidas? – basin

5

La forma más fácil es utilizar la clase 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); 

Debido a que en algunos casos da ...class.getProtectionDomain().getCodeSource().getLocation(); camino con vfs:/, por lo esto debe ser manejado adicionalmente.

Cuestiones relacionadas