阅读我自己的 Jar 清单

发布于 2024-08-01 13:27:40 字数 383 浏览 13 评论 0原文

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

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

我从加载到的第一个 .jar 中获取 MANIFEST Java 运行时。
我的应用程序将从小程序或网络启动运行,
所以我想我将无法访问我自己的 .jar 文件。

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

I need to read the Manifest file, which delivered my class, but when I use:

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

I get the MANIFEST from the first .jar loaded into the Java Runtime.
My app will be running from an applet or a webstart,
so I will not have access to my own .jar file, I guess.

I actually want to read the Export-package attribute from the .jar which started
the Felix OSGi, so I can expose those packages to Felix. Any ideas?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(14

纵情客 2024-08-08 13:27:41

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

The easiest way is to use JarURLConnection class :

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

Because in some cases ...class.getProtectionDomain().getCodeSource().getLocation(); gives path with vfs:/, so this should be handled additionally.

流心雨 2024-08-08 13:27:41

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

The following code works with multiple types of archives (jar, war) and multiple types of classloaders (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);
    }
  }
回心转意 2024-08-08 13:27:41

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

I believe the most appropriate way to get the manifest for any bundle (including the bundle which loaded a given class) is to use the Bundle or BundleContext object.

// 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();

Note that the Bundle object also provides getEntry(String path) to look up resources contained within a specific bundle, rather than searching that bundle's entire classpath.

In general, if you want bundle-specific information, do not rely upon assumptions about the classloaders, just use the OSGi APIs directly.

苏大泽ㄣ 2024-08-08 13:27:41

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

Application.class.getPackage().getImplementationVersion()

A simpler way to do this is to use getPackage().
For example, to get Implementation-Version:

Application.class.getPackage().getImplementationVersion()
百合的盛世恋 2024-08-08 13:27:41

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

You can use getProtectionDomain().getCodeSource() like this :

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();
}
尹雨沫 2024-08-08 13:27:41

为什么要包含 getClassLoader 步骤? 如果您说“this.getClass().getResource()”,您应该获取与调用类相关的资源。 我从未使用过 ClassLoader.getResource(),尽管快速浏览一下 Java 文档,这听起来似乎可以让您在任何当前类路径中找到该名称的第一个资源。

Why are you including the getClassLoader step? If you say "this.getClass().getResource()" you should be getting resources relative to the calling class. I've never used ClassLoader.getResource(), though from a quick look at the Java Docs it sounds like that will get you the first resource of that name found in any current classpath.

放赐 2024-08-08 13:27:41
  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
        }
      }
    }
  }
  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
        }
      }
    }
  }
深海蓝天 2024-08-08 13:27:41

这个解决方案对我有用:

Package pck=this.getClass().getPackage();
String msgVersion =  pck.getImplementationTitle()
+ " version:" + pck.getImplementationVersion()
+ " by " + pck.getImplementationVendor();       

This solution works for me :

Package pck=this.getClass().getPackage();
String msgVersion =  pck.getImplementationTitle()
+ " version:" + pck.getImplementationVersion()
+ " by " + pck.getImplementationVendor();       
青衫负雪 2024-08-08 13:27:41

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

所以我的 MANIFEST.MF 文件包含一个像这样的键:

Mykey: value

然后在激活器或另一个类中,您可以使用 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();

I have used the solution from Anthony Juckel but in the MANIFEST.MF the key have to start with uppercase.

So my MANIFEST.MF file contain a key like:

Mykey: value

Then in the activator or another class you can use the code from Anthony to read the MANIFEST.MF file and the the value that you need.

// 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();
爱你不解释 2024-08-08 13:27:41

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

问题是,在 Tomcat 中时,可以读取清单,但是在 jetty 中时,会拾取随机清单(错过了特殊属性)。

根据 Alex Konshin 的回答,我提出了以下解决方案(输入流是在清单类中使用):

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

I have this weird solution that runs war applications in a embedded Jetty server but these apps need also to run on standard Tomcat servers, and we have some special properties in the manfest.

The problem was that when in Tomcat, the manifest could be read, but when in jetty, a random manifest was picked up (which missed the special properties)

