Contents

JAR 文件中获取类名

1. 概述

大多数 Java 库都以JAR 文件 的形式提供。在本教程中,我们将介绍如何从命令行和 Java 程序中获取给定 JAR 文件中类的名称。 然后,我们将查看在运行时从给定 JAR 文件加载类的 Java 程序示例。

2. 示例 JAR 文件

/uploads/jar_file_get_class_names/1.png

3. 使用 jar命令

JDK 附带了一个 jar 命令。我们可以使用带有tf选项的此命令来列出 JAR 文件的内容

$ jar tf stripe-0.0.1-SNAPSHOT.jar 
META-INF/
META-INF/MANIFEST.MF
...
templates/result.html
templates/checkout.html
application.properties
com/blogdemo/stripe/StripeApplication.class
com/blogdemo/stripe/ChargeRequest.class
com/blogdemo/stripe/StripeService.class
com/blogdemo/stripe/ChargeRequest$Currency.class
...

由于我们只对存档中的**.class*文件感兴趣,我们可以使用grep 命令过滤输出:

$ jar tf stripe-0.0.1-SNAPSHOT.jar | grep '\.class$'
com/blogdemo/stripe/StripeApplication.class
com/blogdemo/stripe/ChargeRequest.class
com/blogdemo/stripe/StripeService.class
com/blogdemo/stripe/ChargeRequest$Currency.class
com/blogdemo/stripe/ChargeController.class
com/blogdemo/stripe/CheckoutController.class

这为我们提供了 JAR 文件中的类文件列表。

4. 在 Java 中获取 JAR 文件的类名

使用 jar命令打印 JAR 文件中的类名非常简单。但是,有时我们希望在我们的 Java 程序中从 JAR 文件中加载一些类。在这种情况下,命令行输出是不够的。 为了实现我们的目标,我们需要从 Java 程序扫描 JAR 文件并获取类名。 让我们看看如何使用*JarFile * 和*JarEntry *类从示例 JAR 文件中提取类名:

public static Set<String> getClassNamesFromJarFile(File givenFile) throws IOException {
    Set<String> classNames = new HashSet<>();
    try (JarFile jarFile = new JarFile(givenFile)) {
        Enumeration<JarEntry> e = jarFile.entries();
        while (e.hasMoreElements()) {
            JarEntry jarEntry = e.nextElement();
            if (jarEntry.getName().endsWith(".class")) {
                String className = jarEntry.getName()
                  .replace("/", ".")
                  .replace(".class", "");
                classNames.add(className);
            }
        }
        return classNames;
    }
}

现在,让我们仔细看看上面方法中的代码,了解它是如何工作的:

  • try (JarFile jarFile = new JarFile(givenFile)) – 在这里,我们使用try-with-resources 语句 从给定的File对象中获取jarFile
  • if (jarEntry.getName().endsWith(“.class”)){…} – 我们获取每个类jarEntry,并将类文件的路径更改为合格的类名,例如更改*“package1/package2/SomeType.类”变成“package1.package2.SomeType”*

让我们通过单元测试方法验证该方法是否可以从我们的示例 JAR 文件中提取类名:

private static final String JAR_PATH = "example-jar/stripe-0.0.1-SNAPSHOT.jar";
private static final Set<String> EXPECTED_CLASS_NAMES = Sets.newHashSet(
  "com.blogdemo.stripe.StripeApplication",
  "com.blogdemo.stripe.ChargeRequest",
  "com.blogdemo.stripe.StripeService",
  "com.blogdemo.stripe.ChargeRequest$Currency",
  "com.blogdemo.stripe.ChargeController",
  "com.blogdemo.stripe.CheckoutController");
@Test
public void givenJarFilePath_whenLoadClassNames_thenGetClassNames() throws IOException, URISyntaxException {
    File jarFile = new File(
      Objects.requireNonNull(getClass().getClassLoader().getResource(JAR_PATH)).toURI());
    Set<String> classNames = GetClassNamesFromJar.getClassNamesFromJarFile(jarFile);
    Assert.assertEquals(EXPECTED_CLASS_NAMES, classNames);
}

5. 在 Java 中从 JAR 文件中获取类

我们已经了解了如何从 JAR 文件中获取类名。有时,我们希望在运行时动态地从 JAR 文件中加载一些类。 在这种情况下,我们可以首先使用我们的getClassNamesFromJarFile方法从给定的 JAR 文件中获取类名。 接下来,我们可以创建一个ClassLoader来按名称加载所需的类:

public static Set<Class> getClassesFromJarFile(File jarFile) throws IOException, ClassNotFoundException {
    Set<String> classNames = getClassNamesFromJarFile(jarFile);
    Set<Class> classes = new HashSet<>(classNames.size());
    try (URLClassLoader cl = URLClassLoader.newInstance(
           new URL[] { new URL("jar:file:" + jarFile + "!/") })) {
        for (String name : classNames) {
            Class clazz = cl.loadClass(name); // Load the class by its name
            classes.add(clazz);
        }
    }
    return classes;
}

在上面的方法中,我们创建了一个*URLClassLoader *对象来加载类。实现非常简单。

但是,可能值得稍微解释一下 JAR URL 的语法。一个有效的 JAR URL 包含三个部分:“ jar: + [JAR 文件的位置] + !/”。 结尾的“ !/ ”表示 JAR URL 指的是整个 JAR 文件。 让我们看几个 JAR URL 示例:

jar:http://www.example.com/some_jar_file.jar!/
jar:file:/local/path/to/some_jar_file.jar!/
jar:file:/C:/windows/path/to/some_jar_file.jar!/

在我们的getClassesFromJarFile方法中,JAR 文件位于本地文件系统中,因此,URL 的前缀是“ file: ”。 现在,让我们编写一个测试方法来验证我们的方法是否可以获得所有预期的Class对象:

@Test
public void givenJarFilePath_whenLoadClass_thenGetClassObjects()
  throws IOException, ClassNotFoundException, URISyntaxException {
    File jarFile
      = new File(Objects.requireNonNull(getClass().getClassLoader().getResource(JAR_PATH)).toURI());
    Set<Class> classes = GetClassNamesFromJar.getClassesFromJarFile(jarFile);
    Set<String> names = classes.stream().map(Class::getName).collect(Collectors.toSet());
    Assert.assertEquals(EXPECTED_CLASS_NAMES, names);
}

一旦我们有了所需的Class对象,我们就可以使用Java 反射 来创建类的实例并调用方法。