如何知道方法的调用者

转载 2006年06月13日 16:32:00
java代码: 

public static String getCaller(){
    int i;
    StackTraceElement stack[] = (new Throwable()).getStackTrace();
    for (i=0; i < stack.length; i++) {
      StackTraceElement ste=stack[i];
        System.out.println(ste.getClassName()+"."+ste.getMethodName()+"(...)");
      System.out.println(i+"--"+ste.getMethodName());
      System.out.println(i+"--"+ste.getFileName());
      System.out.println(i+"--"+ste.getLineNumber());
    }
  }


线程运行栈信息的获取
一、问题的引入
我们在Java程序中使用日志功能(JDK Log或者Log4J)的时候,会发现Log系统会自动帮我们打印出丰富的信息,格式一般如下:
[运行时间] [当前类名] [方法名]
INFO: [用户信息]
具体例子如Tomcat启动信息:
Jul 9, 2004 11:22:41 AM org.apache.coyote.http11.Http11Protocol start
INFO: Starting Coyote HTTP/1.1 on port 8080

看起来这毫无神奇之处,不就是打印了一条信息吗?但如果好奇心重一点,追寻后面的实现原理,会发现这确实很神奇。
上面的Log信息的[当前类名] [方法名]部分 不是用户自己添加的,而是Log系统自动添加的。这意味着Log系统能够自动判断当前执行语句是哪个类的哪个方法。这是如何做到的?
我们翻遍java.lang.reflection package,幻想着找到一个Statement语句级别的Reflection类,通过这个Statement对象获得Method,然后通过这个Method获得declared Class。这不就获得对应的Class和Method信息了吗?这是一个不错的构想,但也只能是一个构想;因为没有这个Statement对象。
再想一下。对了,Java不是有一个Thread类吗?Thread.currentThread()方法获取当前线程,我们能不能通过这个当前线程获取当前运行的Method和Class呢?很遗憾,如果你还在用JDK1.4或以下版本,那么找不到这样的方法。(JDK1.5的情况后面会讲)
再想一下。对了,我们都有很深刻的印象,当系统抛出Exception的时候,总是打印出一串的信息,告诉我们Exception发生的位置,和一层一层的调用关系。我们也可以自己调用Exception的printStackTrace()方法来打印这些信息。这不就是当前线程运行栈的信息吗?找到了,就是它。
Exception的printStackTrace()方法继承自Throwable,那么我们来看一下,JDK的Throwable的printStackTrace()方法是如何实现的。
我们先来看JDK1.3的源代码,会发现Throwable.printStackTrace()方法调用了一个native printStackTrace0()方法。我们找不到任何线索,可以用在我们自己的Java代码中。
那怎么办?Throwable.printStackTrace()的输出结果字符串里面不是包含了当前线程运行栈的所有信息吗?我们可以从这个字符串中抽取自己需要的信息。JDK1.3的时代,也只能这么做了。
二、Log4J 1.2的相关实现
Log4J 1.2是JDK1.3时代的作品。我们来看相关源代码。
java代码: 


  /**
     Instantiate location information based on a Throwable. We
     expect the Throwable <code>t</code>, to be in the format
       <pre>
        java.lang.Throwable
        ...
          at org.apache.log4j.PatternLayout.format(PatternLayout.java:413)
          at org.apache.log4j.FileAppender.doAppend(FileAppender.java:183)
        at org.apache.log4j.Category.callAppenders(Category.java:131)
        at org.apache.log4j.Category.log(Category.java:512)
        at callers.fully.qualified.className.methodName(FileName.java:74)
        ...
       </pre>
    */

  public LocationInfo(Throwable t, String fqnOfCallingClass) {
    String s;

      t.printStackTrace(pw);
      s = sw.toString();
      sw.getBuffer().setLength(0);
  …. // 这里的代码省略
  }