Based on Alex Konshin's answer, I came up with the following solution (the inputstream is then used in a Manifest class):

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;
    }
}
别想她 2024-08-08 13:27:40

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

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

     枚举   资源 = getClass().getClassLoader() 
         .getResources("META-INF/MANIFEST.MF"); 
       while (resources.hasMoreElements()) { 
           尝试 { 
             清单清单 = new Manifest(resources.nextElement().openStream()); 
             // 如果上面的行导致 ;   清单属性尝试从 JarInputStream 尝试: 
             // 清单清单 = resources.nextElement().openStream().getManifest(); 
    
             // 检查这是否是您的清单并执行您需要的操作或获取下一个清单 
             ... 
           } catch (IOException E) { 
             // 处理 
           } 
       } 
      
  2. 您可以尝试检查 getClass().getClassLoader() 是否有效java.net.URLClassLoader 的一个实例。 大多数 Sun 类加载器都是,包括 AppletClassLoader。
    然后,您可以转换它并调用已知的 findResource()(至少对于小程序而言),以直接返回所需的清单:

     URLClassLoader cl = (URLClassLoader) getClass().getClassLoader(); 
       尝试 { 
         URL url = cl.findResource("META-INF/MANIFEST.MF"); 
         清单清单 = new Manifest(url.openStream()); 
         // 用它做一些事情 
         ... 
       } catch (IOException E) { 
         // 处理 
       } 
      

You can do one of two things:

  1. Call getResources() and iterate through the returned collection of URLs, reading them as manifests until you find yours:

     Enumeration<URL> resources = getClass().getClassLoader()
       .getResources("META-INF/MANIFEST.MF");
     while (resources.hasMoreElements()) {
         try {
           Manifest manifest = new Manifest(resources.nextElement().openStream());
           // If the line above leads to <null> manifest Attributes try from JarInputStream:
           // Manifest manifest = resources.nextElement().openStream().getManifest();
    
           // 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
     }
    
人疚 2024-08-08 13:27:40

您可以先找到您的课程的 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());
// If the line above leads to <null> manifest Attributes try instead from JarInputStream:
//Manifest manifest = new URL(manifestPath).openStream().getManifest();
Attributes attr = manifest.getMainAttributes();
String value = attr.getValue("Manifest-Version");

You can find the URL for your class first. If it's a JAR, then you load the manifest from there. For example,

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());
// If the line above leads to <null> manifest Attributes try instead from JarInputStream:
//Manifest manifest = new URL(manifestPath).openStream().getManifest();
Attributes attr = manifest.getMainAttributes();
String value = attr.getValue("Manifest-Version");
妄想挽回 2024-08-08 13:27:40

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

在我的例子中,我想读取属性“Implementation-Version”,所以我从上面给出的解决方案开始获取流,然后读取它当这个解决方案有效时,一位同事审查了我的代码,并向我展示了一种更简单的方法来完成我想要的事情,请注意,这个解决方案是用 Kotlin 编写的,而不是 Java 中的

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

。特定的“Export-package”似乎不是受支持的属性之一也就是说,有一个 myPackage.name 返回一个值,也许比我更了解这一点的人可以评论它是否返回原始海报的值。正在请求。

I will admit up front that this answer does not answer the original question, that of generally being able to access the Manifest. However if what is really required is to read one of a number of "standard" Manifest attributes, the following solution is much simpler than those posted above. Note that this solution is in Kotlin, not Java, but I would expect that a port to Java would be trivial. (Although I admit I don't know the Java equivalent of ".`package`".

In my case I wanted to read the attribute "Implementation-Version" so I started with the solutions given above to obtain the stream and then read it to obtain the value. While this solution worked, a coworker reviewing my code showed me an easier way to do what I wanted. Note that this solution is in Kotlin, not Java.

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

Once again note that this does not answer the original question, in particular "Export-package" does not seem to be one of the supported attributes. That said, there is a myPackage.name that returns a value. Perhaps someone who understands this more than I can comment on whether that returns the value the original poster is requesting.

绮筵 2024-08-08 13:27:40

您可以使用 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/如何阅读-manifest-mf.html

You can use Manifests from jcabi-manifests and read any attribute from any of available MANIFEST.MF files with just one line:

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

The only dependency you need is:

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

Also, see this blog post for more details: http://www.yegor256.com/2014/07/03/how-to-read-manifest-mf.html

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文