读我自己的罐子清单

我需要读取 Manifest文件,它提供了我的类,但是当我使用:

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

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

我实际上想读取 Export-package属性从 .jar开始 这样我就可以把这些软件包暴露给 Felix 有什么办法吗?

140093 次浏览

你可以做两件事:

  1. 调用 getResources()并循环访问返回的 URL 集合,将它们作为清单读取,直到找到您的 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. You can try checking whether getClass().getClassLoader() is an instance of java.net.URLClassLoader. Majority of Sun classloaders are, including AppletClassLoader. You can then cast it and call findResource() which has been known - for applets, at least - to return the needed manifest directly:

    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
    }
    

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

您可以首先找到类的 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");

我相信获取任何 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)来查找包含在特定 Bundle 中的资源,而不是搜索该 Bundle 的整个类路径。

通常,如果您想要特定于捆绑包的信息,不要依赖于关于类加载器的假设,直接使用 OSGi API 即可。

您可以使用 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

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

我已经使用了 Anthony Juckel 的解决方案,但是在 MANIFEST.MF 中,键必须以大写开始。

因此,我的 MANIFEST.MF 文件包含一个类似于:

Mykey: value

然后在 activator 或其他类中,您可以使用来自 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();
  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
}
}
}
}

下面的代码适用于多种类型的归档(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);
}
}

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

我先承认这个答案并没有回答最初的问题,即通常能够访问清单。然而,如果真正需要的是读取一系列“标准”Manifest 属性中的一个,那么下面的解决方案要比上面提到的方案简单得多。所以我希望主持人允许。请注意,这个解决方案是在 Kotlin,而不是 Java,但我认为一个到 Java 的端口是微不足道的。(虽然我承认我不知道 Java 的等价物。“包裹”。

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

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

再次注意,这并没有回答最初的问题,特别是“ Export-package”似乎不是受支持的属性之一。也就是说,有一个返回值的 myPackage.name。也许有人比我更了解这一点,可以评论是否返回的价值,原来的海报是要求。

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

问题在于,在 Tomcat 中,可以读取清单,但是在 Jetty 中,会选择一个随机的清单(这会错过特殊属性)

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

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

Application.class.getPackage().getImplementationVersion()