【问题标题】:Get source code of any class from within a Java program从 Java 程序中获取任何类的源代码
【发布时间】:2018-12-12 18:16:53
【问题描述】:

我正在尝试从 Java 程序中检索任何类(如果可用)的源代码以进行调试。假设我有 Class[_] 的引用,我想检索它的源代码。

到目前为止我所做的尝试 - 在 Scala 中:

val clazz = classOf[ClassDefinedInSeparateFile]

  1. clazz.getProtectionDomain.getCodeSource.getLocation.toString + "/" + clazz.getPackage.getName.replaceAll("\\.","/") + "/" + clazz.getSimpleName + ".scala" - 看起来不错,JAR 在那里并包含 .scala 文件,但无法使用 Source.fromFile(...) 打开。
  2. "/" + clazz.getPackage.getName.replaceAll("\\.","/") + "/" + clazz.getSimpleName + ".scala" - 看起来不错,但无法使用 Source.fromInputStream(...) 打开

备注:

  • 生产或暂存环境中没有可用的 IDE。
  • 在我们的设置中,JAR 包含源代码.java.scala 文件,因此不需要反编译器。 (至少对于应用程序的源代码,而不是依赖项。如果可以访问应用程序源代码的 sn-p,那就足够了 - 大多数异常都在应用程序级别捕获并在那里相关。)

谢谢。

【问题讨论】:

  • 你不能只使用 IDE 吗?
  • 对于这个用例,不幸的是,它不是一个选项。在生产或登台环境中,大多数时候都没有可用的 GUI。
  • 从什么时候开始“大多数 JAR 包含源代码 .java 或 .scala 文件”?谁或什么应该把它们放在那里?我认为您应该更详细地描述您的构建过程。
  • 好吧,在我们的例子中,它们至少包含主程序代码——而不是依赖项——,因此在主程序源捕获异常时,可以打印出相应的代码 sn-p。构建过程可能无关紧要,但它也会包含源文件,只是碰巧作为资源它们无法读取。可能遗漏了什么……
  • 我根据您的建议改进了我的问题。

标签: java scala class jar


【解决方案1】:

如果源在 jar 中,在类路径中,您需要找出它的确切位置。

clazz.getName.replaceAll("\\.", "/") + ".scala" 是一个不错的猜测,但是:(1) 源代码可能与类不在同一个位置 - 可能有前缀(如 src/ 或其他),或者它甚至可以在不同的 jar 中,并且 (2) scala 类不必在具有相同名称的文件中 - 您可以在一个文件中包含多个类,该文件可以称为 foo.scala,生成一些类即时等。此外,包并不总是 scala 中的目录(例如,它可能是包对象)。

如果你知道 jar 中的位置(并且 jar 在类路径中),打开它的方法是:clazz.getClassLoader.getResourceAsStream ... 但是,就像我上面所说的,诀窍是找出位置。这并不容易(并且没有单一的标准方法来做到这一点)。

您最好的选择确实是使用 IDE。我了解您在生产环境中没有它,但您并不真正需要它。您需要的是在您拥有 IDE 的某些机器上可用的生产源代码,您可以通过简单的scp 命令来实现。

【讨论】:

  • 我觉得"/" + clazz.getPackage.getName.replaceAll("\\.","/") + "/" + clazz.getSimpleName + ".scala"比较准确,虽然JAR在classpath里,但是找不到资源。我已经检查过该文件位于正确猜测的位置。不是只能这样打开“资源”吗?你能重现它吗?
  • 我仍然无法重现它。我可以在类路径中看到带有源的 JAR,并且 .scala 文件位于路径 /org/whatever/Class.scala 上,但 getClassLoader.getResourceAsStream("/org/whatever/Class.scala") 会引发 NPE。
【解决方案2】:

如果我只是将源代码与类放在同一个文件夹中,那么下面的代码对于类路径和 jar 中的代码都可以正常工作

请注意,没有理由在名称前加上“/”,但您应该扫描顶级类。

我为它在核心 java 中而道歉,但我不想添加任何额外的依赖项并希望尽可能清楚。

package com.stackoverflow.q53749060;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UncheckedIOException;
import java.util.Arrays;
import java.util.stream.Stream;

import org.junit.Test;

import com.stackoverflow.q53749060.Answer.Result.Found;
import com.stackoverflow.q53749060.Answer.Result.NotFound;
import com.stackoverflow.q53749060.MyTopLevelClass.MyNestedClass;
import com.stackoverflow.q53749060.MyTopLevelClassInAnotherJar.MyNestedClassInAnotherJar;

@SuppressWarnings("javadoc")
public class Answer {

