我需要阅读
Manifest
文件,该文件为我提供了课程,但是当我使用时:
getClass().getClassLoader().getResources(...)
我从加载到 Java 运行时的第一个
MANIFEST
中获取 .jar
。.jar
文件。
我实际上想从开始的
Export-package
读取 .jar
属性
Felix OSGi,这样我就可以将这些包公开给 Felix。有什么想法吗?
您可以执行以下两项操作之一:
调用
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
}
}
您可以尝试检查
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
}
您可以先找到您的课程的 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");
您可以使用 jcabi-manifests
中的
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
我首先承认这个答案并没有回答最初的问题,即通常能够访问清单的问题。但是,如果真正需要的是读取多个“标准”清单属性之一,则以下解决方案比上面发布的解决方案简单得多。所以希望版主允许。请注意,这个解决方案是用 Kotlin 编写的,而不是 Java,但我希望移植到 Java 会很简单。 (虽然我承认我不知道 Java 中“.`package`”的等价物。
就我而言,我想读取属性“Implementation-Version”,因此我从上面给出的解决方案开始获取流,然后读取它以获取值。虽然这个解决方案有效,但审查我的代码的同事向我展示了一种更简单的方法来完成我想要的事情。请注意,此解决方案是用 Kotlin 编写的,而不是 Java 中的。
val myPackage = MyApplication::class.java.`package`
val implementationVersion = myPackage.implementationVersion
再次注意,这并没有回答原来的问题,特别是“Export-package”似乎不是受支持的属性之一。也就是说,有一个 myPackage.name 返回一个值。也许比我更了解这一点的人可以评论这是否返回原始海报所要求的值。
最简单的方法是使用 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:/
的路径,所以这应该另外处理。
以下代码适用于多种类型的档案(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);
}
}
我相信获取任何捆绑包(包括加载给定类的捆绑包)的清单的最合适方法是使用 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。
您可以像这样使用 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();
}
更简单的方法是使用 getPackage()。 例如,要获取实现版本:
Application.class.getPackage().getImplementationVersion()
为什么要包含 getClassLoader 步骤?如果您说“this.getClass().getResource()”,您应该获取与调用类相关的资源。我从未使用过 ClassLoader.getResource(),尽管快速浏览一下 Java 文档,听起来这会让您在任何当前类路径中找到该名称的第一个资源。
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
}
}
}
}
我使用了 Anthony Juckel 的解决方案,但在 MANIFEST.MF 中密钥必须以大写字母开头。
所以我的 MANIFEST.MF 文件包含一个像这样的键:
我的密钥:值
然后在激活器或另一个类中,您可以使用 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();
我有一个奇怪的解决方案,可以在嵌入式 Jetty 服务器中运行 war 应用程序,但这些应用程序还需要在标准 Tomcat 服务器上运行,并且我们在 manfest 中有一些特殊属性。
问题在于,在 Tomcat 中时,可以读取清单,但是在 jetty 中时,会拾取随机清单(缺少特殊属性)
根据 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;
}
}
这个解决方案对我有用:
Package pck=this.getClass().getPackage();
String msgVersion = pck.getImplementationTitle()
+ " version:" + pck.getImplementationVersion()
+ " by " + pck.getImplementationVendor();