java小技巧之获取调用来源

这个话题应该首先明确,这不是类似于 open tracing 的应用间调用路径,而是应用内部,举个例子,我有一个底层方法,比如DAO层的一个方法,有很多调用方,那么有什么方法,第一种是最简单,但是比较麻烦,我去每个调用方那打个日志,然后分析下日志,这个办法肯定是没错的,只是如果我的调用方很多,可能需要加日志的地方就很多了,会比较麻烦
第二种是我可以在这个被调用的DAO层方法里加个日志,但是要怎么获取调用方呢,这是我们可以用代码堆栈trace来实现,这个思路可以从异常的角度出发,我们在出现异常的时候,如何定位异常的代码,就是通过异常堆栈,那么堆栈是不是只有在抛异常的时候才有呢,答案是否定的,了解一点java虚拟机的应该知道我们在内存中有一个区域放的就是我们的代码调用栈,用来保存我们目前程序的调用层次逻辑以及数据,之前在php中也讲到过这个,那时候也是帮助理清调用逻辑和调用来源,
在java中如何使用这个功能呢,

1
2
3
4
5
6
7
8
9
10
11
12
public class StackTrace {
public static void main(String[] args) {
method1();
}
public static void method1() {
StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
for (StackTraceElement stackTraceElement : stackTrace) {
System.out.println(stackTraceElement);
}
}

}

这个方法其实也比较简单

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public StackTraceElement[] getStackTrace() {
if (this != Thread.currentThread()) {
// check for getStackTrace permission
SecurityManager security = System.getSecurityManager();
if (security != null) {
security.checkPermission(
SecurityConstants.GET_STACK_TRACE_PERMISSION);
}
// optimization so we do not call into the vm for threads that
// have not yet started or have terminated
if (!isAlive()) {
return EMPTY_STACK_TRACE;
}
StackTraceElement[][] stackTraceArray = dumpThreads(new Thread[] {this});
StackTraceElement[] stackTrace = stackTraceArray[0];
// a thread that was alive during the previous isAlive call may have
// since terminated, therefore not having a stacktrace.
if (stackTrace == null) {
stackTrace = EMPTY_STACK_TRACE;
}
return stackTrace;
} else {
// Don't need JVM help for current thread
return (new Exception()).getStackTrace();
}
}

这里面返回的 StackTraceElement 的内容

1
2
3
4
5
6
public final class StackTraceElement implements java.io.Serializable {
// Normally initialized by VM (public constructor added in 1.5)
private String declaringClass;
private String methodName;
private String fileName;
private int lineNumber;

分别是定义的类,方法名,文件名,以及代码行数

我们就能看到这样的结果

1
2
3
java.lang.Thread.getStackTrace(Thread.java:1564)
StackTrace.StackTrace.method1(StackTrace.java:14)
StackTrace.StackTrace.main(StackTrace.java:11)

因为是栈,所以就是最上面的是最近的方法,而第二行其实是我的目标方法,就是前面描述的逻辑其实应该找到第3个元素,因为第1个是调用 getStackTrace 方法,
第2个是目标方法,第3个才是我们想要找的调用方方法,以及所在的行数