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

StackOverflow https://stackoverflow.com/questions/421280

  •  05-07-2019
  •  | 
  •  

Pregunta

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

¿Fue útil?

Solución

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

Según 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.

Un StackTraceElement tiene getClassName () , getFileName () , getLineNumber () y getMethodName () .

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

Otros consejos

Se puede encontrar una solución alternativa en un comentario a esta solicitud de mejora . Utiliza el método getClassContext () de un SecurityManager personalizado y parece ser más rápido que el método de seguimiento de la 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 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 interno de Reflexión es mucho más rápido que los otros. Obtener un seguimiento de pila de un Throwable recién creado es más rápido que obtenerlo del Thread actual. Y entre las formas no internas de encontrar la clase de la persona que llama, el SecurityManager personalizado parece ser el más rápido.

Actualizar

Como lyomi señala en este comentario el método sun.reflect.Reflection.getCallerClass () 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 ha encontrado zammbi , Oracle era forzado a retirarse del cambio que eliminó el sun.reflect.Reflection.getCallerClass () . Todavía está disponible en Java 8 (pero está en desuso).

Actualización 3

3 años después: Actualización sobre la sincronización 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.

Parece que está intentando evitar pasar una referencia a this en el método. Pasar este es mucho mejor que encontrar a la persona que llama a través del seguimiento actual de la pila. Refactorizar 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.

Java 9 - JEP 259: API de desplazamiento de pila

JEP 259 proporciona una API estándar eficiente para el apilado que permite un filtrado fácil y perezoso Accede a, la información en las pistas de pila. Antes de la API de Stack-Walking, las formas comunes de acceder a los marcos de pila eran:

  

Throwable :: getStackTrace y Thread :: getStackTrace devuelven una matriz de    StackTraceElement , que contienen el nombre y el método de la clase   nombre 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-internal sun.reflect.Reflection :: getCallerClass que no debería utilizar de todos modos

El uso de estas API suele ser ineficiente:

  

Estas API requieren que la VM capture con entusiasmo una instantánea de todo el   stack , y devuelven información que representa la pila completa.   No hay manera de evitar el costo de examinar todos los marcos si el   la persona que llama solo está interesada en los primeros marcos de la pila.

Para encontrar la clase del llamante inmediato, primero obtenga un StackWalker :

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

Luego, llama al getCallerClass () :

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

o camine los StackFrame y obtenga el primer StackFrame anterior:

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

Oneliner :

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

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

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

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 debería ser el valor de realFramesToSkip , las versiones de Sun 1.5 y 1.6 VM de java.lang.System , existe un método protegido por paquete llamado getCallerClass ( ) que llama a sun.reflect.Reflection.getCallerClass (3) , pero en mi clase de utilidad de ayuda, utilicé 4 ya que existe el marco agregado de la invocación de la clase de ayuda.

     /**
       * 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 uso potencial 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;
      }

He hecho esto antes. Simplemente puede crear una nueva excepción y tomar el seguimiento de la pila en él sin lanzarlo, luego examinar el seguimiento de la pila. Sin embargo, como la otra respuesta dice, es extremadamente costoso, no lo hagas en un circuito cerrado.

Lo he hecho antes para una utilidad de registro en una aplicación donde el rendimiento no importaba mucho (el rendimiento rara vez importa mucho, en realidad, siempre que se muestre el resultado de una acción, como hacer clic en un botón rápidamente). ).

Antes de que pudieras obtener el seguimiento de la pila, las excepciones solo tenían .printStackTrace (), así que tuve que redirigir System.out a una secuencia de mi propia creación, luego (new Exception ()). printStackTrace (); Redirigir System.out hacia atrás y analizar la secuencia. Cosas divertidas.

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

Aquí hay una parte del código que hice en base a las sugerencias que se muestran en este tema. Espero que ayude.

(No dude en hacer cualquier sugerencia para mejorar este código, por favor, dígame)

El contador:

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

    (...)
}

Y el objeto:

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

    (...)



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

O

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

utiliza este método: -

 StackTraceElement[] stacktrace = Thread.currentThread().getStackTrace();
 stackTraceElement e = stacktrace[2];//maybe this number needs to be corrected
 System.out.println(e.getMethodName());
El código de ejemplo de la persona que llama al método está aquí: -

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();
    }
}
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top