¿Cómo encuentro la persona que llama a un método usando stacktrace o reflection?

12 minutos de lectura

¿Como encuentro la persona que llama a un metodo usando
Satish

Necesito encontrar la persona que llama de un método. ¿Es posible usar stacktrace o reflexión?

  • Solo me preguntaba, pero ¿por qué necesitarías hacer esto?

    – Julieta

    7 de enero de 2009 a las 17:51

  • Tengo una clase principal (modelo MVC) con un evento notificador y solo los configuradores de mis subclases llaman a este método. No quiero ensuciar mi código con un argumento redundante. Prefiero dejar que el método en la clase principal descubra el setter que lo llamó.

    – Sathish

    7 de enero de 2009 a las 17:56

  • @Sathish Parece que deberías repensar ese diseño

    – krosenvold

    7 de enero de 2009 a las 17:59

  • @Juliet Como parte de la refactorización de una gran cantidad de código, recientemente cambié un método que utilizan muchas cosas. Hay una cierta forma de detectar si el código estaba usando el nuevo método correctamente, así que estaba imprimiendo la clase y el número de línea que lo llamaba en esos casos. Fuera del registro, no veo ningún propósito real para algo como esto. Aunque ahora quiero escribir API que arrojen un DontNameYourMethodFooException si el método de llamada se llama foo.

    – Triturador

    16 mayo 2014 a las 20:06


  • Encuentro que poder obtener la persona que llama a mi método es una herramienta de depuración invaluable: así es como una búsqueda en la web me trajo aquí. Si se llama a mi método desde varios lugares, ¿se está llamando desde la ubicación correcta en el momento correcto? Fuera de la depuración o el registro, la utilidad probablemente sea limitada en el mejor de los casos, como menciona @Cruncher.

    – Ogro Salmo33

    19 de agosto de 2014 a las 12:11

¿Como encuentro la persona que llama a un metodo usando
Adán Paynter

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

De acuerdo con los Javadocs:

El último elemento de la matriz representa la parte inferior de la pila, que es la invocación de método menos reciente en la secuencia.

A StackTraceElement posee getClassName(), getFileName(), getLineNumber() y getMethodName().

Tendrá que experimentar para determinar qué índice desea (probablemente stackTraceElements[1] o [2]).

  • Debo señalar que getStackTrace() todavía crea una excepción, por lo que esto no es realmente más rápido, solo más conveniente.

    – Michael Myers

    7 de enero de 2009 a las 18:08

  • Tenga en cuenta que este método no le dará la persona que llama, sino solo la tipo de la persona que llama. No tendrá una referencia al objeto que llama a su método.

    – Joaquín Sauer

    7 de enero de 2009 a las 18:30

  • Solo una nota al margen, pero en un 1.5 JVM Thread.currentThread().getStackTrace() parece ser mucho más lento que crear una nueva excepción() (aproximadamente 3 veces más lento). Pero como ya se señaló, de todos modos no debería usar un código como este en un área crítica para el rendimiento. 😉 Una JVM 1.6 solo parece ser ~ 10% más lenta y, como dijo Software Monkey, expresa la intención mejor que la forma de “nueva excepción”.

    – GaZ

    7 de julio de 2009 a las 14:42

  • @Eelco Thread.currentThread() es barato. Thread.getStackTrace() es costoso porque, a diferencia de Throwable.fillInStackTrace(), no hay garantía de que el método sea llamado por el mismo subproceso que está examinando, por lo que la JVM tiene que crear un “punto seguro”, bloqueando el montón y la pila. Vea este informe de error: bugs.sun.com/bugdatabase/view_bug.do?bug_id=6375302

    – David Moles

    8 dic 2011 a las 23:30

  • @JoachimSauer, ¿conoce alguna forma de obtener una referencia al objeto que llama al método?

    – jophde

    19 de enero de 2015 a las 22:31

¿Como encuentro la persona que llama a un metodo usando
johan kaving

Nota: si está usando Java 9 o posterior, debe usar StackWalker.getCallerClass() como se describe en la respuesta de Ali Dehghani.

La comparación de los diferentes métodos a continuación es principalmente interesante por razones históricas.


Una solución alternativa se puede encontrar en un comentario a esta solicitud de mejora. utiliza el getClassContext() método de una costumbre SecurityManager y parece ser más rápido que el método de seguimiento de pila.

El siguiente programa prueba la velocidad de los diferentes métodos sugeridos (el bit más interesante está en la clase interna 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.");
  }
}

Un ejemplo de la salida de mi MacBook Intel Core 2 Duo de 2,4 GHz con Java 1.6.0_17:

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

El método de reflexión interna es mucho más rápido que los demás. Obtener un seguimiento de pila de una nueva creación Throwable es más rápido que obtenerlo de la corriente Thread. Y entre las formas no internas de encontrar la clase de llamada, la costumbre SecurityManager parece ser el más rápido.

Actualizar

Como lyomi señala en este comentario la sun.reflect.Reflection.getCallerClass() El método se ha desactivado de forma predeterminada en la actualización 40 de Java 7 y se ha eliminado por completo en Java 8. Lea más sobre esto en este problema en la base de datos de errores de Java.

Actualización 2

Como zammbi ha encontrado, Oracle fue obligado a retirarse del cambio que eliminó el sun.reflect.Reflection.getCallerClass(). Todavía está disponible en Java 8 (pero está obsoleto).

