`
vvggsky
  • 浏览: 67347 次
  • 性别: Icon_minigender_1
  • 来自: 上海
社区版块
存档分类
最新评论

如何知道方法的调用者

    博客分类:
  • J2SE
阅读更多

http://www.iteye.com/topic/13179

线程运行栈信息的获取
一、问题的引入
我们在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时代的作品。我们来看相关源代码。


   1.   /** 
   2.      Instantiate location information based on a Throwable. We 
   3.      expect the Throwable <code>t</code>, to be in the format 
   4.        <pre> 
   5.         java.lang.Throwable 
   6.         ... 
   7.           at org.apache.log4j.PatternLayout.format(PatternLayout.java:413); 
   8.           at org.apache.log4j.FileAppender.doAppend(FileAppender.java:183); 
   9.         at org.apache.log4j.Category.callAppenders(Category.java:131); 
  10.         at org.apache.log4j.Category.log(Category.java:512); 
  11.         at callers.fully.qualified.className.methodName(FileName.java:74); 
  12.         ... 
  13.        </pre> 
  14.     */  
  15.   public LocationInfo(Throwable t, String fqnOfCallingClass); {  
  16.     String s;  
  17. …  
  18.       t.printStackTrace(pw);;  
  19.       s = sw.toString();;  
  20.       sw.getBuffer();.setLength(0);;  
  21.   …. // 这里的代码省略  
  22.   }  



这里我们可以看到整体的实现思路。
首先,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&amp;lc&amp;
*
*/
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不仅提供越来越多、越来越强的功能,而且暴露给用户的控制方法越来越多,越来越强大。
分享到:
评论

相关推荐

    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