【问题标题】:How do I find the caller of a method using stacktrace or reflection?如何使用堆栈跟踪或反射找到方法的调用者?
【发布时间】:2010-09-30 02:15:36
【问题描述】:

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

【问题讨论】:

  • 只是想知道,但你为什么需要这样做?
  • 我有一个带有通知事件的父类(MVC 模型),并且只有我的子类的设置器调用此方法。我不想用多余的参数乱扔我的代码。我宁愿让父类中的方法找出调用它的setter。
  • @Sathish 听起来你应该重新考虑那个设计
  • @Juliet 作为重构大量代码的一部分,最近我更改了许多东西使用的方法。有一种方法可以检测代码是否正确使用了新方法,所以我打印了在这些情况下调用它的类和行号。除了记录之外,我认为这样的事情没有真正的目的。虽然我现在有点想编写 API,如果调用方法名为 foo,则抛出 DontNameYourMethodFooException
  • 我发现能够让我的方法的调用者成为一个非常宝贵的调试工具:这就是网络搜索将我带到这里的原因。如果从多个地方调用我的方法,是否在正确的时间从正确的位置调用它?正如@Cruncher 所提到的,在调试或日志记录之外,用处最多可能是有限的。

标签: java stack-trace


【解决方案1】:
StackTraceElement[] stackTraceElements = Thread.currentThread().getStackTrace()

根据 Javadocs:

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

StackTraceElementgetClassName()getFileName()getLineNumber()getMethodName()

您必须尝试确定您想要的索引 (可能是stackTraceElements[1][2])。

【讨论】:

  • 我应该注意到 getStackTrace() 仍然会创建一个异常,所以这并不是真的更快——只是更方便。
  • 注意这个方法不会给你调用者,而只会给你调用者的类型。您将不会引用调用您的方法的对象。
  • 只是附带说明,但在 1.5 JVM 上 Thread.currentThread().getStackTrace() 似乎比创建新的 Exception() 慢很多(大约慢 3 倍)。但正如已经指出的那样,无论如何,您都不应该在性能关键领域使用这样的代码。 ;) 1.6 JVM 似乎只慢了约 10%,正如 Software Monkey 所说,它比“新异常”方式更好地表达了意图。
  • @Eelco Thread.currentThread() 很便宜。 Thread.getStackTrace() 很昂贵,因为与 Throwable.fillInStackTrace() 不同,无法保证该方法被它正在检查的同一线程调用,因此 JVM 必须创建一个“安全点”——锁定堆和堆栈。请参阅此错误报告:bugs.sun.com/bugdatabase/view_bug.do?bug_id=6375302
  • @JoachimSauer 你知道获得对调用该方法的对象的引用的方法吗?
【解决方案2】:

注意:如果您使用的是 Java 9 或更高版本,则应使用StackWalker.getCallerClass(),如Ali Dehghani's answer 中所述。

出于历史原因,以下不同方法的比较主要是有趣的。


可以在this request for enhancement 的评论中找到替代解决方案。 它使用自定义SecurityManagergetClassContext() 方法,并且似乎比堆栈跟踪方法更快。

