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

How do I find the caller of a method using stacktrace or reflection?

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


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

根据Javadocs:

The last element of the array represents the bottom of the stack, which is the least recent method invocation in the sequence.

StackTraceElement具有getClassName()getFileName()getLineNumber()getMethodName()

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


在此增强请求的注释中可以找到替代解决方案。
它使用自定义SecurityManagergetClassContext()方法,并且似乎比堆栈跟踪方法快。

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
/**
 * 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的输出示例:

1
2
3
4
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被迫退出删除sun.reflect.Reflection.getCallerClass()的更改。它在Java 8中仍然可用(但已弃用)。

更新3

3年后:使用当前JVM更新计时。

1
2
3
4
5
6
7
8
9
> 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.


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


Java 9-JEP 259:堆栈步行API

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

Throwable::getStackTrace and Thread::getStackTrace return an array of
StackTraceElement objects, which contain the class name and method
name of each stack-trace element.

SecurityManager::getClassContext is a protected method, which allows a
SecurityManager subclass to access the class context.

JDK-internal sun.reflect.Reflection::getCallerClass method which you shouldn't use anyway

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

These APIs require the VM to eagerly capture a snapshot of the entire
stack, and they return information representing the entire stack.
There is no way to avoid the cost of examining all the frames if the
caller is only interested in the top few frames on the stack.

为了找到直接调用者的类,请首先获取StackWalker

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

然后调用getCallerClass()

1
Class< ? > callerClass = walker.getCallerClass();

walk the StackFrame s并获得前面的第一个StackFrame

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

Oneliner:

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

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


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

1
2
3
4
5
6
7
8
9
10
11
12
sun.reflect.Reflection

    /** Returns the class of the method <wyn>realFramesToSkip</wyn>
        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
        <wyn>getCallerClass(0)</wyn> 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,因为辅助类调用增加了框架。


1
2
3
4
5
6
7
8
9
10
11
12
13
     /**
       * 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用法)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
        /**
          * 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;
      }


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

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

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


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
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.
"

               + ioEx.getMessage() );
         }
      }
   }

使用这种方法:

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
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();
    }
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
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);
    }
}

要么

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
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();
    }
}


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

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

柜台:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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);
    }

    (...)
}

和对象:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
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

    (...)



}