当前位置:  开发笔记 > 编程语言 > 正文

如何使用堆栈跟踪或反射找到方法的调用者?

如何解决《如何使用堆栈跟踪或反射找到方法的调用者?》经验,为你挑选了8个好方法。

我需要找到一个方法的调用者.是否可以使用堆栈跟踪或反射?



1> Adam Paynter..:
StackTraceElement[] stackTraceElements = Thread.currentThread().getStackTrace()

根据Javadocs:

数组的最后一个元素表示堆栈的底部,这是序列中最近的方法调用.

StackTraceElementgetClassName(),getFileName(),getLineNumber()getMethodName().

您将不得不尝试确定您想要的索引(可能stackTraceElements[1][2]).


请注意,此方法不会向您提供调用者,而只会调用调用者*的*类型.您将不会引用调用方法的对象.
@Eelco Thread.currentThread()很便宜.Thread.getStackTrace()很昂贵,因为与Throwable.fillInStackTrace()不同,不能保证该方法被它正在检查的同一个线程调用,因此JVM必须创建一个"安全点" - 锁定堆和堆栈.请参阅此错误报告:http://bugs.sun.com/bugdatabase/view_bug.do?video_id = 6375302
我应该注意到getStackTrace()仍然会创建一个Exception,所以这不是更快 - 只是更方便.
@JoachimSauer你知道一种获取对调用方法的对象的引用的方法吗?
只是旁注,但是在1.5 JVM上Thread.currentThread().getStackTrace()似乎比创建一个新的Exception()要慢很多(大约慢3倍).但正如已经指出的那样,你不应该在性能关键领域使用这样的代码.;)1.6 JVM似乎只慢了约10%,正如Software Monkey所说,它表达的意图比"新异常"方式更好.
我不知道为什么人们会说这不是答案,因为这可以使用[here](https://www.tutorialspoint.com/java/lang/java_lang_stacktraceelement htm)。

2> Johan Kaving..:

可以在对此增强请求的评论中找到替代解决方案.它使用getClassContext()自定义的方法,SecurityManager似乎比堆栈跟踪方法更快.

以下程序测试不同建议方法的速度(最有趣的位在内部类中SecurityManagerMethod):

/**
 * Test the speed of various methods for getting the caller class name
 */
public class TestGetCallerClassName {

  /**
   * Abstract class for testing different methods of getting the caller class name
   */
  private static abstract class GetCallerClassNameMethod {
      public abstract String getCallerClassName(int callStackDepth);
      public abstract String getMethodName();
  }

  /**
   * Uses the internal Reflection class
   */
  private static class ReflectionMethod extends GetCallerClassNameMethod {
      public String getCallerClassName(int callStackDepth) {
          return sun.reflect.Reflection.getCallerClass(callStackDepth).getName();
      }

      public String getMethodName() {
          return "Reflection";
      }
  }

  /**
   * Get a stack trace from the current thread
   */
  private static class ThreadStackTraceMethod extends GetCallerClassNameMethod {
      public String  getCallerClassName(int callStackDepth) {
          return Thread.currentThread().getStackTrace()[callStackDepth].getClassName();
      }

      public String getMethodName() {
          return "Current Thread StackTrace";
      }
  }

  /**
   * Get a stack trace from a new Throwable
   */
  private static class ThrowableStackTraceMethod extends GetCallerClassNameMethod {

      public String getCallerClassName(int callStackDepth) {
          return new Throwable().getStackTrace()[callStackDepth].getClassName();
      }

      public String getMethodName() {
          return "Throwable StackTrace";
      }
  }

  /**
   * Use the SecurityManager.getClassContext()
   */
  private static class SecurityManagerMethod extends GetCallerClassNameMethod {
      public String  getCallerClassName(int callStackDepth) {
          return mySecurityManager.getCallerClassName(callStackDepth);
      }

      public String getMethodName() {
          return "SecurityManager";
      }

      /** 
       * A custom security manager that exposes the getClassContext() information
       */
      static class MySecurityManager extends SecurityManager {
          public String getCallerClassName(int callStackDepth) {
              return getClassContext()[callStackDepth].getName();
          }
      }

      private final static MySecurityManager mySecurityManager =
          new MySecurityManager();
  }

  /**
   * Test all four methods
   */
  public static void main(String[] args) {
      testMethod(new ReflectionMethod());
      testMethod(new ThreadStackTraceMethod());
      testMethod(new ThrowableStackTraceMethod());
      testMethod(new SecurityManagerMethod());
  }

  private static void testMethod(GetCallerClassNameMethod method) {
      long startTime = System.nanoTime();
      String className = null;
      for (int i = 0; i < 1000000; i++) {
          className = method.getCallerClassName(2);
      }
      printElapsedTime(method.getMethodName(), startTime);
  }

  private static void printElapsedTime(String title, long startTime) {
      System.out.println(title + ": " + ((double)(System.nanoTime() - startTime))/1000000 + " ms.");
  }
}

运行Java 1.6.0_17的2.4 GHz Intel Core 2 Duo MacBook的输出示例:

Reflection: 10.195 ms.
Current Thread StackTrace: 5886.964 ms.
Throwable StackTrace: 4700.073 ms.
SecurityManager: 1046.804 ms.

内部反射的方法是比别人快.从新创建的堆栈跟踪获取堆栈跟踪Throwable比从当前获取堆栈跟踪更快Thread.在查找调用者类的非内部方式中,自定义SecurityManager似乎是最快的.

更新

作为lyomi中指出此评论的sun.reflect.Reflection.getCallerClass()方法已经默认在Java 7中更新40被禁用,完全用Java 8了解更多关于这在去除这个问题在Java bug数据库.

更新2

正如zammbi所发现的那样,甲骨文被迫退出了取消该计划的变革sun.reflect.Reflection.getCallerClass().它仍然可以在Java 8中使用(但不推荐使用).

更新3

3年后:使用当前JVM更新时序.

> java -version
java version "1.8.0"
Java(TM) SE Runtime Environment (build 1.8.0-b132)
Java HotSpot(TM) 64-Bit Server VM (build 25.0-b70, mixed mode)
> java TestGetCallerClassName
Reflection: 0.194s.
Current Thread StackTrace: 3.887s.
Throwable StackTrace: 3.173s.
SecurityManager: 0.565s.


是的,似乎就是这样.但请注意,我在示例中给出的时间是一百万次调用 - 所以根据您使用它的方式,这可能不是问题.

3> Craig P. Mot..:

听起来你正试图避免将引用传递给this方法.传递this比通过当前堆栈跟踪查找调用者更好. 重构为更多OO设计甚至更好. 您不应该需要知道呼叫者.如有必要,传递回调对象.


存在想要这样做的有效理由.我有几次在测试过程中发现它很有帮助.
++了解来电者的信息太多了.如果必须,您可以传入一个界面,但很有可能需要进行重大的重构.@satish应该发布他的代码,让我们玩得开心:)
这是一般的好建议,但它没有回答这个问题.
@chillenious我知道:)我自己创建了一个像`LoggerFactory.getLogger(MyClass.class)`这样的方法,我不需要传入类文字.它仍然很少是正确的事情.

