`

如何知道方法的调用者

阅读更多

 

转自http://hellboys.bokee.com/1904804.html

比如有2个类:ClassA,ClassB
ClassA的一个实例调用了ClassB的一个方法,通过动态代理可以截取这个调用,但是不能获得是谁调用了ClassB的方法,如何才能截取到呢,
下面给个思路.
java代码:
public static String getCaller(){ 
    int i; 
    StackTraceElement stack[] = (new Throwable()).getStackTrace(); 
    for (i=0; i < stack.length; i++) { 
      StackTraceElement ste=stack; 
      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 t, to be in the format 
 
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) 
...
*/ 
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 t, to be in the format
 
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) 
...
 
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. 
* 
*
If there is a security manager, and this thread is not 
* the current thread, then the security manager's 
* checkPermission method is called with a 
* RuntimePermission("getStackTrace") permission 
* to see if it's ok to get the stack trace. 
* 
*
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 StackTraceElement, 
* 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 getAllStackTraces() { 
// check for getStackTrace permission 
// Get a snapshot of the list of all threads 
}
 
六、总结
从总的发展趋势来看,JDK不仅提供越来越多、越来越强的功能,而且暴露给用户的控制方法越来越多,越来越强大。
 

关于直接获取调用类名的方法。
我们来看sun.reflect.Reflection的getCallerClass()方法的说明。
java代码:

/** Returns the class of the method realFramesToSkip 
frames up the stack (zero-based), ignoring frames associated 
with java.lang.reflect.Method.invoke() and its implementation. 
The first frame is that associated with this method, so 
getCallerClass(0) returns the Class object for 
sun.reflect.Reflection. Frames associated with 
java.lang.reflect.Method.invoke() and its implementation are 
completely ignored and do not count toward the number of "real" 
frames skipped. */ 
public static native Class getCallerClass(int realFramesToSkip);
 
这是一个native方法。原理也是根据StackFrame(运行栈)获取相应类的信息。这个方法直接返回一个Class名字,直接有效。参数realFramesToSkip用来选取你所需要的Stack层次,所以,你可以用这个方法获得任何层次的上的调用类名。

 

分享到:
评论

相关推荐

    子VI创建与调用方法(适合初学者).pdf

    laview中关于子vi创建与调用方法(适合初学者),很多同学不知道怎么创建子vi,本文档详细介绍了如何快速的创建子vi。

    javascript实现根据函数名称字符串动态执行函数的方法示例

    //动态函数调用,调用者使用targetFunction function targetFunction() { alert&#40;11111&#41;; return 9; } test(myFunc); function test(funcName) { if(typeof(eval&#40;funcName&#41;) == function) { var ...

    【易语言】组件复制的调用和事件响应使用方法【源代码】组件复制拖曳

    在易语言中,偶尔也会遇到组件复制并使用组件的情况,很多初学者不知道如何对复制的组件如何使用。本篇使用自带系统核心支持库实现组件复制后的调用和事件响应。【原创】【源代码】组件复制拖曳

    Vue中使用方法、计算属性或观察者的方法实例详解

    熟悉 Vue 的都知道 方法methods、计算属性computed、观察者watcher 在 Vue 中有着非常重要的作用,有些时候我们实现一个功能的时候可以使用它们中任何一个都是可以的,但是它们之间又存在一些不同之处,每一个都有...

    命令模式(Command)原理图

    这个接口是所有具体命令类的基础,确保它们具有统一的执行方法调用方式。 ConcreteCommand:这是实现了Command接口的具体类。它定义了接收者如何进行具体的操作执行。一个具体的命令类通常会持有一个对接收者的引用...

    java-exception:java异常处理

    Java异常的处理 Java异常处理的五个关键字: try,catch,... throw用在方法内,用来引发一个异常对象,将这个异常对象传递到调用者处,并结束当前方法的执行。 使用格式: throw new 异常类名(参数); 例如: 传递

    你必须知道的495个C语言问题(PDF)

    调用者的指针却没有任何变化。. . . . . . . . . . . . . . . 18 4.5 我能否用void** 指针作为参数, 使函数按引用接受一般指针? . . 18 4.6 我有一个函数extern int f(int *); 它接受指向int 型的指针。我怎 样用...

    想成为嵌入式程序员应知道的n个基本问题

    C语言测试是招聘嵌入式系统程序员过程中必须而且有效的方法。这些年,我既参加也组织了许多这种测试,在这过程中我意识到这些测试能为带面试者和被面试者提供许多有用信息,此外,撇开面试的压力不谈,这种测试也是...

    qt 将数据存入优盘的两种方法

    qt 将数据存入优盘的两种方法 方式1: 检测Windows的事件–WM_DEVICECHANGE ...通过调用Windows库函数,对磁盘的类型进行判断,即可知道磁盘是否为U盘。 使用:需要检测时,执行此函数即可,无方式1缺陷。推荐使用。

    你必须知道的495个C语言问题

    调用者的指针没有任何变化。 4.9 能否用void**通用指针作为参数,使函数模拟按引用传递参数? 4.10 我有一个函数externintf(int*);,它接受指向int型的指针。我怎样用引用方式传入一个常数?调用f(&5);似乎不行。...

    MD5加密的方法

    MD5加密算法,其实很简单,但是初学者可能会不理解,不知道怎么用,所以我封装了一个方法,并且详细注释好,可供初学者看。以及直接调用

    想成为嵌入式程序员必须要知道的0x10个基本问题

    C语言测试是招聘嵌入式系统程序员过程中必须而且有效的方法。这些年,我既参加也组织了许多这种测试,在这过程中我意识到这些测试能为带面试者和被面试者提供许多有用信息,此外,撇开面试的压力不谈,这种测试也是...

    【09-异常处理】

    发生异常的方法逐渐向外传播,首先传给该方法的调用者,该方法调用者再次传给其调用者……直至最 后传到 main方法,如果main方法依然没有处理该异常,JVM会中止该程序,并打印异常的跟踪栈信 息。 异常处理...

    Delphi源码调试方法(适合新手)

    然而许多初学者不知道如何进行调试,写完程序就运行,发现结果不对再看代码,这样觉得非常吃力。这里,简单介绍一下Delphi所提供的调试功能。主要内容包括:1. 语法检查(Syntax Check) 2. 启动、暂停、中止集成...

    java中的反射(详解)

    JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。 Java...

    想成为嵌入式程序员应知道的0x10个基本问题

    C语言测试是招聘嵌入式系统程序员过程中必须而且有效的方法。这些年,我既参加也组织了许多这种测试,在这过程中我意识到这些测试能为带面试者和被面试者提供许多有用信息,此外,撇开面试的压力不谈,这种测试也是...

    java的5种模式.docx

    代理模式(Proxy Pattern) 定义:Provide a surrogate or placeholder for another object to control access to it...强制代理则是调用者直接调用真实角色,而不用关心代理是否存在,其代理的产生 是由真实角色决定的。

    java面试题

    这时候假设该类中有两个名称和参数列表完全相同的方法,仅仅是返回类型不同,java就无法确定编程者倒底是想调用哪个方法了,因为它无法通过返回结果类型来判断。 override可以翻译为覆盖,从字面就可以知道,它是...

Global site tag (gtag.js) - Google Analytics