如何从java.lang.Class对象获取源文件名/行号 [英] How to get source file-name/line-number from a java.lang.Class object

查看:131
本文介绍了如何从java.lang.Class对象获取源文件名/行号的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在给定 java.lang.Class 对象的情况下,是否有可能获取源文件名和声明类的行号?

Is it possible, given a java.lang.Class object, to get the source file name and the line number at which the class was declared?

数据应该在 .class 文件的调试信息中提供。我知道的唯一一个JDK返回此类调试信息的地方是 java.lang.StackTraceElement 但是我不确定是否可以强制Java创建 java.lang.StackTraceElement 任意类的实例,因为我们没有在类中执行方法。

The data should be available in the .class file's debug info. The only place I know of, where the JDK returns such debug info is in java.lang.StackTraceElement but I'm not sure if it's possible to force Java to create a java.lang.StackTraceElement instance for an arbitrary class, because we are not executing a method in the class.

My确切用例是一个匿名内部类,它具有编译器生成的名称。我想知道类声明的文件名和行号。

My exact use case is an anonymous inner class which has a compiler generated name. I want to know the file name and the line number for the class declaration.

我不想使用字节码操作框架,但我可以回到它如果必须的话。

I prefer not to use a byte-code manipulation framework, but I can fall back to it if I have to.

推荐答案

这个问题的答案取决于你对实现监听器的代码有多少控制权。你是对的,不能在一个方法中创建一个堆栈跟踪。

The answer to this comes down to how much control you have over the code that is implementing the Listener. You are right that it is not possible to create a stacktrace without being in a method.

一般的技巧是在构造函数中创建一个Exception(),但是不要扔掉它。它包含stacktrace信息,您可以按照自己的方式使用它。这将为您提供构造函数的行号,但不包含类的行号。请注意,此方法也不是特别有效,因为创建堆栈跟踪非常昂贵。

The general technique is to create an Exception(), in the constructor, but don't throw it. This contains the stacktrace information, which you can use how you want. This will give you the line number of the constructor, but not of the class. Please note that this method is not particularly performant either, because creating a stacktrace is expensive.

您需要:


  1. 强制在构造函数中执行一些代码(如果你的Listener是你控制的抽象类,则相对容易)

  2. 以某种方式检测代码(治愈似乎比疾病更糟糕了。)

  3. 对类的命名方式做一些假设。

  4. 读取jar(做同样的事情)作为javac -p)

  1. force some code to be executed in the constructor (relatively easy if your Listener is an abstract class which you control)
  2. Instrument the code somehow (the cure seems worse than the disease here).
  3. Make some assumptions about the way classes are named.
  4. Read the jar (do the same thing as javac -p)

对于1),你只需将Exception创建放在抽象类中,然后调用构造函数由子类:

For 1), you'd simply put the Exception creation in the abstract class, and the constructor gets called by the subclass:

class Top {
    Top() {
        new Exception().printStackTrace(System.out);
    }
}

class Bottom extends Top {
    public static void main(String[] args) {
        new Bottom();
    }
}

这会产生类似于:

java.lang.Exception
    at uk.co.farwell.stackoverflow.Top.<init>(Top.java:4)
    at uk.co.farwell.stackoverflow.Bottom.<init>(Bottom.java: 11)
    at uk.co.farwell.stackoverflow.Bottom.main(Bottom.java: 18)

通常,遵循一些命名规则:如果你有一个名为Actor的外部类和一​​个内部调用Consumer,然后编译的类将被称为Actor $ Consumer。匿名内部类按它们在文件中出现的顺序命名,因此Actor $ 1将出现在Actor $ 2之前的文件中。我不认为这实际上是在任何地方指定的,所以这可能只是一个约定,如果你正在做任何复杂的多个jvms等,不应该依赖它。

In general, there are some naming rules which are followed: If you have an outer class called Actor and an inner called Consumer, then the compiled class will be called Actor$Consumer. Anonymous inner classes are named in the order in which they appear in the file, so Actor$1 will appear in the file before Actor$2. I don't think this is actually specified anywhere, so this is probably just a convention, and shouldn't be relied upon if you're doing anything sophisticated with multiple jvms etc.

正如jmg所指出的那样,你可以在同一个文件中定义多个顶级类。如果你有一个公共类Foo,这必须在Foo.java中定义,但非公共类可以包含在另一个文件中。上面的方法将解决这个问题。

It is possible, as jmg pointed out, that you can define multiple top level classes in the same file. If you have a public class Foo, this must be defined in Foo.java, but a non-public class can be included in another file. The above method will cope with this.

说明:

如果你反汇编java(javap -c - verbose),你会看到调试信息中有行号,但它们只适用于方法。使用以下内部类:

If you disassemble the java (javap -c -verbose), you'll see that there are line numbers in the debug information, but they only apply to methods. Using the following inner class:

static class Consumer implements Runnable {
    public void run() {
        // stuff
    }
}

并且javap输出包含:

and the javap output contains:

uk.co.farwell.stackoverflow.Actors$Consumer();
  Code:
   Stack=1, Locals=1, Args_size=1
   0:   aload_0
   1:   invokespecial   #10; //Method java/lang/Object."<init>":()V
   4:   return
  LineNumberTable: 
   line 20: 0

  LocalVariableTable: 
   Start  Length  Slot  Name   Signature
   0      5      0    this       Luk/co/farwell/stackoverflow/Actors$Consumer;

LineNumberTable包含适用于方法的行号列表。所以我的Consumer构造函数从第20行开始。但这是构造函数的第一行,而不是类的第一行。它只是同一行,因为我使用的是默认构造函数。如果我添加一个构造函数,那么行号将会改变。编译器不存储声明该类的行。因此,如果不解析java本身,就无法找到声明类的位置。您根本没有可用的信息。

The LineNumberTable contains the list of line numbers which apply to a method. So my constructor for the Consumer starts at line 20. But this is the first line of the constructor, not the first line of the class. It is only the same line because I'm using the default constructor. If I add a constructor, then the line numbers will change. the compiler does not store the line that the class is declared on. So you can't find where the class is declared without parsing the java itself. You simply don't have the information available.

但是,如果您使用的是匿名内部类,例如:

However, if you're using an anonymous inner class such as:

Runnable run = new Runnable() {
    public void run() {
    }
};

然后构造函数和类的行号将匹配[*],所以这会给你一行数字。

Then the line number of the constructor and class will match[*], so this gives you an line number.

[*]除非new和Runnable()在不同的行上。

[*] Except if the "new" and "Runnable()" are on different lines.

这篇关于如何从java.lang.Class对象获取源文件名/行号的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