4> Ali Dehghani..:
Java 9 - JEP 259:Stack-Walking API

JEP 259为堆栈遍历提供了一种有效的标准API,允许轻松过滤和延迟访问堆栈跟踪中的信息.在Stack-Walking API之前,访问堆栈帧的常用方法是:

Throwable::getStackTraceThread::getStackTrace返回一个StackTraceElement对象数组 ,其中包含每个stack-trace元素的类名和方法名.

SecurityManager::getClassContext是一个受保护的方法,它允许 SecurityManager子类访问类上下文.

JDK内部sun.reflect.Reflection::getCallerClass方法,你不应该使用它

使用这些API通常效率低下:

这些API要求VM急切地捕获整个堆栈的快照,并返回表示整个堆栈的信息.如果调用者只对堆栈中的前几帧感兴趣,则无法避免检查所有帧的成本.

为了找到直接调用者的类,首先获得一个StackWalker:

StackWalker walker = StackWalker
                           .getInstance(StackWalker.Option.RETAIN_CLASS_REFERENCE);

然后要么致电getCallerClass():

Class callerClass = walker.getCallerClass();

walkStackFrameS和获得第1跟前StackFrame:

walker.walk(frames -> frames
      .map(StackWalker.StackFrame::getDeclaringClass)
      .skip(1)
      .findFirst());



