自分のジャーのマニフェストを読む


134

Manifest私のクラスを提供したファイルを読み取る必要がありますが、使用すると:

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

JavaランタイムにMANIFEST最初に.jarロードされたから取得します。
私のアプリはアプレットまたはWebstartから実行される
ので、自分の.jarファイルにアクセスできないと思います。

Felix OSGiを起動したExport-packageから属性を実際に読み取りたい.jarので、これらのパッケージをFelixに公開できます。何か案は?


3
以下のFrameworkUtil.getBundle()の答えが最良だと思います。それはあなたが尋ねたもの(マニフェストを読む)ではなく、あなたが実際にやりたいこと(バンドルのエクスポートを取得する)に答えます。
Chris Dolan 2012

回答:


117

次の2つのいずれかを実行できます。

  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
    }
    

5
パーフェクト!同じ名前のリソースを繰り返し処理できることを知りませんでした。
Houtman、

クラスローダーが単一の.jarファイルのみを認識していることをどのようにして知るのですか?(多くの場合は正しいと思います)問題のクラスに直接関連付けられたものを使用したいです。
Jason S、

7
それはです良い練習作るために別々の代わりに1つの答えで2つの修正などの、それぞれのいずれかの答えを。個別の回答には個別に投票できます。
アルバメンデス

単なるメモ:私は同様のものが必要でしたが、JBossのWAR内にいるため、2番目のアプローチは機能しませんでした。最終的に、stackoverflow.com
Gregor

1
最初の方法ではうまくいきませんでした。62の依存関係jarのマニフェストを取得しましたが、現在のクラスが定義されたものは取得していませんでした...
Jolta

120

最初にクラスの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");

このソリューションは、検索する必要がなく、独自のマニフェストを直接取得するので気に入っています。
ジェイ

1
状態チェックを削除することで少し改善できますclassPath.replace("org/example/MyClass.class", "META-INF/MANIFEST.MF"
Jay

2
誰がストリームを閉じますか?
2013

1
これはgetSimpleName、外部クラス名を削除するため、内部クラスでは機能しません。これは内部クラスで機能しますclazz.getName().replace (".", "/") + ".class"
2013

3
ストリームを閉じる必要がありますが、マニフェストコンストラクタは閉じません。
BrianT。

21

jcabi-manifestsManifestsから使用し、1行だけで使用可能な任意の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


とても素敵なライブラリ。ログレベルを制御する方法はありますか?
アッシリア2013

1
すべてのjcabi libsはSLF4Jを介してログを記録します。ログメッセージは、log4jやlogbackなどの任意の機能を使用してディスパッチできます
yegor256

logback.xmlを使用する場合、追加する必要のある行は次のようになります<logger name="com.jcabi.manifests" level="OFF"/>
ドリフトキャッチャー2015

1
同じクラスローダーの複数のマニフェストが重複し、互いに上書きされる
guai

13

この回答は元の質問、一般的にはマニフェストにアクセスできるという質問には回答しないことを前に認めておきます。ただし、実際に必要なのが多数の「標準」マニフェスト属性の1つを読み取ることである場合、次の解決策は上記の投稿よりもはるかに簡単です。だから私はモデレーターがそれを許可することを願っています。このソリューションはJavaではなくKotlinにありますが、Javaへの移植は簡単であることを期待しています。(Javaで ".`package`"に相当するものを知らないことは認めますが。

私の場合、属性「Implementation-Version」を読み取りたいので、ストリームを取得するために上記のソリューションを開始し、それを読み取って値を取得しました。このソリューションは機能しましたが、私のコードをレビューしている同僚から、私がやりたいことを簡単に行う方法が示されました。このソリューションはJavaではなくKotlinにあることに注意してください。

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

これは元の質問に答えないことに再度注意してください。特に、「Export-package」はサポートされている属性の1つではないようです。つまり、値を返すmyPackage.nameがあります。おそらく、私がこれよりも理解している人が、元の投稿者が要求している値を返すかどうかについてコメントすることができます。


4
実際、Javaのポートは簡単です:String implementationVersion = MyApplication.class.getPackage().getImplementationVersion();
イアン・ロバートソン

事実、これは私が探していたものです。Javaにも同等の機能があることも嬉しいです。
Aleksander Stelmaczonek

12

バンドル(特定のクラスをロードしたバンドルを含む)のマニフェストを取得する最も適切な方法は、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を直接使用してください。


9

次のコードは、複数のタイプのアーカイブ(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);
    }
  }

