Can I determine the version of a Java library at runtime?

旧街凉风 提交于 2021-02-06 16:10:39

问题


Is it possible to determine the version of a third party Java library at Runtime?


回答1:


Third party Java library means a Jar file, and the Jar file manifest has properties specifically to specify the version of the library.

Beware: Not all Jar files actually specify the version, even though they should.

Built-in Java way to read that information is to use reflection, but you need to know some class in the library to query. Doesn't really matter which class/interface.

Example

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

Output

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



回答2:


Although there is no universal standard, there is a hack that works for most open source libraries, or anything that is released through a Maven repository through the Maven Release Plugin or compatible mechanisms. Since most other build systems on the JVM are Maven compatible, this should apply to libraries distributed through Gradle or Ivy as well (and possibly others).

The Maven release plugin (and all compatible processes) create a file in the released Jar called META-INF/${groupId}.${artifactId}/pom.properties, which contains the properties groupId, artifactId and version.

By checking for this file and parsing it, we can detect the versions of a majority of library versions out there. Example code (Java 8 or higher):

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

To test this code, I'll try 3 classes, one from VAVR, one from Guava, and one from the 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);
}

Output, on my machine:

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



回答3:


As I used to be tasked doing this for a lot of very legacy Java projects, the answer is "it can be done, but how to do it depends".

First, check your JAR MANIFEST.MF file. Sometimes you get very lucky.

Second, scan the JAR file for a version field. Sometimes you get lucky, and sometimes that value will lie.

Third, scan the included properties files. There was a common ANT build pattern to keep the version in a property file (where it was easier to update).

Fourth, start downloading the available JAR files for that project. Occasionally the version number is truly lost, and the only way to validate it is a specific version is to find a known old version and do a JAR to JAR comparison.

There are other techniques, but these 4 cover nearly all scenarios. For some of the very poorly named niche libraries, it can be quite a challenge.



来源:https://stackoverflow.com/questions/49889510/can-i-determine-the-version-of-a-java-library-at-runtime

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!