146

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

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

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

我实际上想Export-package从启动 Felix OSGi 的 中读取属性.jar,因此我可以将这些包公开给 Felix。有任何想法吗?

4

13 回答 13

125

您可以执行以下两项操作之一:

  1. 调用getResources()并遍历返回的 URL 集合,将它们作为清单读取,直到找到你的:

    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()已知的 - 至少对于小程序 - 直接返回所需的清单:

    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
    }
    
于 2009-08-13T16:46:28.060 回答
122

您可以先找到课程的 URL。如果它是 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");
于 2009-08-13T17:27:02.420 回答
21

您可以使用jcabi-manifestsManifests并从任何可用的 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

于 2012-12-30T11:02:19.353 回答
18

我会预先承认,这个答案并没有回答最初的问题,即通常能够访问清单的问题。但是,如果真正需要的是读取多个“标准”清单属性之一,则以下解决方案比上面发布的解决方案要简单得多。所以希望版主允许。请注意,此解决方案使用 Kotlin,而不是 Java,但我希望移植到 Java 将是微不足道的。(虽然我承认我不知道“.`package`”的Java等价物。

就我而言,我想读取属性“Implementation-Version”,所以我从上面给出的解决方案开始获取流,然后读取它以获取值。虽然这个解决方案有效,但审查我的代码的同事向我展示了一种更简单的方法来做我想做的事。请注意,此解决方案使用 Kotlin,而不是 Java。

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

再次注意,这并不能回答原始问题,特别是“导出包”似乎不是受支持的属性之一。也就是说,有一个返回值的 myPackage.name。也许比我更了解这一点的人可以评论这是否会返回原始发布者要求的值。

于 2018-03-23T18:24:13.253 回答
13

最简单的方法是使用 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:/,所以这应该另外处理。

于 2016-02-17T11:47:47.203 回答
11

我相信获取任何包(包括加载给定类的包)的清单的最合适方法是使用 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。

于 2009-08-16T05:15:43.103 回答
11

以下代码适用于多种类型的档案(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);
    }
  }
于 2015-03-17T15:23:15.137 回答
6

您可以像这样使用 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();
}
于 2013-03-29T09:12:23.087 回答
3

一个更简单的方法是使用 getPackage()。例如,要获得实施版本:

Application.class.getPackage().getImplementationVersion()
于 2020-11-18T05:45:34.373 回答
1

为什么要包括 getClassLoader 步骤?如果你说“this.getClass().getResource()”,你应该得到与调用类相关的资源。我从来没有使用过 ClassLoader.getResource(),尽管快速浏览一下 Java 文档,听起来这会让你在任何当前的类路径中找到第一个同名的资源。

于 2009-08-13T16:48:57.507 回答
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
        }
      }
    }
  }
于 2014-05-29T17:39:11.290 回答
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();
于 2014-01-02T18:12:01.603 回答
0

我有一个奇怪的解决方案,它在嵌入式 Jetty 服务器中运行战争应用程序,但这些应用程序还需要在标准 Tomcat 服务器上运行,并且我们在 manfest 中有一些特殊属性。

问题是在 Tomcat 中,可以读取清单,但是在码头中,随机清单被拾取(错过了特殊属性)

根据 Alex Konshin 的回答,我提出了以下解决方案(输入流随后用于 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;
    }
}
于 2018-08-03T16:30:49.887 回答