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

前端 未结 3 549
南笙
南笙 2021-02-13 23:01

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

相关标签:
3条回答
  • 2021-02-13 23:23

    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
    
    0 讨论(0)
  • 2021-02-13 23:28

    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.

    0 讨论(0)
  • 2021-02-13 23:35

    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
    
    0 讨论(0)
提交回复
热议问题