问题描述
这就是我要实现的:如果有一个方法a()
调用了方法b()
,我想知道是谁调用了方法b()
。
public void a(){
b();//but it is not necessarily in the same class
}
public void b(){
String method = getCallerMethod();//returns 'a'
}
现在,这可以使用StackWalker
API在Java 9+中有效地实现。在Java 8中,我可以使用Thread.currentThread().getStackTrace()
或new Exception().getStackTrace()
,但是这两种方法都非常慢。我不需要整个堆栈跟踪,只需要堆栈跟踪中的前一帧,只需要该帧中方法的名称(可能还需要类名)即可。
有没有办法在Java 8中有效地实现这一目标?
解决方法
-
在JDK 8中,有一个内部未公开的API,该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)
方法仅返回给定深度的一个堆栈帧。 -
在仅需要调用者类的情况下(没有确切的方法),快速,标准和可移植的解决方案就是
MethodHandles.lookup().lookupClass()
-
最后,如果仅需要调用方方法,则另一种解决方案是使用Bytecode Instrumentation查找调用方法
invoke*
的所有a
字节码,并将其重写为调用方法a_with_caller(String callerMethod)
,其中callerMethod
参数是一个检测时间常数,从所检测的方法派生。
您可以创建一个Exception并使用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
}
}
}
}