java反射之Call stack introspection
java是基于栈设计的语言,其实与C、C++语言相同。整个程序的运行表现在方法的执行是一系列入栈出栈的行为,栈是线程私有的。
在java语言中,我们可以跟踪方法的调用关系,即当前栈帧(栈顶)和已经入栈的栈帧的层次关系。
从java1.4以后,java语言的Throwable类提供了以下方法:
StackTraceElement[] java. lang. Throwable.getStackTrace()
Open Declaration StackTraceElement[] java.lang.Throwable.getStackTrace()
Provides programmatic access to the stack trace information printed by printStackTrace(). Returns an array of stack trace elements, each representing one stack frame. The zeroth element of the array (assuming the array's length is non-zero) represents the top of the stack, which is the last method invocation in the sequence. Typically, this is the point at which this throwable was created and thrown. The last element of the array (assuming the array's length is non-zero) represents the bottom of the stack, which is the first method invocation in the sequence.
Some virtual machines may, under some circumstances, omit one or more stack frames from the stack trace. In the extreme case, a virtual machine that has no stack trace information concerning this throwable is permitted to return a zero-length array from this method. Generally speaking, the array returned by this method will contain one element for every frame that would be printed by printStackTrace. Writes to the returned array do not affect future calls to this method.
Returns:
an array of stack trace elements representing the stack trace pertaining to this throwable.
Since:
1.4
该方法返回的StackTraceElement[] 就是栈帧数组。数组下标0的元素代表当前栈顶栈帧,数组的最大下标代表调用栈序列中第一个栈帧,也就是第一个方法的调用。我们可以从StackTraceElement得到栈调用层级的关系、调用方法名及调用入口位置,代码示例:
执行结果:
调用结果显示的方法调用层级关系。
那我们得到这些信息有什么用呢。
1.日志:这些信息可以让应用的日志系统得到信息更详细。
2.安全:API可以决定调用者当前包或者类是否有权限进入。
3.流程控制:可以避免一些流程错误,比如无限递归调用。
实现一个简单的日志系统:
package com.doctor.reflect;
import java.io.PrintWriter;
import java.io.StringWriter;
/**
* Call stack introspection
*
* @author sdcuike
*
* Created At 2016年8月29日 下午9:40:35
*/
public class CallStackIntrospectionDemo {
private static final MyLogger logger = new LoggerImpl();
public static void main(String[] args) {
logger.logRecord("hello");
IllegalArgumentException exception = new IllegalArgumentException("IllegalArgumentException");
logger.logProblem("throwable", exception);
}
public interface MyLogger {
// Types for log records
int ERROR = 0;
int WARNING = 100;
int STATUS = 200;
int DEBUG = 300;
int TRACE = 400;
void logRecord(String message);
void logProblem(String message, Throwable throwable);
}
public static class LoggerImpl implements MyLogger {
@Override
public void logRecord(String message) {
Throwable throwable = new Throwable();
log(message, throwable.getStackTrace()[1]);
}
@Override
public void logProblem(String message, Throwable throwable) {
StringWriter out = new StringWriter();
PrintWriter writer = new PrintWriter(out);
throwable.printStackTrace(writer);
writer.flush();
log(message + out.toString(), throwable.getStackTrace()[0]);
}
private void log(String message, StackTraceElement stackTraceElement) {
String className = stackTraceElement.getClassName();
String methodName = stackTraceElement.getMethodName();
int lineNumber = stackTraceElement.getLineNumber();
System.out.println(String.join(" ", "模拟打印日志:", methodName, className, "" + lineNumber, message));
}
}
}
执行结果:
模拟打印日志: main com.doctor.reflect.CallStackIntrospectionDemo 36 hello
模拟打印日志: main com.doctor.reflect.CallStackIntrospectionDemo 38 throwablejava.lang.IllegalArgumentException: IllegalArgumentException
at com.doctor.reflect.CallStackIntrospectionDemo.main(CallStackIntrospectionDemo.java:38)
上述日志,只是简单的在控制台打印一些信息。
参考:java doc
Java Reflection in Action 第五章 Call stack introspection