    static final String[] EXTENSIONS = { "java", "scala" };

    @Test
    public void test() {

        Arrays.stream(EXTENSIONS)
            .flatMap(ext -> toSource(ext, MyTopLevelClass.class, MyNestedClass.class,MyTopLevelClassInAnotherJar.class,MyNestedClassInAnotherJar.class, String.class))
            .forEach(System.out::println);

    }

    public Stream<Result> toSource(final String extension, final Class<?>... classes) {

        return Arrays.stream(classes)
            .map(clazz -> toSource(extension, clazz));
    }

    public Result toSource(final String extension, final Class<?> clazz) {

        Class<?> topLevelClass = clazz;

        while (topLevelClass.getEnclosingClass() != null) {
            topLevelClass = topLevelClass.getEnclosingClass();
        }

        final String name = topLevelClass.getName()
            .replaceAll("\\.", "/") + "." + extension;

        final Thread currentThread = Thread.currentThread();

        final ClassLoader contextClassLoader = currentThread.getContextClassLoader();

        if (contextClassLoader.getResource(name) == null) {
            return new NotFound(clazz);
        }

        final String source = toSource(name, contextClassLoader);

        return new Found(clazz, name, source);
    }

    public String toSource(final String name, final ClassLoader contextClassLoader) {

        try (final InputStream resourceInputStream = contextClassLoader.getResourceAsStream(name);
                final ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream()) {

            int length;
            byte[] data = new byte[1024];

            while ((length = resourceInputStream.read(data, 0, data.length)) != -1) {
                byteArrayOutputStream.write(data, 0, length);
            }

            byteArrayOutputStream.flush();

            byte[] byteArray = byteArrayOutputStream.toByteArray();

            return new String(byteArray);

        } catch (IOException ioe) {
            throw new UncheckedIOException("Failed to read source file: " + name, ioe);
        }
    }

    static class Result {

        final Class<?> clazz;

        Result(Class<?> clazz) {
            super();
            this.clazz = clazz;
        }

        @Override
        public int hashCode() {
            final int prime = 31;
            int result = 1;
            result = prime * result + ((this.clazz == null) ? 0 : this.clazz.hashCode());
            return result;
        }

        @Override
        public boolean equals(Object obj) {
            if (this == obj) {
                return true;
            }
            if (obj == null) {
                return false;
            }
            if (getClass() != obj.getClass()) {
                return false;
            }
            Result other = (Result) obj;
            if (this.clazz == null) {
                if (other.clazz != null) {
                    return false;
                }
            } else if (!this.clazz.equals(other.clazz)) {
                return false;
            }
            return true;
        }

        @Override
        public String toString() {
            return "Result [clazz=" + this.clazz + "]";
        }

        static class Found extends Result {

            final String source;

            final String path;

            Found(Class<?> clazz, String path, String source) {
                super(clazz);
                this.path = path;
                this.source = source;
            }

            @Override
            public int hashCode() {
                final int prime = 31;
                int result = super.hashCode();
                result = prime * result + ((this.source == null) ? 0 : this.source.hashCode());
                return result;
            }

            @Override
            public boolean equals(Object obj) {
                if (this == obj) {
                    return true;
                }
                if (!super.equals(obj)) {
                    return false;
                }
                if (getClass() != obj.getClass()) {
                    return false;
                }
                Found other = (Found) obj;
                if (this.source == null) {
                    if (other.source != null) {
                        return false;
                    }
                } else if (!this.source.equals(other.source)) {
                    return false;
                }
                return true;
            }

            @Override
            public String toString() {
                return "Found [source=" + this.source + ", clazz=" + this.clazz + "]";
            }

        }

        static class NotFound extends Result {

            NotFound(Class<?> clazz) {
                super(clazz);

            }

            @Override
            public String toString() {
                return "NotFound [clazz=" + this.clazz + "]";
            }

        }
    }
}

【讨论】:

    【解决方案3】:

    如果你想获得类似于 Java 中原始源代码的东西,你需要一个 Java Decompiler。

    您将无法访问源代码(您的可执行程序由 Java bytecode 组成,而不是 Java 源代码)。

    这是我的favorite Java Decompiler的链接。

    【讨论】:

    • 大多数 JAR 包含源代码,因此并不总是需要反编译器。
    • 一些 JAR 包含源代码。查找哪个JAR有源代码,然后在JAR中查找源代码就变成了另外两个问题。
    • 我认为大多数 JAR包含源代码。 :-)
    • 感谢您的评论,我根据您的建议更新了我的问题。
    猜你喜欢
    • 2011-03-12
    • 2018-06-30
    • 2020-10-16
    • 2015-09-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多