clz.getResource(resource).toString()バックスラッシュが発生する可能性がありますか?
盆地

9

最も簡単な方法は、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:/これは追加で処理する必要があります。


これは、断然、最も簡単でクリーンな方法です。
walen

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

1
getCodeSource戻るかもしれませんnull。これが機能するための基準は何ですか?ドキュメントには、このことを説明していません。
2013

4
どこDataUtilitiesから輸入していますか?JDKにはないようです。
Jolta、2016年

2

なぜgetClassLoaderステップを含めるのですか?「this.getClass()。getResource()」と言った場合、呼び出し元のクラスに関連するリソースを取得しているはずです。ClassLoader.getResource()を使用したことはありませんが、Javaドキュメントをざっと見てみると、現在のクラスパスで見つかったその名前の最初のリソースを取得できるように思えます。


クラスの名前が「com.mypackage.MyClass」の場合、呼び出しclass.getResource("myresource.txt")はそのリソースをからロードしようとしますcom/mypackage/myresource.txt。マニフェストを取得するためにこのアプローチをどの程度正確に使用しますか?
ChssPly76 09

1
さて、私はバックトラックする必要があります。それがテストではないことです。this.getClass()。getResource( "../../ META-INF / MANIFEST.MF")と言えると思っていました(ただし、パッケージ名を指定するには、多くの ".."が必要です)。これは、ディレクトリ内のクラスファイルに対して機能し、ディレクトリツリーを上に向かって移動します。JARに対しては機能しません。なぜか分かりませんが、それがそうです。this.getClass()。getResource( "/ META-INF / MANIFEST.MF")も機能せず、rt.jarのマニフェストを取得します。(続く...)
ジェイ

実行できることは、getResourceを使用して独自のクラスファイルへのパスを検索し、「!」の後のすべてを削除することです。jarへのパスを取得するには、「/ META-INF / MANIFEST.MF」を追加します。Zhihongが提案したように、私は彼を投票しています。
ジェイ

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

この回答は、マニフェストをロードするための非常に複雑でエラーが発生しやすい方法を使用しています。より簡単な解決策はを使用することcl.getResourceAsStream("META-INF/MANIFEST.MF")です。
ロバート

やってみましたか?クラスパスに複数のjarがある場合、どのjarマニフェストが取得されますか?それはあなたが必要とするものではない最初のものをとります。私のコードはこの問題を解決し、実際に機能します。
Alex Konshin 2017

特定のリソースをロードするためのクラスローダーの使用方法を批判しませんでした。私は間のすべてのコードと指摘したclassLoader.getResource(..)とはurl.openStream()それのように同じことをしようとすると全く無関係とエラーが発生しやすくなりclassLoader.getResourceAsStream(..)ません。
ロバート

いいえ。違います。私のコードは、クラスパスの最初のjarからではなく、クラスが配置されている特定のjarからマニフェストを取得します。
Alex Konshin 2017

あなたの「ジャー特定のコードをロードする」次の2行に相当しますClassLoader classLoader = cl.getClassLoader(); return new Manifest(classLoader.getResourceAsStream("/META-INF/MANIFEST.MF"));
ロバート・

0

私は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();

0

組み込みJettyサーバーでwarアプリケーションを実行するこの奇妙なソリューションがありますが、これらのアプリケーションは標準のTomcatサーバーでも実行する必要があり、manfestにはいくつかの特別なプロパティがあります。

問題は、Tomcatではマニフェストを読み取ることができたが、突堤ではランダムなマニフェストがピックアップされた(特別なプロパティが欠落していた)ことでした。

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;
    }
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.