web-dev-qa-db-ja.com

実行時にJavaライブラリのバージョンを判別できますか?

実行時にサードパーティのJavaライブラリのバージョンを判別することは可能ですか?

8

サードパーティJavaライブラリはJarファイルを意味し、Jarファイルマニフェストにはライブラリのバージョンを指定するためのプロパティがあります。

注意:shouldであっても、すべてのJarファイルが実際にバージョンを指定しているわけではありません。

組み込みJavaその情報を読み取る方法はリフレクションを使用することですが、クエリを実行するにはライブラリ内のsomeクラスを知っている必要があります。どのクラスでもかまいません/インターフェース。

public class Test {
    public static void main(String[] args) {
        printVersion(org.Apache.http.client.HttpClient.class);
        printVersion(com.fasterxml.jackson.databind.ObjectMapper.class);
        printVersion(com.google.gson.Gson.class);
    }
    public static void printVersion(Class<?> clazz) {
        Package p = clazz.getPackage();
        System.out.printf("%s%n  Title: %s%n  Version: %s%n  Vendor: %s%n",
                          clazz.getName(),
                          p.getImplementationTitle(),
                          p.getImplementationVersion(),
                          p.getImplementationVendor());
    }
}

出力

org.Apache.http.client.HttpClient
  Title: HttpComponents Apache HttpClient
  Version: 4.3.6
  Vendor: The Apache Software Foundation
com.fasterxml.jackson.databind.ObjectMapper
  Title: jackson-databind
  Version: 2.7.0
  Vendor: FasterXML
com.google.gson.Gson
  Title: null
  Version: null
  Vendor: null
14
Andreas

普遍的な標準はありませんが、ほとんどのオープンソースライブラリ、またはMavenリリースプラグインまたは互換性のあるメカニズムを介してMavenリポジトリからリリースされるものすべてに機能するハックがあります。 JVM上の他のほとんどのビルドシステムはMavenと互換性があるため、これはGradleまたはIvy(および場合によっては他のライブラリ)を介して配布されるライブラリにも適用されます。

Mavenリリースプラグイン(および互換性のあるすべてのプロセス)は、リリースされたJarにMETA-INF/${groupId}.${artifactId}/pom.propertiesというファイルを作成します。このファイルには、プロパティgroupIdartifactId、およびversionが含まれています。

このファイルをチェックして解析することで、ライブラリバージョンの大部分のバージョンを検出できます。サンプルコード(Java 8以降):

/**
 * Reads a library's version if the library contains a Maven pom.properties
 * file. You probably want to cache the output or write it to a constant.
 *
 * @param referenceClass any class from the library to check
 * @return an Optional containing the version String, if present
 */
public static Optional<String> extractVersion(
    final Class<?> referenceClass) {
    return Optional.ofNullable(referenceClass)
                   .map(cls -> unthrow(cls::getProtectionDomain))
                   .map(ProtectionDomain::getCodeSource)
                   .map(CodeSource::getLocation)
                   .map(url -> unthrow(url::openStream))
                   .map(is -> unthrow(() -> new JarInputStream(is)))
                   .map(jis -> readPomProperties(jis, referenceClass))
                   .map(props -> props.getProperty("version"));
}

/**
 * Locate the pom.properties file in the Jar, if present, and return a
 * Properties object representing the properties in that file.
 *
 * @param jarInputStream the jar stream to read from
 * @param referenceClass the reference class, whose ClassLoader we'll be
 * using
 * @return the Properties object, if present, otherwise null
 */
private static Properties readPomProperties(
    final JarInputStream jarInputStream,
    final Class<?> referenceClass) {

    try {
        JarEntry jarEntry;
        while ((jarEntry = jarInputStream.getNextJarEntry()) != null) {
            String entryName = jarEntry.getName();
            if (entryName.startsWith("META-INF")
                && entryName.endsWith("pom.properties")) {

                Properties properties = new Properties();
                ClassLoader classLoader = referenceClass.getClassLoader();
                properties.load(classLoader.getResourceAsStream(entryName));
                return properties;
            }
        }
    } catch (IOException ignored) { }
    return null;
}

/**
 * Wrap a Callable with code that returns null when an exception occurs, so
 * it can be used in an Optional.map() chain.
 */
private static <T> T unthrow(final Callable<T> code) {
    try {
        return code.call();
    } catch (Exception ignored) { return null; }
}

このコードをテストするために、3つのクラスを試してみます。1つは [〜#〜] vavr [〜#〜] 、1つは Guava から、もう1つはJDKから。

public static void main(String[] args) {
    Stream.of(io.vavr.collection.LinkedHashMultimap.class,
              com.google.common.collect.LinkedHashMultimap.class,
              Java.util.LinkedHashMap.class)
          .map(VersionExtractor::extractVersion)
          .forEach(System.out::println);
}

私のマシンでの出力:

Optional[0.9.2]
Optional[24.1-jre]
Optional.empty
5

私は多くの非常にレガシーなJavaプロジェクトでこれを行うことを任されていたので、答えは「それは可能ですが、それを行う方法は異なります」です。

まず、JARMANIFEST.MFファイルを確認します。時々あなたはとても幸運になります。

次に、JARファイルをスキャンしてバージョンフィールドを探します。運が良ければ、その価値がうそをつくこともあります。

第三に、含まれているプロパティファイルをスキャンします。バージョンをプロパティファイルに保持するための一般的なANTビルドパターンがありました(更新が簡単でした)。

第4に、そのプロジェクトで使用可能なJARファイルのダウンロードを開始します。時折、バージョン番号が本当に失われることがあり、それを検証する唯一の方法は、既知の古いバージョンを見つけて、JARとJARの比較を行うことです。

他のテクニックもありますが、これら4つはほぼすべてのシナリオをカバーしています。非常に貧弱な名前のニッチライブラリのいくつかにとって、それはかなりの挑戦になる可能性があります。

1
Edwin Buck