Actualización 3

3 años después: Actualización sobre el tiempo con la JVM actual.

> 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.

  • Sí, eso parece. Pero tenga en cuenta que los tiempos que doy en el ejemplo son para un millón de llamadas, por lo que, dependiendo de cómo esté usando esto, podría no ser un problema.

    – Johan Kaving

    27 de junio de 2012 a las 11:09

  • Para mí, eliminar la reflexión de mi proyecto resultó en un aumento de velocidad de 10x.

    –Kevin Parker

    27 de junio de 2012 a las 14:20

  • Sí, la reflexión en general es lenta (consulte, por ejemplo, stackoverflow.com/questions/435553/java-reflection-performance), pero en este caso específico, usar la clase interna sun.reflect.Reflection es la más rápida.

    – Johan Kaving

    28 de junio de 2012 a las 12:39

  • En realidad no es necesario. Puede verificarlo modificando el código anterior para imprimir el className devuelto (y sugiero reducir el recuento de bucles a 1). Verá que todos los métodos devuelven el mismo nombre de clase: TestGetCallerClassName.

    – Johan Kaving

    7 de agosto de 2012 a las 6:07

  • getCallerClass está en desuso y se eliminará en 7u40 .. triste 🙁

    – lyomi

    5 de agosto de 2013 a las 2:02

1646958430 304 ¿Como encuentro la persona que llama a un metodo usando
Ali Deghani

Java 9 – JEP 259: API de recorrido de pila

JPY 259 proporciona una API estándar eficiente para el recorrido de la pila que permite un filtrado fácil y un acceso diferido a la información en los seguimientos de la pila. Antes de la API Stack-Walking, las formas comunes de acceder a los marcos de pila eran:

Throwable::getStackTrace y Thread::getStackTrace devolver una matriz de
StackTraceElement objetos, que contienen el nombre de clase y el nombre de método de cada elemento de seguimiento de pila.

SecurityManager::getClassContext es un método protegido, que permite una
SecurityManager subclase para acceder al contexto de la clase.

JDK-interno sun.reflect.Reflection::getCallerClass método que no deberías usar de todos modos

El uso de estas API suele ser ineficiente:

Estas API requieren que la máquina virtual capture con entusiasmo una instantánea de toda la pilay devuelven información que representa la pila completa. No hay forma de evitar el costo de examinar todos los marcos si la persona que llama solo está interesada en los pocos marcos superiores de la pila.

Para encontrar la clase de la persona que llama inmediatamente, primero obtenga una StackWalker:

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

Entonces llame al getCallerClass():

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

o walk los StackFrames y obtener el primero anterior StackFrame:

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

1646958430 877 ¿Como encuentro la persona que llama a un metodo usando
Craig P. Motlin

Parece que estás tratando de evitar pasar una referencia a this en el método. Paso this es mucho mejor que encontrar a la persona que llama a través del seguimiento de la pila actual. La refactorización a un diseño más OO es aún mejor. No debería necesitar conocer a la persona que llama. Pase un objeto de devolución de llamada si es necesario.

Un trazador de líneas:

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

Tenga en cuenta que es posible que deba reemplazar el 2 con 1.

  • Pequeña corrección para Android. Thread.currentThread().getStackTrace()[3].getMethodName() devolver el nombre del método de la persona que llama

    – Qamar

    7 dic 2020 a las 11:05

1646958430 310 ¿Como encuentro la persona que llama a un metodo usando
Nicolás

Este método hace lo mismo pero un poco más simple y posiblemente un poco más eficaz y, en caso de que esté utilizando la reflexión, omite esos cuadros automáticamente. El único problema es que puede no estar presente en JVM que no sean de Sun, aunque está incluido en las clases de tiempo de ejecución de JRockit 1.4–>1.6. (El punto es que no es un público clase).

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

En cuanto a lo que realFramesToSkip debería ser, las versiones Sun 1.5 y 1.6 VM de java.lang.Systemhay un método protegido por paquetes llamado getCallerClass() que llama sun.reflect.Reflection.getCallerClass(3)pero en mi clase de utilidad auxiliar usé 4 ya que existe el marco agregado de la invocación de la clase auxiliar.

  • Pequeña corrección para Android. Thread.currentThread().getStackTrace()[3].getMethodName() devolver el nombre del método de la persona que llama

    – Qamar

    7 dic 2020 a las 11:05

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

Por ejemplo, si intenta obtener la línea del método de llamada con fines de depuración, debe pasar la clase de utilidad en la que codifica esos métodos estáticos:
(antiguo código java1.4, solo para ilustrar un posible uso de 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;
      }

  • ¡Necesitaba algo que funcionara con Java 1.4 y esta respuesta fue muy útil! ¡Gracias!

    – RGO

    7 de marzo de 2014 a las 6:03

¿Ha sido útil esta solución?

Esta web utiliza cookies propias y de terceros para su correcto funcionamiento y para fines analíticos y para mostrarte publicidad relacionada con sus preferencias en base a un perfil elaborado a partir de tus hábitos de navegación. Al hacer clic en el botón Aceptar, acepta el uso de estas tecnologías y el procesamiento de tus datos para estos propósitos. Configurar y más información
Privacidad