Как найти вызывающего метода, используя трассировку стека или отражение? - PullRequest
366 голосов
/ 07 января 2009

Мне нужно найти вызывающего метода. Возможно ли использовать Stacktrace или отражение?

Ответы [ 12 ]

391 голосов
/ 07 января 2009
StackTraceElement[] stackTraceElements = Thread.currentThread().getStackTrace()

Согласно Javadocs:

Последний элемент массива представляет нижнюю часть стека, что является наименее недавним вызовом метода в последовательности.

A StackTraceElement имеет getClassName(), getFileName(), getLineNumber() и getMethodName().

Вам придется поэкспериментировать, чтобы определить, какой индекс вы хотите (возможно stackTraceElements[1] или [2]).

207 голосов
/ 27 мая 2010

Альтернативное решение можно найти в комментарии к этому запросу на улучшение . Он использует 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.");
  }
}

Пример вывода с моего 2,4 ГГц Intel Core 2 Duo MacBook под управлением Java 1.6.0_17:

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 update 40 и полностью удален в Java 8. Подробнее об этом в эта проблема в базе данных ошибок Java .

Обновление 2

Как обнаружил zammbi , Oracle был вынужден отказаться от изменения , которое удалило 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.
34 голосов
/ 07 января 2009

Похоже, вы пытаетесь избежать передачи ссылки на this в метод. Передача this намного лучше, чем поиск вызывающей стороны по текущей трассировке стека. Рефакторинг для более оригинального дизайна еще лучше. Вам не нужно знать вызывающего При необходимости передайте объект обратного вызова.

24 голосов
/ 22 августа 2017

Java 9 - JEP 259: API для хождения по стеку

JEP 259 предоставляет эффективный стандартный API для обхода стека, который позволяет легко фильтровать информацию и легко получать доступ к информации в трассировках стека. До использования Stack-Walking API распространенными способами доступа к фреймам стека были:

Throwable::getStackTrace и Thread::getStackTrace возвращают массив StackTraceElement объекты, которые содержат имя класса и метод имя каждого элемента трассировки стека.

SecurityManager::getClassContext - это защищенный метод, который позволяет SecurityManager подкласс для доступа к контексту класса.

JDK-внутренний sun.reflect.Reflection::getCallerClass метод, который вы не должны использовать в любом случае

Использование этих API обычно неэффективно:

Эти API требуют, чтобы виртуальная машина охотно делала снимок всего stack , и они возвращают информацию, представляющую весь стек. Невозможно избежать затрат на проверку всех кадров, если вызывающий абонент интересуется только несколькими верхними кадрами в стеке.

Чтобы найти класс непосредственного абонента, сначала получите StackWalker:

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

Тогда либо позвоните на getCallerClass():

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

или walk StackFrame с и получите первое предшествующее StackFrame:

walker.walk(frames -> frames
      .map(StackWalker.StackFrame::getDeclaringClass)
      .skip(1)
      .findFirst());
11 голосов
/ 07 июня 2017

Oneliner :

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

Обратите внимание, что вам может понадобиться заменить 2 на 1.

10 голосов
/ 07 января 2009

Этот метод делает то же самое, но немного проще и, возможно, немного более производительно, и, если вы используете отражение, он автоматически пропускает эти кадры. Единственная проблема заключается в том, что он может отсутствовать в JVM не от Sun, хотя он включен в классы выполнения 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 виртуальных версий Sun 1.5 и 1.6 java.lang.System, то существует защищенный пакетный метод getCallerClass (), который вызывает sun.reflect.Reflection.getCallerClass(3), но в моем классе вспомогательных утилит I используется 4, поскольку добавлен фрейм вызова вспомогательного класса.

6 голосов
/ 07 января 2009
     /**
       * 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;
      }
6 голосов
/ 07 января 2009

Я делал это раньше. Вы можете просто создать новое исключение и получить трассировку стека, не выбрасывая его, а затем изучить трассировку стека. Как говорится в другом ответе, это чрезвычайно дорого - не делайте это в узком кругу.

Я делал это раньше для утилиты ведения журналов в приложении, где производительность не имела большого значения (на самом деле производительность вообще не имеет большого значения), если вы отображаете результат для действия, такого как быстрое нажатие кнопки ).

Это было до того, как вы смогли получить трассировку стека, исключения имели только .printStackTrace (), поэтому мне пришлось перенаправить System.out в поток моего собственного создания, затем (new Exception ()). PrintStackTrace (); Перенаправьте System.out обратно и проанализируйте поток. Прикольные вещи.

1 голос
/ 03 февраля 2014
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 голосов
/ 18 января 2014

используйте этот метод: -

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