首页 文章

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

提问于
浏览
343

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

12 回答

  • 201
    StackTraceElement[] stackTraceElements = Thread.currentThread().getStackTrace()
    

    根据Javadocs:

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

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

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

  • 6

    可以在对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.
    

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

    更新

    正如this comment中的 lyomi 指出的那样, 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.
    
  • 21

    听起来你正试图避免将对 this 的引用传递给方法 . 传递 this 比通过当前堆栈跟踪查找调用方更好 . Refactoring to a more OO design is even better. 您不需要知道呼叫者 . 必要时传递回调对象 .

  • 11

    以下是我在本主题中显示的提示中创建的代码的一部分 . 希望能帮助到你 .

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

    柜台:

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

    Java 9 - JEP 259:Stack-Walking API

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

    Throwable :: getStackTrace和Thread :: 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();
    

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

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

    Oneliner

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

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

  • 0

    这种方法做了同样的事情,但更简单,可能更高效,如果你使用反射,它会自动跳过这些帧 . 唯一的问题是它可能不存在于非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);
    

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

  • 34
    /**
           * 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; }
  • 10

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

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

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

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

相关问题