我可以在运行时确定 Java 库的版本吗?
Can I determine the version of a Java library at runtime?
是否可以在运行时确定第三方Java库的版本?
虽然没有通用标准,但有一种 hack 适用于大多数开源库,或任何通过 Maven 发布插件或兼容机制通过 Maven 存储库发布的内容。由于 JVM 上的大多数其他构建系统都是 Maven 兼容的,这应该适用于通过 Gradle 或 Ivy(可能还有其他)分发的库。
Maven 发布插件(以及所有兼容进程)在发布的 Jar 中创建一个名为 META-INF/${groupId}.${artifactId}/pom.properties
的文件,其中包含属性 groupId
、artifactId
和 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 个 类,一个来自 VAVR, one from Guava,另一个来自 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
第三方Java库是指Jar文件,Jar文件清单有属性专门指定库的版本。
注意:并非所有 Jar 文件都实际指定版本,即使它们应该。
内置Java读取该信息的方式是使用反射,但是你需要知道库中的some class才能查询。哪个 class/interface.
并不重要
例子
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
因为我曾经为很多非常遗留的 Java 项目执行此任务,所以答案是 "it can be done, but how to do it depends"。
首先,检查您的 JAR MANIFEST.MF 文件。有时你会很幸运。
其次,扫描 JAR 文件以查找版本字段。有时你很幸运,有时你的价值会撒谎。
第三,扫描包含的属性文件。有一个常见的 ANT 构建模式将版本保存在 属性 文件中(更新更容易)。
第四步,开始下载该项目可用的 JAR 文件。有时版本号真的丢失了,验证它是特定版本的唯一方法是找到已知的旧版本并进行 JAR 与 JAR 比较。
还有其他技术,但这 4 种几乎涵盖了所有场景。对于一些名字非常糟糕的利基图书馆来说,这可能是一个相当大的挑战。
是否可以在运行时确定第三方Java库的版本?
虽然没有通用标准,但有一种 hack 适用于大多数开源库,或任何通过 Maven 发布插件或兼容机制通过 Maven 存储库发布的内容。由于 JVM 上的大多数其他构建系统都是 Maven 兼容的,这应该适用于通过 Gradle 或 Ivy(可能还有其他)分发的库。
Maven 发布插件(以及所有兼容进程)在发布的 Jar 中创建一个名为 META-INF/${groupId}.${artifactId}/pom.properties
的文件,其中包含属性 groupId
、artifactId
和 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 个 类,一个来自 VAVR, one from Guava,另一个来自 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
第三方Java库是指Jar文件,Jar文件清单有属性专门指定库的版本。
注意:并非所有 Jar 文件都实际指定版本,即使它们应该。
内置Java读取该信息的方式是使用反射,但是你需要知道库中的some class才能查询。哪个 class/interface.
并不重要例子
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
因为我曾经为很多非常遗留的 Java 项目执行此任务,所以答案是 "it can be done, but how to do it depends"。
首先,检查您的 JAR MANIFEST.MF 文件。有时你会很幸运。
其次,扫描 JAR 文件以查找版本字段。有时你很幸运,有时你的价值会撒谎。
第三,扫描包含的属性文件。有一个常见的 ANT 构建模式将版本保存在 属性 文件中(更新更容易)。
第四步,开始下载该项目可用的 JAR 文件。有时版本号真的丢失了,验证它是特定版本的唯一方法是找到已知的旧版本并进行 JAR 与 JAR 比较。
还有其他技术,但这 4 种几乎涵盖了所有场景。对于一些名字非常糟糕的利基图书馆来说,这可能是一个相当大的挑战。