如何使用stacktrace或反射找到方法的调用者?


390

我需要找到一个方法的调用者。是否可以使用stacktrace或反射?


5
只是想知道,但是为什么要这样做呢?
朱丽叶

2
我有一个带有通知事件的父类(MVC模型),并且只有我的子类的设置者才调用此方法。我不想用多余的参数乱扔我的代码。我宁愿让父类中的方法找出调用它的设置方法。
Sathish

30
@Sathish听起来您应该重新考虑这种设计
krosenvold

7
@Juliet作为重构大量代码的一部分,最近我更改了一种用于许多事物的方法。有某种方法可以检测代码是否正确使用了新方法,因此在这种情况下,我将打印调用它的类和行号。在日志记录之外,我看不到像这样的东西的真正目的。尽管我现在想编写API,但DontNameYourMethodFooException如果调用方法名为foo ,则抛出a 。
Cruncher 2014年

5
我发现能够使我的方法的调用者获得无价的调试工具:这就是网络搜索将我带到这里的方式。如果从多个位置调用我的方法,是否在正确的时间从正确的位置调用了我的方法?正如@Cruncher所提到的那样,在调试或日志记录之外,有用性可能最多受到限制。
Ogre Psalm33

Answers:


412
StackTraceElement[] stackTraceElements = Thread.currentThread().getStackTrace()

根据Javadocs:

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

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

您将不得不尝试确定所需的索引(可能是stackTraceElements[1][2])。


7
我应该注意,getStackTrace()仍会创建一个Exception,因此这实际上并不快-只是更方便。
迈克尔·迈尔斯

41
请注意,此方法不会为您提供呼叫者,而只会为您提供呼叫者的类型。您将没有对调用您的方法的对象的引用。
约阿希姆·绍尔

3
只是附带说明,但在1.5 JVM上,Thread.currentThread()。getStackTrace()似乎比创建新的Exception()慢得多(大约慢3倍)。但是,正如已经指出的那样,您绝对不应该在性能至关重要的区域中使用这种代码。;)1.6 JVM似乎只慢了约10%,而且正如Software Monkey所说,它表达的意图比“新异常”更好。
GaZ

21
@Eelco Thread.currentThread()很便宜。Thread.getStackTrace()昂贵,因为与Throwable.fillInStackTrace()不同,无法保证该方法由正在检查的同一线程调用,因此JVM必须创建一个“安全点”-锁定堆和堆栈。查看此错误报告:bugs.sun.com/bugdatabase/view_bug.do?bug_id=6375302
David Moles

7
@JoachimSauer您知道一种获取对调用该方法的对象的引用的方法吗?
jophde 2015年

216

可以在对此增强请求的评论中找到替代解决方案。它使用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所发现的那样,Oracle 被迫退出删除了.NET 的更改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.

5
是的,好像是这样。但是请注意,我在示例中给出的计时是针对一百万个电话的-因此,根据您的使用方式,这可能不是问题。
Johan Kaving 2012年

1
对我来说,从项目中消除反射会导致速度提高10倍。
凯文·帕克2012年

1
是的,反射通常很慢(请参见例如stackoverflow.com/questions/435553/java-reflection-performance),但是在这种特定情况下,使用内部sun.reflect.Reflection类是最快的。
Johan Kaving 2012年

1
实际上并不需要。您可以通过修改上面的代码来打印返回的className来验证它(我建议将循环数减少为1)。您将看到所有方法都返回相同的className-TestGetCallerClassName。
Johan Kaving

1
getCallerClass已弃用,将在7u40中删除。.sad :(
lyomi

36

听起来您正在尝试避免将引用传递给this方法。传递this比通过当前堆栈跟踪查找调用方更好。 重构为更多的OO设计甚至更好。 您不需要认识呼叫者。如有必要,传递一个回调对象。


6
++知道调用方的信息太多。如果需要,可以传入一个接口,但是很有可能需要进行重大的重构。@satish应该发布他的代码,让我们玩一些它:)
Bill K

15
存在这样做的正当理由。例如,在某些情况下,我发现它对测试很有帮助。
Eelco '02

2
@chillenious我知道:)我自己创建了一种方法,就像LoggerFactory.getLogger(MyClass.class)不需要在类文字中传递的方法一样。它仍然很少是正确的事情。
Craig P. Motlin

