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

后端 未结 12 1148
鱼传尺愫
鱼传尺愫 2020-11-21 13:26

I need to find the caller of a method. Is it possible using stacktrace or reflection?

相关标签:
12条回答
  • 2020-11-21 13:39

    Java 9 - JEP 259: Stack-Walking API

    JEP 259 provides an efficient standard API for stack walking that allows easy filtering of, and lazy access to, the information in stack traces. Before Stack-Walking API, common ways of accessing stack frames were:

    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

    Using these APIs are usually inefficient:

    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.

    In order to find the immediate caller's class, first obtain a StackWalker:

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

    Then either call the getCallerClass():

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

    or walk the StackFrames and get the first preceding StackFrame:

    walker.walk(frames -> frames
          .map(StackWalker.StackFrame::getDeclaringClass)
          .skip(1)
          .findFirst());
    
    0 讨论(0)
  • 2020-11-21 13:39
    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 讨论(0)
  • 2020-11-21 13:40

    use this method:-

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

    Caller of method example Code is here:-

    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();
        }
    }
    
    0 讨论(0)
  • 2020-11-21 13:44

    An alternative solution can be found in a comment to this request for enhancement. It uses the getClassContext() method of a custom SecurityManager and seems to be faster than the stack trace method.

    The following program tests the speed of the different suggested methods (the most interesting bit is in the inner class 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.");
      }
    }
    

    An example of the output from my 2.4 GHz Intel Core 2 Duo MacBook running Java 1.6.0_17:

    Reflection: 10.195 ms.
    Current Thread StackTrace: 5886.964 ms.
    Throwable StackTrace: 4700.073 ms.
    SecurityManager: 1046.804 ms.
    

    The internal Reflection method is much faster than the others. Getting a stack trace from a newly created Throwable is faster than getting it from the current Thread. And among the non-internal ways of finding the caller class the custom SecurityManager seems to be the fastest.

    Update

    As lyomi points out in this comment the sun.reflect.Reflection.getCallerClass() method has been disabled by default in Java 7 update 40 and removed completely in Java 8. Read more about this in this issue in the Java bug database.

    Update 2

    As zammbi has found, Oracle was forced to back out of the change that removed the sun.reflect.Reflection.getCallerClass(). It is still available in Java 8 (but it is deprecated).

    Update 3

    3 years after: Update on timing with current 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.
    
    0 讨论(0)
  • 2020-11-21 13:45

    Oneliner:

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

    Note that you might need to replace the 2 with 1.

    0 讨论(0)
  • 2020-11-21 13:45
    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);
        }
    }
    

    OR

    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 讨论(0)
提交回复
热议问题