1. 概述

大多数 Java 库都以 JAR 文件 的形式提供。在本教程中,我们将介绍如何从命令行和 Java 程序中获取指定 JAR 文件内的类名。

接着,我们还会通过一个示例程序演示如何在运行时从 JAR 文件中加载类。

2. 示例 JAR 文件

本文将以 stripe-0.0.1-SNAPSHOT.jar 为例,展示如何获取 JAR 文件中的类名:

a jar example 1

3. 使用 jar 命令

JDK 自带了 jar 命令。我们可以使用 jar 命令配合 -t-f 参数来列出 JAR 文件中的内容:

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

⚠️ 如果我们只关心 JAR 文件中的 *.class 文件,可以使用 grep 过滤输出:

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

这样就能得到一个干净的类文件列表。

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

虽然用 jar 命令查看类名很简单粗暴,但在某些场景下我们需要在 Java 程序中动态获取类名,这时命令行的输出就不够用了。

✅ 正确姿势是:在 Java 程序中扫描 JAR 文件并提取类名

来看一个使用 JarFileJarEntry 的示例:

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 自动管理资源。
  • if (jarEntry.getName().endsWith(".class")):筛选出 .class 文件。
  • replace("/", ".").replace(".class", ""):将路径转换为标准类名格式,如将 package1/package2/SomeType.class 转为 package1.package2.SomeType

✅ 单元测试验证

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.baeldung.stripe.StripeApplication",
  "com.baeldung.stripe.ChargeRequest",
  "com.baeldung.stripe.StripeService",
  "com.baeldung.stripe.ChargeRequest$Currency",
  "com.baeldung.stripe.ChargeController",
  "com.baeldung.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. 从 JAR 文件中加载类

有时候我们不只是想获取类名,还想在运行时动态加载类。

✅ 实现思路:

  1. 先用 getClassNamesFromJarFile 获取类名。
  2. 然后使用 URLClassLoader 加载类。
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); // 根据类名加载类
            classes.add(clazz);
        }
    }
    return classes;
}

🧠 JAR URL 语法小知识

JAR URL 的结构为:jar: + [文件路径] + !/

例如:

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!/

在我们的例子中,JAR 文件在本地,所以用的是 file: 协议。

✅ 测试加载类对象

@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 反射 来创建实例、调用方法等操作。

6. 总结

在这篇文章中,我们学习了两种获取 JAR 文件中类名的方法:

✅ 使用 jar 命令适合快速查看类是否存在,比如排查依赖问题。

✅ 使用 JarFileJarEntry 则适合在程序运行时动态获取类信息。

最后我们还演示了如何在运行时从 JAR 文件中加载类,这在插件化、模块化开发中非常常见。

完整代码见 GitHub:https://github.com/eugenp/tutorials/tree/master/core-java-modules/core-java-jar


原始标题:Get Names of Classes Inside a JAR File