`

如何知道方法的调用者

阅读更多

 

转自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层次,所以,你可以用这个方法获得任何层次的上的调用类名。

 

分享到:
评论

相关推荐

    c/c++中函数调用方式

    这种调用方式的一个限制是不支持可变参数列表,因为被调用者无法预先知道调用者究竟压入了多少参数。 ### __fastcall `__fastcall`调用约定试图通过减少栈操作来提高函数调用的效率,尤其是在需要频繁传递参数的...

    易语言调用LIB和OBJ方法 源码

    这是为了让易语言知道如何正确地调用这些外部函数。 3. **调用函数**:在程序适当的位置,使用易语言的调用语句来执行LIB或OBJ中的函数。易语言提供了一些关键字,如`调用标准函数`或`调用动态链接库函数`,用于...

    剖析C++函数调用约定

    在这种约定中,参数从右向左压入堆栈,调用者负责清理堆栈。这意味着调用函数必须知道参数的数量和类型,以便正确地弹出参数。__cdecl的一个关键特性是它可以处理可变参数列表,如printf函数。函数名在编译后不添加...

    E语言 DLL防止被别人调用 源码

    这种方法可以通过检查调用者的身份、证书或者使用特定的密钥来实现。 2. **加密导出函数名**:对导出函数的名称进行加密或哈希处理,使得第三方无法通过名字直接查找和调用函数。在运行时,只有知道解密方法的应用...

    Java RPC调用示例

    首先,RPC的核心思想是将远程调用过程透明化,使得开发者可以像调用本地方法一样调用远程服务。这种抽象简化了分布式系统的设计和开发。在Java中,实现RPC通常包括以下几个关键步骤: 1. **定义服务接口**:RPC调用...

    Python调用windows下DLL

    对于stdcall,参数由被调用者清理;而对于cdecl,参数由调用者清理。在Windows下,大部分API函数使用stdcall约定,而C语言函数通常使用cdecl约定。因此,当你知道DLL函数的调用约定时,可以使用`ctypes.windll`或`...

    c#调用dll示例

    这里的"Cdecl"调用约定是指C风格的函数调用约定,它要求参数按照声明顺序压栈,由被调用者清理栈。如果你的C++代码使用了其他调用约定(如stdcall),需要相应调整DllImport的设置。 在`TestCallDll`这个例子中,...

    java-rpc远程过程调用

    5. **请求解包(Request Unpacking)**:服务端接收到消息后,进行解包,恢复出原始的方法调用信息。 6. **执行服务(Service Execution)**:服务提供者执行相应的方法,并获取结果。 7. **结果打包(Result ...

    回调函数就是一个通过函数指针调用的函数

    调用者不关心谁是被调用者,所有它需知道的,只是存在一个具有某种特定原型、某些限制条件(如返回值为int)的被调用函数。 回调函数在实际中有很多应用场景,例如,在库中提供了某些排序算法的实现,如冒泡排序、...

    webservice动态调用

    在Java开发中,Webservice动态调用是一项常见的技术,主要用于实现服务消费者和服务提供者之间的松耦合通信。本篇将详细介绍如何进行Webservice动态调用,并以CXF框架为例进行演示。 首先,我们要理解什么是动态...

    易语言调用API实现flash播放器

    5. **播放Flash内容**:通过调用对象的特定方法,如`Play`,启动Flash的播放。 6. **处理事件**:若需要监听Flash播放器的事件,如播放结束、暂停等,也需要使用API函数注册事件处理函数。 7. **错误处理**:在...

    java自制简单RPC调用例子

    客户端需要知道服务端的位置(例如,IP地址和端口号),并且能够将方法调用转换为网络消息发送给服务端。 3. **服务端模块(Server)**: 服务端是接收并处理RPC请求的程序。它实现了API模块中定义的接口,并提供...

    AXIS2客户端调用实例

    4. **实例化并调用服务**:在客户端代码中,创建stub类的实例,然后通过实例调用服务的方法,传递参数并获取返回值。 5. **处理响应**:服务的响应通常以XML形式返回,客户端需要解析这个响应,并根据业务逻辑进行...

    C#.net 动态调用web服务

    这些代理类封装了服务调用的所有细节,使得开发者可以像调用本地方法一样调用Web服务。 4. **使用`SoapHttpClientProtocol`类**:在较旧版本的.NET Framework中,如.NET Framework 2.0,可以使用`System.Web....

    远程调用技术代码追踪(webservice) 之一

    在Web服务中,服务提供者定义一组接口,这些接口通过WSDL(Web服务描述语言)文档对外发布,客户端则通过解析WSDL找到调用服务的方法。 在Web服务的实现中,源码分析是理解其工作流程的关键。以BORLAND的实现为例,...

    rpc 远程调用

    RPC使得分布式系统中的组件能够像调用本地方法一样调用远程服务,极大地简化了分布式编程。 在Java中,RPC框架的实现通常涉及到以下关键概念和技术: 1. **接口与代理**:RPC的核心是通过接口来定义服务。客户端...

    Servlet转发与重定向

    4. 请求与响应的对象:RequestDispatcher.forward 方法的调用者与被调用者之间共享相同的 request 对象和 response 对象,而 HttpServletResponse.sendRedirect 方法调用者与被调用者使用各自的 request 对象和 ...

    java调用本地打印机

    这可以通过调用`PrinterJob.getPrinterJob()`静态方法实现。 ```java import java.awt.print.PrinterJob; PrinterJob job = PrinterJob.getPrinterJob(); ``` 2. **设置PrintService** `PrinterJob`对象...

    C++中,由vs工程生成、调用dll的方法(含源码和说明文档)

    1. **添加引用**:在调用DLL的工程中,引入刚刚创建的DLL头文件,这将使编译器知道如何声明和调用DLL中的函数。 2. **链接DLL**:将DLL的.lib文件添加到调用工程的链接器输入项,这样编译器就知道在哪里寻找DLL的...

    设计模式_行为型_命令模式.md

    调用者包含一个命令对象,并在适当的时候调用命令的`execute`方法。调用者不需要了解命令的具体细节,它只需要知道如何调用命令。 4. 接收者(Receiver):实际执行命令操作的对象。它知道如何执行具体的操作,命令...

Global site tag (gtag.js) - Google Analytics