这里我们可以看到整体的实现思路。
首先,t.printStackTrace(pw); 获得stack trace字符串。这个t是 new Throwable()的结果。用户程序调用Log4J方法之后,Log4J自己又进行了4次调用,然后才获得了 t = new Throwable() :
at org.apache.log4j.PatternLayout.format(PatternLayout.java:413)
at org.apache.log4j.FileAppender.doAppend(FileAppender.java:183)
at org.apache.log4j.Category.callAppenders(Category.java:131)
at org.apache.log4j.Category.log(Category.java:512)
那么,往下走4行,就可以回到用户程序本身的调用信息:
at callers.fully.qualified.className.methodName(FileName.java:74)
这一行里面,类名、方法名、文件名、行号等信息全有了。解析这一行,就可以获得需要的所有信息。
三、JDK1.4 Log的相关实现
Log4J大获成功,Sun决定在JDK1.4中引入这个Log功能。
为了免去解析StackTrace字符串的麻烦,JDK1.4引入了一个新的类,StackTraceElement。

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;

可以看到,恰好包括类名、方法名、文件名、行号等信息。
我们来看JDK1.4 Log的相关实现。
LocationInfo.java 的infoCaller方法(推算调用者)

// Private method to infer the caller's class and method names
private void inferCaller() {

// Get the stack trace.
StackTraceElement stack[] = (new Throwable()).getStackTrace();
// First, search back to a method in the Logger class.
…. // 这里的代码省略
// Now search for the first frame before the "Logger" class.
while (ix < stack.length) {
StackTraceElement frame = stack[ix];
String cname = frame.getClassName();
if (!cname.equals("java.util.logging.Logger"))
// We've found the relevant frame.
… // 这里的代码省略
}
// We haven't found a suitable frame, so just punt. This is
// OK as we are only committed to making a "best effort" here.
}

从注释中就可以看出实现思路。过程和Log4J异曲同工。只是免去了解析字符串的麻烦。
四、Log4J 1.3 alpha的相关实现
既然JDK1.4中引入了StackTraceElement类,Log4J也要与时俱进。LocationInfo类也有了相应的变化。

/**
Instantiate location information based on a Throwable. We
expect the Throwable <code>t</code>, to be in the format

<pre>
java.lang.Throwable
...
at org.apache.log4j.PatternLayout.format(PatternLayout.java:413)
at org.apache.log4j.FileAppender.doAppend(FileAppender.java:183)
at org.apache.log4j.Category.callAppenders(Category.java:131)
at org.apache.log4j.Category.log(Category.java:512)
at callers.fully.qualified.className.methodName(FileName.java:74)
...
</pre>

<p>However, we can also deal with JIT compilers that "lose" the
location information, especially between the parentheses.

*/
public LocationInfo(Throwable t, String fqnOfInvokingClass) {
if(PlatformInfo.hasStackTraceElement()) {
StackTraceElementExtractor.extract(this, t, fqnOfInvokingClass);
} else {
LegacyExtractor.extract(this, t, fqnOfInvokingClass);
}
}

可以看到,Log4J首先判断Java平台是否支持StackTraceElement,如果是,那么用StackTraceElementExtractor,否则使用原来的LegacyExtractor。
下面来看StackTraceElementExtractor.java

/**
* A faster extractor based on StackTraceElements introduced in JDK 1.4.
*
* The present code uses reflection. Thus, it should compile on all platforms.
*
* @author Martin Schulz
* @author Ceki G&lc&
*
*/
public class StackTraceElementExtractor {
protected static boolean haveStackTraceElement = false;
private static Method getStackTrace = null;
private static Method getClassName = null;
private static Method getFileName = null;
private static Method getMethodName = null;
private static Method getLineNumber = null;
…. // 以下代码省略

可以看到,Log4J 1.3仍然兼容JDK1.3,而且为JDK1.4也做了相应的优化。
五、JDK1.5的Thread Stack Trace
JDK1.5在Thread类里面引入了getStackTrace()和getAllStackTraces()两个方法。这下子,我们不用 (new Throwable()).getStackTrace ();可以调用
Thread.getCurrentThread().getStackTrace()来获得当前线程的运行栈信息。不仅如此,只要权限允许,还可以获得其它线程的运行栈信息。

/**
* Returns an array of stack trace elements representing the stack dump
* of this thread. This method will return a zero-length array if
* this thread has not started or has terminated.
* If the returned array is of non-zero length then the first element of
* the array represents the top of the stack, which is the most recent
* method invocation in the sequence. The last element of the array
* represents the bottom of the stack, which is the least recent method
* invocation in the sequence.
*
* <p>If there is a security manager, and this thread is not
* the current thread, then the security manager's
* <tt>checkPermission</tt> method is called with a
* <tt>RuntimePermission("getStackTrace")</tt> permission
* to see if it's ok to get the stack trace.
*
* <p>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 thread is permitted to return a zero-length array from this
* method.
*
* @return an array of <tt>StackTraceElement</tt>,
* each represents one stack frame.
*
* @since 1.5
*/
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);
}
}

