读我自己的Jar's Manifest

时间:2009-08-13 15:17:04

标签: java osgi manifest.mf apache-felix

我需要阅读提供课程的Manifest文件,但是当我使用时:

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

我从加载到Java Runtime的第一个MANIFEST获得.jar 我的应用程序将从applet或webstart运行,
所以我想我无法访问自己的.jar文件。

我实际上想要从Export-package开始阅读.jar属性 Felix OSGi,所以我可以将这些包暴露给Felix。有什么想法吗?

13 个答案:

答案 0 :(得分:113)

您可以先找到课程的网址。如果它是JAR,那么从那里加载清单。例如,

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

答案 1 :(得分:105)

你可以做以下两件事之一:

  1. 调用getResources()并遍历返回的网址集,将其作为清单读取,直至找到您的网址:

    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的实例。大多数Sun类加载器都包括AppletClassLoader。 然后你可以把它投射并调用已经知道的findResource() - 对于applet,至少 - 直接返回所需的清单:

    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
    }
    

答案 2 :(得分:20)

您可以使用Manifests中的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-mf.html

答案 3 :(得分:11)

我认为获取任何bundle的清单(包括加载给定类的bundle)的最合适方法是使用Bundle或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();

请注意,Bundle对象还提供getEntry(String path)来查找特定包中包含的资源,而不是搜索该包的整个类路径。

通常,如果您需要特定于包的信息,请不要依赖关于类加载器的假设,只需直接使用OSGi API。

答案 4 :(得分:8)

以下代码适用于多种类型的存档(jar,war)和多种类型的类加载器(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);
    }
  }

答案 5 :(得分:5)

您可以像这样使用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();
}

答案 6 :(得分:5)

最简单的方法是使用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);

因为在某些情况下...class.getProtectionDomain().getCodeSource().getLocation();会给出vfs:/的路径,所以应该另外处理。

答案 7 :(得分:5)

我会事先承认,这个答案并不能回答原来的问题,即通常能够访问Manifest的问题。但是,如果真正需要的是阅读一些&#34;标准&#34;清单属性,以下解决方案比上面发布的解决方案简单得多。所以我希望主持人允许它。请注意,这个解决方案是在Kotlin中,而不是Java,但我希望Java的端口是微不足道的。 (虽然我承认我不知道Java等同于&#34; .`package`&#34;。

在我的情况下,我想阅读属性&#34; Implementation-Version&#34;所以我从上面给出的解决方案开始获取流,然后读取它以获取值。虽然这个解决方案有效,但是审查我的代码的同事向我展示了一种更简单的方法来完成我想要的工作。请注意,此解决方案在Kotlin中,而不是Java。

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

再次注意,这不能回答原始问题,特别是&#34; Export-package&#34;似乎不是受支持的属性之一。也就是说,有一个myPackage.name返回一个值。也许有人比我更了解这一点,可以评论是否能够恢复原始海报所要求的价值。

答案 8 :(得分:2)

为什么要包含getClassLoader步骤?如果你说“this.getClass()。getResource()”你应该获得相对于调用类的资源。我从来没有使用过ClassLoader.getResource(),但是通过快速浏览一下Java Docs,听起来这样可以获得在任何当前类路径中找到的该名称的第一个资源。

答案 9 :(得分: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
        }
      }
    }
  }

答案 10 :(得分:1)

更简单的方法是使用getPackage()。 例如,要获取实现版本:

Application.class.getPackage().getImplementationVersion()

答案 11 :(得分:0)

我使用过Anthony Juckel的解决方案,但在MANIFEST.MF中,密钥必须以大写字母开头。

所以我的MANIFEST.MF文件包含一个键,如:

Mykey:

然后在激活器或其他类中,您可以使用Anthony的代码来读取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();

答案 12 :(得分:0)

我有这个奇怪的解决方案,可以在嵌入式Jetty服务器中运行war应用程序,但是这些应用程序也需要在标准Tomcat服务器上运行,并且在清单中有一些特殊的属性。

问题在于,在Tomcat中可以读取清单,但是在码头中可以找到随机清单(缺少特殊属性)

基于Alex Konshin的回答,我提出了以下解决方案(然后在Manifest类中使用inputstream):

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