5> Nir Duan..:

Oneliner:

Thread.currentThread().getStackTrace()[2].getMethodName()

请注意,您可能需要将2替换为1.



6> Nicholas..:

这个方法做了同样的事情,但更简单,可能更高效,如果你使用反射,它会自动跳过这些帧.唯一的问题是它可能不存在于非Sun JVM中,尽管它包含在JRockit 1.4的运行时类 - > 1.6中.(重点是,它不是公共课).

sun.reflect.Reflection

    /** 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);

至于realFramesToSkip值应该是什么,Sun 1.5和1.6 VM版本java.lang.System,有一个名为getCallerClass()的包保护方法调用sun.reflect.Reflection.getCallerClass(3),但在我的帮助实用程序类中我使用了4,因为有一个辅助类的添加框架调用.


使用JVM实现类是一个非常糟糕的主意.
此外,通过类似的逻辑,您也可以争辩说,无论何时使用不兼容JPA的Hibernate特定功能,这始终是一个"非常糟糕的想法".或者,如果您要使用其他数据库中没有的Oracle特定功能,那么这是一个"非常糟糕的主意".当然,它是一种更安全的心态,对某些用途来说绝对是一个好建议,但是因为它无法使用你所用的软件配置而自动丢弃有用的工具......呃......根本不使用*?这有点太灵活了,有点傻.
指出.我确实指出它不是公共类,java.lang.System中的受保护方法getCallerClass()存在于我所看到的所有1.5+ VM中,包括IBM,JRockit和Sun,但你的断言是保守的.
@Software Monkey,和往常一样,"一切都取决于".做这样的事情来帮助调试或测试日志记录 - 特别是如果它永远不会在生产代码中结束 - 或者如果部署目标完全是开发人员的PC,那么可能会没问题.任何人在这种情况下仍然不这么认为:你需要真正解释"__ally_ bad idea"的推理,而不仅仅是说它很糟糕......
对供应商特定类的无保护使用将提出更高的问题可能性,但如果相关类不存在(或由于某种原因被禁止),则应确定优雅降级的路径.在我看来,一种拒绝使用任何供应商特定类别的政策有点天真.在生产中使用的某些库的源代码中查看,看看是否有人这样做.(sun.misc.Unsafe也许?)
嗯,对不起,如果我伤害了某人的自我(为什么是毫无根据的广告?).=)鉴于我超过你的任意资格,我现在会再次和你谈谈.重新阅读我输入的内容.理解我并不提倡像你建议的那样愚蠢的东西,特别是在生产代码中.我同意我的"类似"逻辑示例并不相同(没有说"相同"的逻辑),并且无可否认,这有些偏离主题.我最初给出的具体例子是调试/测试记录,虽然我无法想象为什么他需要这个,如果它有助于调试问题 - 使用它!

7> Bill K..:

我之前做过这个.您可以创建一个新的异常并在其上抓取堆栈跟踪而不抛出它,然后检查堆栈跟踪.正如另一个答案所说的那样,它的成本非常高 - 不要在紧密的环路中进行.

我之前已经完成了在应用程序上的日志实用程序,其中性能并不重要(性能很少,实际上 - 只要您将结果显示为快速按钮单击等操作).

在您获得堆栈跟踪之前,异常只有.printStackTrace()所以我不得不将System.out重定向到我自己创建的流,然后(new Exception()).printStackTrace(); 重定向System.out并解析流.好玩的东西.



8> VonC..:
     /**
       * Get the method name for a depth in call stack. 
* Utility function * @param depth depth in the call stack (0 means current method, 1 means call method, ...) * @return method name */ public static String getMethodName(final int depth) { final StackTraceElement[] ste = new Throwable().getStackTrace(); //System. out.println(ste[ste.length-depth].getClassName()+"#"+ste[ste.length-depth].getMethodName()); return ste[ste.length - depth].getMethodName(); }

例如,如果您尝试将调用方法行用于调试目的,则需要通过Utility类来编写这些静态方法:(
旧的java1.4代码,仅用于说明潜在的StackTraceElement用法)

        /**
          * Returns the first "[class#method(line)]: " of the first class not equal to "StackTraceUtils". 
* From the Stack Trace. * @return "[class#method(line)]: " (never empty, first class past StackTraceUtils) */ public static String getClassMethodLine() { return getClassMethodLine(null); } /** * Returns the first "[class#method(line)]: " of the first class not equal to "StackTraceUtils" and aclass.
* Allows to get past a certain class. * @param aclass class to get pass in the stack trace. If null, only try to get past StackTraceUtils. * @return "[class#method(line)]: " (never empty, because if aclass is not found, returns first class past StackTraceUtils) */ public static String getClassMethodLine(final Class aclass) { final StackTraceElement st = getCallingStackTraceElement(aclass); final String amsg = "[" + st.getClassName() + "#" + st.getMethodName() + "(" + st.getLineNumber() +")] <" + Thread.currentThread().getName() + ">: "; return amsg; } /** * Returns the first stack trace element of the first class not equal to "StackTraceUtils" or "LogUtils" and aClass.
* Stored in array of the callstack.
* Allows to get past a certain class. * @param aclass class to get pass in the stack trace. If null, only try to get past StackTraceUtils. * @return stackTraceElement (never null, because if aClass is not found, returns first class past StackTraceUtils) * @throws AssertionFailedException if resulting statckTrace is null (RuntimeException) */ public static StackTraceElement getCallingStackTraceElement(final Class aclass) { final Throwable t = new Throwable(); final StackTraceElement[] ste = t.getStackTrace(); int index = 1; final int limit = ste.length; StackTraceElement st = ste[index]; String className = st.getClassName(); boolean aclassfound = false; if(aclass == null) { aclassfound = true; } StackTraceElement resst = null; while(index < limit) { if(shouldExamine(className, aclass) == true) { if(resst == null) { resst = st; } if(aclassfound == true) { final StackTraceElement ast = onClassfound(aclass, className, st); if(ast != null) { resst = ast; break; } } else { if(aclass != null && aclass.getName().equals(className) == true) { aclassfound = true; } } } index = index + 1; st = ste[index]; className = st.getClassName(); } if(resst == null) { //Assert.isNotNull(resst, "stack trace should null"); //NO OTHERWISE circular dependencies throw new AssertionFailedException(StackTraceUtils.getClassMethodLine() + " null argument:" + "stack trace should null"); //$NON-NLS-1$ } return resst; } static private boolean shouldExamine(String className, Class aclass) { final boolean res = StackTraceUtils.class.getName().equals(className) == false && (className.endsWith("LogUtils" ) == false || (aclass !=null && aclass.getName().endsWith("LogUtils"))); return res; } static private StackTraceElement onClassfound(Class aclass, String className, StackTraceElement st) { StackTraceElement resst = null; if(aclass != null && aclass.getName().equals(className) == false) { resst = st; } if(aclass == null) { resst = st; } return resst; }

推荐阅读
ERIK又
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有