在 Java 8 中获取调用方方法的有效方法?
Efficient way to get caller method in Java 8?
这就是我想要实现的:如果有一个方法 a()
调用了方法 b()
,我想知道是谁调用了方法 b()
。
public void a(){
b();//but it is not necessarily in the same class
}
public void b(){
String method = getCallerMethod();//returns 'a'
}
现在,这可以在 Java 9+ 中使用 StackWalker
API 高效地实现。在Java 8 中,我可以使用Thread.currentThread().getStackTrace()
或new Exception().getStackTrace()
,但这两种方法都非常慢。我不需要整个堆栈跟踪,我只需要堆栈跟踪中的前一帧,我只需要该帧中的方法名称(可能还有 class 名称)。
有没有办法在 Java 8 中有效地实现这一点?
您可以创建异常并使用 fillInStacktrace()
,然后 printStacktrace()
并粘贴结果。
它的效率可能不是很高,但如果它仅用于调试,我不明白为什么需要如此。
例如(我不在我的电脑前,所以我没有尝试编译它):
try (StringWriter wr = new StringWriter();
PrintWriter pw = new PrintWriter(wr)) {
new Exception().fillInStacktrace().printStacktrace(pw);
try (Scanner sc = new Scanner(wr.toString())) {
int atFound = 0;
while (sc.hasNextLine()) {
String line = sc.nextLine();
if (line.contains("at")) {
atFound++;
}
if (atFound == 2) {
// this should be the caller, first one is this method
}
}
}
}
在 JDK 8 中有内部未记录的 API 提供对单个堆栈跟踪元素的访问而无需解码完整的堆栈跟踪:
SharedSecrets.getJavaLangAccess().getStackTraceDepth(e)
SharedSecrets.getJavaLangAccess().getStackTraceElement(e, index)
这有助于避免解码堆栈跟踪的大量成本,但仍需要收集整个跟踪。有关详细信息,请参阅 this answer。
更快的方法是 JVM TI GetStackTrace 函数。它的 start_depth
和 max_frame_count
参数只允许获取堆栈跟踪的选定部分。
这种方法的缺点是它需要本地库。
我有一个 example 使用 GetStackTrace
几乎可以满足您的需要:StackFrame.getLocation(depth)
方法 returns 在给定的深度只有一个堆栈帧。
在只需要调用者 class 的情况下(没有确切的方法),快速、标准和可移植的解决方案是
MethodHandles.lookup().lookupClass()
最后,如果您只需要调用方法,另一种解决方案是使用 Bytecode Instrumentation 查找调用方法 a
的所有 invoke*
字节码,并重写它们以调用方法 a_with_caller(String callerMethod)
,其中 callerMethod
参数是一个检测时间常数,派生自被检测的方法。
这就是我想要实现的:如果有一个方法 a()
调用了方法 b()
,我想知道是谁调用了方法 b()
。
public void a(){
b();//but it is not necessarily in the same class
}
public void b(){
String method = getCallerMethod();//returns 'a'
}
现在,这可以在 Java 9+ 中使用 StackWalker
API 高效地实现。在Java 8 中,我可以使用Thread.currentThread().getStackTrace()
或new Exception().getStackTrace()
,但这两种方法都非常慢。我不需要整个堆栈跟踪,我只需要堆栈跟踪中的前一帧,我只需要该帧中的方法名称(可能还有 class 名称)。
有没有办法在 Java 8 中有效地实现这一点?
您可以创建异常并使用 fillInStacktrace()
,然后 printStacktrace()
并粘贴结果。
它的效率可能不是很高,但如果它仅用于调试,我不明白为什么需要如此。
例如(我不在我的电脑前,所以我没有尝试编译它):
try (StringWriter wr = new StringWriter();
PrintWriter pw = new PrintWriter(wr)) {
new Exception().fillInStacktrace().printStacktrace(pw);
try (Scanner sc = new Scanner(wr.toString())) {
int atFound = 0;
while (sc.hasNextLine()) {
String line = sc.nextLine();
if (line.contains("at")) {
atFound++;
}
if (atFound == 2) {
// this should be the caller, first one is this method
}
}
}
}
在 JDK 8 中有内部未记录的 API 提供对单个堆栈跟踪元素的访问而无需解码完整的堆栈跟踪:
SharedSecrets.getJavaLangAccess().getStackTraceDepth(e) SharedSecrets.getJavaLangAccess().getStackTraceElement(e, index)
这有助于避免解码堆栈跟踪的大量成本,但仍需要收集整个跟踪。有关详细信息,请参阅 this answer。
更快的方法是 JVM TI GetStackTrace 函数。它的
start_depth
和max_frame_count
参数只允许获取堆栈跟踪的选定部分。这种方法的缺点是它需要本地库。
我有一个 example 使用
GetStackTrace
几乎可以满足您的需要:StackFrame.getLocation(depth)
方法 returns 在给定的深度只有一个堆栈帧。在只需要调用者 class 的情况下(没有确切的方法),快速、标准和可移植的解决方案是
MethodHandles.lookup().lookupClass()
最后,如果您只需要调用方法,另一种解决方案是使用 Bytecode Instrumentation 查找调用方法
a
的所有invoke*
字节码,并重写它们以调用方法a_with_caller(String callerMethod)
,其中callerMethod
参数是一个检测时间常数,派生自被检测的方法。