以下程序测试不同建议方法的速度(最有趣的是内部类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 似乎是最快的。

更新

正如 lyomithis comment 中指出的那样,sun.reflect.Reflection.getCallerClass() 方法已在 Java 7 更新 40 中默认禁用,并在 Java 8 中完全删除。在 this issue in the Java bug database 中了解更多信息。

更新 2

正如 zammbi 所发现的,Oracle 是 forced to back out of the change 删除了 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.

【讨论】:

  • 是的,好像是这样。但请注意,我在示例中给出的时间是一百万次调用 - 所以取决于你如何使用它可能不是问题。
  • 对我来说,从我的项目中删除反射导致速度提高了 10 倍。
  • 是的,一般来说反射很慢(参见例如stackoverflow.com/questions/435553/java-reflection-performance),但在这种特定情况下,使用内部 sun.reflect.Reflection 类是最快的。
  • 其实不需要。您可以通过修改上面的代码以打印返回的 className 来验证它(我建议将循环计数减少到 1)。您将看到所有方法都返回相同的类名 - TestGetCallerClassName。
  • getCallerClass 已弃用,将在 7u40 中删除.. 悲伤 :(
【解决方案3】:

Java 9 - JEP 259:Stack-Walking API

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

Throwable::getStackTraceThread::getStackTrace 返回一个数组 StackTraceElement 对象,包含类名和方法 每个堆栈跟踪元素的名称。

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 并获得前面的第一个StackFrame

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

【讨论】:

    【解决方案4】:

    听起来您试图避免将对 this 的引用传递到方法中。传递this 比通过当前堆栈跟踪找到调用者要好得多。 重构为更面向对象的设计会更好。您不需要知道调用者。如有必要,传递一个回调对象。

    【讨论】:

    • ++ 知道调用者信息太多。如果必须,您可以传入一个接口,但很有可能需要进行重大重构。 @satish 应该发布他的代码,让我们玩得开心:)
    • 存在想要这样做的正当理由。例如,我有几次发现它在测试过程中很有帮助。
    • @chillenious 我知道 :) 我自己创建了一个像 LoggerFactory.getLogger(MyClass.class) 这样的方法,我不必传入类文字。这仍然很少是正确的做法。
    • 一般来说这是个好建议,但它不能回答问题。
    • 在实现 .NET INotifyPropertyChanged 接口时,获取有关调用者的信息可能是正确的设计决策的具体示例。虽然这个特定示例不在 Java 中,但在尝试将字段/getter 建模为反射的字符串时,同样的问题可能会显现出来。
    【解决方案5】:

    Oneliner

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

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

    【讨论】:

    • Android 的小修正。 Thread.currentThread().getStackTrace()[3].getMethodName()返回调用者方法名
    【解决方案6】:

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

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

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

    【讨论】:

    • 使用 JVM 实现类是一个真的坏主意。
    • 注明。我确实指定它不是公共类,并且 java.lang.System 中的受保护方法 getCallerClass() 存在于我看过的所有 1.5+ 虚拟机中,包括 IBM、JRockit 和 Sun,但您的断言保守地说是合理的.
    • @Software Monkey,和往常一样,“这一切都取决于”。做这样的事情来帮助调试或测试日志记录——特别是如果它永远不会出现在生产代码中——或者如果部署目标严格地是开发人员的 PC,可能会很好。即使在这种情况下仍然有不同想法的任何人:您需要真正解释“真的坏主意”的推理,而不是仅仅说它不好......
    • 此外,通过类似的逻辑,您还可以争辩说,每当您使用与 JPA 不兼容的特定于 Hibernate 的功能时,这始终是一个“真的坏主意”。或者,如果您要使用其他数据库中没有的特定于 Oracle 的功能,那么这是一个“真的坏主意”。当然,这是一种更安全的心态,并且对于某些用途来说绝对是好的建议,但会自动丢弃有用的工具,因为它不适用于您正在使用的软件配置,呃..根本不使用?这有点太不灵活了,有点傻。
    • 不加保护地使用供应商特定的类会带来更高的问题可能性,但如果相关类不存在(或由于某种原因被禁止),则应该确定一条优雅降级的路径。在我看来,完全拒绝使用任何供应商特定类的政策有点幼稚。查看您在生产中使用的一些库的源代码,看看它们中的任何一个是否这样做。 (sun.misc.Unsafe 也许?)
    【解决方案7】:
         /**
           * 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();
          }
    

    例如,如果您尝试获取调用方法行以进行调试,则需要越过编写这些静态方法的实用程序类:
    (旧的 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;
          }
    

    【讨论】:

    • 我需要一些适用于 Java 1.4 的东西,这个答案非常有帮助!谢谢!
    【解决方案8】:

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

    我之前已经为应用程序上的日志记录实用程序完成了它,其中性能并不重要(实际上,性能根本不重要 - 只要您将结果显示为诸如快速单击按钮之类的操作)。

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

    【讨论】:

    • 酷;不用扔吗?
    • 不,至少我记得是这样,几年没做过了,但我很确定新异常只是创建一个对象,抛出异常并没有' 除了将它传递给 catch() 子句之外,什么都不做。
    • 整洁。我倾向于抛出它来模拟一个实际的异常。
    • 不,因为 Java 5 有一个方法在 Thread 上获取当前堆栈作为 StackTraceElements 数组;它仍然不便宜,但比旧的异常解析解决方案便宜。
    • @Software Monkey 虽然我确信它更合适,但你凭什么说它更便宜?我假设会使用相同的机制,如果没有,为什么在做同样的事情时让它变慢?
    【解决方案9】:
    private void parseExceptionContents(
          final Exception exception,
          final OutputStream out)
       {
          final StackTraceElement[] stackTrace = exception.getStackTrace();
          int index = 0;
          for (StackTraceElement element : stackTrace)
          {
             final String exceptionMsg =
                  "Exception thrown from " + element.getMethodName()
                + " in class " + element.getClassName() + " [on line number "
                + element.getLineNumber() + " of file " + element.getFileName() + "]";
             try
             {
                out.write((headerLine + newLine).getBytes());
                out.write((headerTitlePortion + index++ + newLine).getBytes() );
                out.write((headerLine + newLine).getBytes());
                out.write((exceptionMsg + newLine + newLine).getBytes());
                out.write(
                   ("Exception.toString: " + element.toString() + newLine).getBytes());
             }
             catch (IOException ioEx)
             {
                System.err.println(
                     "IOException encountered while trying to write "
                   + "StackTraceElement data to provided OutputStream.\n"
                   + ioEx.getMessage() );
             }
          }
       }
    

    【讨论】:

      【解决方案10】:

      这是我根据本主题中显示的提示编写的部分代码。 希望对您有所帮助。

      (请随时提出改进此代码的任何建议,请告诉我)

      计数器:

      public class InstanceCount{
          private static Map<Integer, CounterInstanceLog> instanceMap = new HashMap<Integer, CounterInstanceLog>();
      private CounterInstanceLog counterInstanceLog;
      
      
          public void count() {
              counterInstanceLog= new counterInstanceLog();
          if(counterInstanceLog.getIdHashCode() != 0){
          try {
              if (instanceMap .containsKey(counterInstanceLog.getIdHashCode())) {
               counterInstanceLog= instanceMap .get(counterInstanceLog.getIdHashCode());
          }
      
          counterInstanceLog.incrementCounter();
      
                  instanceMap .put(counterInstanceLog.getIdHashCode(), counterInstanceLog);
          }
      
          (...)
      }
      

      还有对象:

      public class CounterInstanceLog{
          private int idHashCode;
          private StackTraceElement[] arrayStackTraceElements;
          private int instanceCount;
          private String callerClassName;
      
          private StackTraceElement getProjectClasses(int depth) {
            if(depth< 10){
              getCallerClassName(sun.reflect.Reflection.getCallerClass(depth).getName());
              if(getCallerClassName().startsWith("com.yourproject.model")){
                  setStackTraceElements(Thread.currentThread().getStackTrace());
                  setIdHashCode();
              return arrayStackTraceElements[depth];
              }
              //+2 because one new item are added to the stackflow
              return getProjectClasses(profundidade+2);           
            }else{
              return null;
            }
          }
      
          private void setIdHashCode() {
              if(getNomeClasse() != null){
                  this.idHashCode = (getCallerClassName()).hashCode();
              }
          }
      
          public void incrementaContador() {
          this.instanceCount++;
      }
      
          //getters and setters
      
          (...)
      
      
      
      }
      

      【讨论】:

        【解决方案11】:
        import java.io.ByteArrayOutputStream;
        import java.io.IOException;
        import java.io.PrintWriter;
        
        class DBConnection {
            String createdBy = null;
        
            DBConnection(Throwable whoCreatedMe) {
                ByteArrayOutputStream os = new ByteArrayOutputStream();
                PrintWriter pw = new PrintWriter(os);
                whoCreatedMe.printStackTrace(pw);
                try {
                    createdBy = os.toString();
                    pw.close();
                    os.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        
        public class ThrowableTest {
        
            public static void main(String[] args) {
        
                Throwable createdBy = new Throwable(
                        "Connection created from DBConnectionManager");
                DBConnection conn = new DBConnection(createdBy);
                System.out.println(conn.createdBy);
            }
        }
        

        public static interface ICallback<T> { T doOperation(); }
        
        
        public class TestCallerOfMethod {
        
            public static <T> T callTwo(final ICallback<T> c){
                // Pass the object created at callee to the caller
                // From the passed object we can get; what is the callee name like below.
                System.out.println(c.getClass().getEnclosingMethod().getName());
                return c.doOperation();
            }
        
            public static boolean callOne(){
                ICallback callBackInstance = new ICallback(Boolean){
                    @Override
                    public Boolean doOperation() 
                    {
                        return true;
                    }
                };
                return callTwo(callBackInstance);
            }
        
            public static void main(String[] args) {
                 callOne();
            }
        }
        

        【讨论】:

          【解决方案12】:

          使用这个方法:-

           StackTraceElement[] stacktrace = Thread.currentThread().getStackTrace();
           stackTraceElement e = stacktrace[2];//maybe this number needs to be corrected
           System.out.println(e.getMethodName());
          

          方法示例代码的调用者在这里:-

          public class TestString {
          
              public static void main(String[] args) {
                  TestString testString = new TestString();
                  testString.doit1();
                  testString.doit2();
                  testString.doit3();
                  testString.doit4();
              }
          
              public void doit() {
                  StackTraceElement[] stacktrace = Thread.currentThread().getStackTrace();
                  StackTraceElement e = stacktrace[2];//maybe this number needs to be corrected
                  System.out.println(e.getMethodName());
              }
          
              public void doit1() {
                  doit();
              }
          
              public void doit2() {
                  doit();
              }
          
              public void doit3() {
                  doit();
              }
          
              public void doit4() {
                  doit();
              }
          }
          

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2015-06-18
            • 1970-01-01
            • 2015-11-18
            • 2012-01-10
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多