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

Sat*_*ish 378 java stack-trace

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

Ada*_*ter 403

StackTraceElement[] stackTraceElements = Thread.currentThread().getStackTrace()
Run Code Online (Sandbox Code Playgroud)

根据Javadocs:

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

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

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

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

Joh*_*ing 212

可以在对此增强请求的评论中找到替代解决方案.它使用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.");
  }
}
Run Code Online (Sandbox Code Playgroud)

运行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.
Run Code Online (Sandbox Code Playgroud)

内部反射的方法是比别人快.从新创建的堆栈跟踪获取堆栈跟踪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.
Run Code Online (Sandbox Code Playgroud)

  • 是的,似乎就是这样.但请注意,我在示例中给出的时间是一百万次调用 - 所以根据您使用它的方式,这可能不是问题. (5认同)
  • 是的,反射通常很慢(参见例如http://stackoverflow.com/questions/435553/java-reflection-performance),但在这种特定情况下,使用内部 sun.reflect.Reflection 类是最快的。 (2认同)

Cra*_*lin 35

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

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

Ali*_*ani 29

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);
Run Code Online (Sandbox Code Playgroud)

然后要么致电getCallerClass():

Class<?> callerClass = walker.getCallerClass();
Run Code Online (Sandbox Code Playgroud)

walkStackFrameS和获得第1跟前StackFrame:

walker.walk(frames -> frames
      .map(StackWalker.StackFrame::getDeclaringClass)
      .skip(1)
      .findFirst());
Run Code Online (Sandbox Code Playgroud)


Nir*_*uan 12

Oneliner:

Thread.currentThread().getStackTrace()[2].getMethodName()
Run Code Online (Sandbox Code Playgroud)

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

  • Android 版稍作修正。`Thread.currentThread().getStackTrace()[3].getMethodName()` 返回调用者方法名称 (5认同)

Nic*_*las 10

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

sun.reflect.Reflection

    /** 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);
Run Code Online (Sandbox Code Playgroud)

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

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

Bil*_*l K 6

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

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

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


Von*_*onC 6

     /**
       * Get the method name for a depth in call stack. <br />
       * 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();
      }
Run Code Online (Sandbox Code Playgroud)

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

        /**
          * Returns the first "[class#method(line)]: " of the first class not equal to "StackTraceUtils". <br />
          * 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. <br />
          * 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. <br />
       * Stored in array of the callstack. <br />
       * 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;
      }
Run Code Online (Sandbox Code Playgroud)