6
一般而言,这是一个很好的建议,但不能回答问题。
纳文2013年

1
实现.NET INotifyPropertyChanged接口时,一个具体的示例可能是正确的设计决策来获取有关调用方的信息。尽管此特定示例不在Java中,但在尝试将字段/获取器建模为Reflection的字符串时,同样的问题也会显现出来。
克里斯·克瑞克斯

30

Java 9-JEP 259:堆栈步行API

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

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

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

sun.reflect.Reflection::getCallerClass您不应该使用的JDK内部方法

使用这些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());

15

Oneliner

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

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


10

此方法执行相同的操作,但性能稍有提高,并且性能可能更高,如果您使用反射,它将自动跳过那些帧。唯一的问题是,尽管它已包含在JRockit 1.4-> 1.6的运行时类中,但它可能不会在非Sun JVM中出现。(要点是,它不是公共类)。

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

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


16
使用JVM实现类是一个非常糟糕的主意。
劳伦斯·多尔

7
注意。我确实指定了它不是一个公共类,并且java.lang.System中受保护的方法getCallerClass()在我查看过的所有1.5+ VM中都存在,包括IBM,JRockit和Sun,但您的断言是合理的。
尼古拉斯

6
@Software Monkey和往常一样,“全都取决于”。做这样的事情来帮助调试或测试日志记录-特别是如果它永远不会最终出现在生产代码中-或者如果部署目标严格是开发人员的PC,那么可能会很好。即使在这种情况下,任何人仍然认为不是这样的人:您需要真正地解释“ 非常糟糕的主意”推理,而不只是说这是不好的……

8
同样,通过类似的逻辑,您也可能会争辩说,只要您使用与JPA不兼容的特定于Hibernate的功能,那总是一个“ 非常糟糕的主意”。或者,如果您要使用Oracle特有的功能,而这些功能在其他数据库中是不可用的,那么这是一个“ 非常糟糕的主意”。当然,这是一种安全的心态,并且对于某些用途绝对是很好的建议,但是会因为它无法与您正在使用的软件配置一起使用而自动放弃有用的工具,呃.. 根本不使用?有点太不灵活,有点傻。

5
不加保护地使用特定于供应商的类将很可能出现问题,但是如果不存在(或由于某些原因而被禁止)该类,则应该确定一条适当降级的途径。在我看来,全面拒绝使用任何特定于供应商的类的策略有些天真。在生产中使用的某些库的源代码中查找,看看是否有任何一个这样做。(可能是sun.misc。不安全吗?)
尼古拉斯

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

例如,如果尝试获取用于调试目的的调用方法行,则需要
跳过在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;
      }

我需要一些与Java 1.4兼容的东西,这个答案非常有帮助!谢谢!
RGO 2014年

6

我以前做过 您可以创建一个新的异常并在不引发异常的情况下获取堆栈跟踪,然后检查堆栈跟踪。正如另一个答案所言,这是非常昂贵的-不要紧紧地做。

我之前在应用程序上的日志记录实用程序就已经做到了这一点,该应用程序的性能并不重要(实际上,性能几乎不很重要,只要您将结果显示给诸如单击按钮之类的操作即可)。

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


凉; 你不必扔吗?
krosenvold

不,至少我是这样记得的,几年来我都没有做过,但是我敢肯定,新建一个异常只是创建一个对象,并且抛出异常对它没有任何作用,除非通过它到catch()子句。
比尔K

整齐。我倾向于抛出它来模拟实际的异常。
Sathish

不,因为Java 5有一个在Thread上的方法,以将当前堆栈作为StackTraceElements数组获取;它仍然不便宜,但是比旧的异常解析解决方案便宜。
劳伦斯·多尔

@Software Monkey尽管我确定它更合适,但是什么让您说它更便宜?我以为会使用相同的机制,如果没有,为什么做同样的事情时要慢一点呢?
比尔K

1
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() );
         }
      }
   }

0

这是我根据本主题中显示的提示编写的代码的一部分。希望能帮助到你。

(欢迎提出任何建议来改进此代码,请告诉我)

柜台:

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

    (...)



}

0
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();
    }
}

0

使用这种方法:

 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();
    }
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.