一、什么是StackTrace
StackTrace(堆栈轨迹)存放的就是方法调用栈的信息,每次调用一个方法会产生一个方法栈,当前方法调用另外一个方法时会使用栈将当前方法的现场信息保存在此方法栈当中,获取这个栈就可以得到方法调用的详细过程。例如:异常处理中常用的e.printStackTrace()实质就是打印异常调用的堆栈信息。
二、StackTraceElement介绍
StackTraceElement表示StackTrace(堆栈轨迹)中的一个方法对象,通过这个对象可以获取调用栈当中的调用过程信息,包括方法的类名、方法名、文件名以及调用的行数。查看StackTraceElement类的源代码,我们可以获取方法所在行、所在类等的信息;
/* @since 1.4
* @author Josh Bloch
*/
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;
public StackTraceElement(String declaringClass, String methodName,
String fileName, int lineNumber) {
this.declaringClass = Objects.requireNonNull(declaringClass, "Declaring class is null");
this.methodName = Objects.requireNonNull(methodName, "Method name is null");
this.fileName = fileName;
this.lineNumber = lineNumber;
}
public String getFileName() {
return fileName;
}
public int getLineNumber() {
return lineNumber;
}
public String getClassName() {
return declaringClass;
}
public String getMethodName() {
return methodName;
}
public boolean isNativeMethod() {
return lineNumber == -2;
}
public String toString() {
return getClassName() + "." + methodName +
(isNativeMethod() ? "(Native Method)" :
(fileName != null && lineNumber >= 0 ?
"(" + fileName + ":" + lineNumber + ")" :
(fileName != null ? "("+fileName+")" : "(Unknown Source)")));
}
public boolean equals(Object obj) {
if (obj==this)
return true;
if (!(obj instanceof StackTraceElement))
return false;
StackTraceElement e = (StackTraceElement)obj;
return e.declaringClass.equals(declaringClass) &&
e.lineNumber == lineNumber &&
Objects.equals(methodName, e.methodName) &&
Objects.equals(fileName, e.fileName);
}
public int hashCode() {
int result = 31*declaringClass.hashCode() + methodName.hashCode();
result = 31*result + Objects.hashCode(fileName);
result = 31*result + lineNumber;
return result;
}
private static final long serialVersionUID = 6992337162326171013L;
}
获取StackTraceElement的方法有两种,均返回StackTraceElement数组,也就是这个栈的信息:
///method 1
Thread.currentThread().getStackTrace()
///method 2
new Throwable().getStackTrace()
测试代码和测试结果如下:
public class StackTraceElementTest {
@Test
public void testStackTraceElement(){
获取堆栈轨迹数组
StackTraceElement[] element = Thread.currentThread().getStackTrace();
for (int i = 0 ;i < element.length; i++){
System.out.println(element[i]);
}
}
}
执行结果如下:
cn.missbe.util.StackTraceElementTest.testStackTraceElement(StackTraceElementTest.java:21)
sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
java.lang.reflect.Method.invoke(Method.java:498)
org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:47)
org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:44)
org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:271)
org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:70)
org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:50)
org.junit.runners.ParentRunner$3.run(ParentRunner.java:238)
org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:63)
org.junit.runners.ParentRunner.runChildren(ParentRunner.java:236)
org.junit.runners.ParentRunner.access$000(ParentRunner.java:53)
org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:229)
org.junit.runners.ParentRunner.run(ParentRunner.java:309)
org.junit.runner.JUnitCore.run(JUnitCore.java:160)
com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68)
com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:47)
com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242)
com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)
从上面的测试结果可以看出,开始执行点为JUnitStarter.java:70由Junit测试框架进行调用,我们可以看到Junit的详细调用过程,可以获取整个方法调用栈信息;
三、用途
1、进行源码分析,通过StackTraceElement对象,可以知道框架的调用过程,帮助我们理解执行流程。例如上面的Junit测试框架,我们可以根据堆栈信息知道Junit的调用过程;
2、我们可以进行包装日志类,如log4j,在打印目标日志信息的时候,可以通过StackTraceElement 对象获取调用的方法,方法所在的类,方法所在行数写入日志文件当中,代码如下:
StackTraceElement[] temp = Thread.currentThread().getStackTrace();
///最近的调用方法
StackTraceElement method = temp[1];
for (StackTraceElement stackTraceElement : temp) {
if (stackTraceElement.getClassName().contains("police")
&& !stackTraceElement.getClassName().contains("CGLIB")
&& !stackTraceElement.getMethodName().equals("print")
&& !stackTraceElement.getMethodName().equals("around")) {
method = stackTraceElement;
break;
}
}
String fromLine = method.getClassName().replaceAll(App.PACKAGE_BASE + ".", "") + "." + method.getMethodName()+ "() line@" + method.getLineNumber();
logMsgStr写入日志文件或者输出到控制台
String logMsgStr = sdf.format(Calendar.getInstance().getTime()) + " :" + msgStr + " @来自:" + fromLine;