锁定老帖子 主题:问个问题:如何知道方法的调用者
该帖已经被评为精华帖
|
|
---|---|
作者 | 正文 |
发表时间:2005-05-19
ClassA的一个实例调用了ClassB的一个方法,通过动态代理可以截取这个调用,但是不能获得是谁调用了ClassB的方法,请问如何才能截取到。 先给个思路吧,谢谢 声明:ITeye文章版权属于作者,受法律保护。没有作者书面许可不得转载。
推荐链接
|
|
返回顶楼 | |
发表时间:2005-05-19
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(););; } } |
|
返回顶楼 | |
发表时间:2005-05-19
谢谢楼上,这东西太有用了
|
|
返回顶楼 | |
发表时间:2005-05-20
有积累了一点知识
收下! myy是个好人 |
|
返回顶楼 | |
发表时间:2005-05-21
好东东,收下,建议放入精华贴:)
|
|
返回顶楼 | |
发表时间:2005-05-23
myy 写道 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(););; } } 这的确是个好方法,也从来没有用过使用StackTraceElement 去获得堆栈的信息。 |
|
返回顶楼 | |
发表时间:2005-05-23
csdn buaawhl blog 写道 线程运行栈信息的获取 一、问题的引入 我们在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时代的作品。我们来看相关源代码。 /** 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不仅提供越来越多、越来越强的功能,而且暴露给用户的控制方法越来越多,越来越强大。 http://forum.iteye.com/viewtopic.php?t=6450 引用 关于直接获取调用类名的方法。 我们来看sun.reflect.Reflection的getCallerClass()方法的说明。 java代码: /** Returns the class of the method <code>realFramesToSkip</code> 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 <code>getCallerClass(0)</code> 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层次,所以,你可以用这个方法获得任何层次的上的调用类名。 |
|
返回顶楼 | |
发表时间:2005-05-23
myy 写道 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(););; } } 这样只能得到caller's class. 不能得到caller这个object instance |
|
返回顶楼 | |
发表时间:2005-05-23
这个方法我就是看 buaawhl blog 学来的,呵呵。
当时,我们系统的连接池(Weblogic自带的)总是爆满,又查不出来是“谁”干的,于是一气之下,自己包装了Apache-commons 的dbcp做连接池,中间就用到了此法来跟踪连接池的使用情况,直接就可以知道那些类或jsp没有关闭连接了。 |
|
返回顶楼 | |
发表时间:2005-05-24
说来巧了,我问这问题也是为了看连接池出问题的原因,呵呵。
今天看了一下spring的一个源码,它也是用这方法的。org.springframework.core.ControlFlowFactory |
|
返回顶楼 | |