if (!isAlive()) {
return EMPTY_STACK_TRACE;
}

Thread[] threads = new Thread[1];
threads[0] = this;
StackTraceElement[][] result = dumpThreads(threads);
return result[0];
}

/**
* Returns a map of stack traces for all live threads.
*
* @since 1.5
*/
public static Map<Thread, StackTraceElement[]> getAllStackTraces() {
// check for getStackTrace permission
// Get a snapshot of the list of all threads
}

六、总结
从总的发展趋势来看,JDK不仅提供越来越多、越来越强的功能,而且暴露给用户的控制方法越来越多,越来越强大。

StackTraceElement获取方法调用栈的信息

一、什么是StackTraceStackTrace(堆栈轨迹)存放的就是方法调用栈的信息,异常处理中常用的printStackTrace()实质就是打印异常调用的堆栈信息。二、StackTraceEl...
  • hp910315
  • hp910315
  • 2016年09月29日 15:20
  • 4732

异常的转换思想:当出现的异常是调用者处理不了的,就需要将此异常转换为一个调用者可以处理的异常抛出。

最经碰到学生问这样一道题目,网上有很多解释,但是现在网上大多数答案都是一个复制一个,这是在相关网站找到的资料,明确解释了该思想!    1、异常转译的原理     所谓的异常转...
  • jolfe
  • jolfe
  • 2015年03月06日 13:23
  • 1169

通过StackTraceElement获取方法调用者的具体信息

原文链接: http://blog.csdn.net/argel_lj/article/details/49925657 StackTrace(堆栈轨迹) 其中包含的消息   ,  其实就是我们跑程序...
  • liuxiangke0210
  • liuxiangke0210
  • 2017年07月09日 10:26
  • 87

通过StackTraceElement获取方法调用者的具体信息

本文简要介绍了StackTrace(堆栈轨迹)以及StackTraceElement的一些用法
  • argel_lj
  • argel_lj
  • 2015年11月19日 11:55
  • 3054

java中的最顶级的异常类Throwable

Throwable类是整个异常体系类的父级类,当然最终追根溯源到底的父类当然要归于Object类。Throwable类实现了Serializable接口,表示Throwable可以进行序列化,继承自O...
  • pangqiandou
  • pangqiandou
  • 2016年12月03日 23:24
  • 2256

JAVA编程——Throwable常见异常及源码详解

Throwable
  • RamProg
  • RamProg
  • 2015年08月16日 00:14
  • 978

利用线程运行栈StackTraceElement设计Android日志模块

如果你想实现自动MainActivity:onCreate(line:37)
  • tangjiean
  • tangjiean
  • 2014年08月25日 20:19
  • 1884

Java异常处理(I)

java异常处理的类所有lang包中的类常用的类和继承继承结构 可以看到这些类都直接或者间接继承自java.lang.Throwable类。当然有些子类非常不常用。一个类职业是Throwable类的...
  • uncle_gy
  • uncle_gy
  • 2017年11月16日 21:06
  • 81

Java异常的栈轨迹(Stack Trace)

捕获到异常时,往往需要进行一些处理。比较简单直接的方式就是打印异常栈轨迹Stack Trace。说起栈轨迹,可能很多人和我一样,第一反应就是printStackTrace()方法。其实除了这个方法,还...
  • z69183787
  • z69183787
  • 2015年06月26日 07:16
  • 1346

Java异常处理原理与原则

一、Exception和Error的区别 在Java中我们可以这样区分,异常(Exception)是一种非程序原因的操作失败(Failure),而错误(Error)则意味着程序有缺陷(Bug)。 ...
  • u010567606
  • u010567606
  • 2014年11月09日 16:16
  • 394
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:如何知道方法的调用者
举报原因:
原因补充:

(最多只允许输入30个字)