web-dev-qa-db-fra.com

Comment trouver l'appelant d'une méthode utilisant stacktrace ou réflexion?

Je dois trouver l'appelant d'une méthode. Est-il possible d'utiliser stacktrace ou réflexion?

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

Selon les Javadocs:

Le dernier élément du tableau représente le bas de la pile, qui correspond à l'appel de méthode le moins récent de la séquence.

Une StackTraceElement a getClassName(), getFileName(), getLineNumber() et getMethodName().

Vous devrez expérimenter pour déterminer quel index vous voulez (probablement stackTraceElements[1] ou [2]).

398
Adam Paynter

Une solution alternative peut être trouvée dans un commentaire sur cette demande d'amélioration . Il utilise la méthode getClassContext() d'une personnalisation SecurityManager et semble être plus rapide que la méthode de trace de pile.

Le programme suivant teste la vitesse des différentes méthodes suggérées (le bit le plus intéressant est dans la classe interne 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.");
  }
}

Exemple de sortie de mon MacBook Intel Core 2 Duo cadencé à 2,4 GHz et exécutant Java 1.6.0_17:

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

La méthode de réflexion interne est beaucoup plus rapide que les autres. Obtenir une trace de pile à partir d'un Throwable nouvellement créé est plus rapide que de l'obtenir à partir du Thread actuel. Et parmi les méthodes non internes de recherche de la classe de l'appelant, la personnalisation SecurityManager semble être la plus rapide.

Mise à jour

Comme , lyomi indique dans ce commentaire , la méthode Sun.reflect.Reflection.getCallerClass() a été désactivée par défaut dans Java 7 mise à jour 40 et complètement supprimé dans Java 8. Plus d'informations à ce sujet dans ce problème dans la base de données de bogues Java .

Mise à jour 2

Comme zammbi a découvert, Oracle était obligé de revenir en arrière du changement qui a supprimé la Sun.reflect.Reflection.getCallerClass(). Il est toujours disponible dans Java 8 (mais il est déconseillé).

Mise à jour 3

3 ans après: mise à jour du calendrier avec la JVM actuelle.

> 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.
209
Johan Kaving

On dirait que vous essayez d'éviter de passer une référence à this dans la méthode. Passer this est bien mieux que de trouver l'appelant à travers le tracé actuel de la pile. Refactoriser à un design plus OO est encore mieux. Vous ne devriez pas avoir besoin de connaître l'appelant. Transmettez un objet de rappel si nécessaire.

34
Craig P. Motlin

Java 9 - JEP 259: API Stack-Walking

JEP 259 fournit une API standard efficace pour la marche en pile, qui permet de filtrer facilement les informations contenues dans les traces de pile et d’y accéder facilement. Avant l'API Stack-Walking, les méthodes courantes d'accès aux images de pile étaient les suivantes:

Throwable::getStackTrace et Thread::getStackTrace retournent un tableau d'objets StackTraceElement, contenant le nom de la classe et le nom de la méthode de chaque élément de trace de pile.

SecurityManager::getClassContext est une méthode protégée, qui permet à une sous-classe SecurityManager d'accéder au contexte de la classe.

Méthode JDK-internal Sun.reflect.Reflection::getCallerClass que vous ne devriez pas utiliser de toute façon

L'utilisation de ces API est généralement inefficace:

Ces API requièrent que VM capture rapidement un instantané de la pile entière , et renvoie des informations représentant la pile entière. Il n'y a aucun moyen d'éviter le coût de l'examen de toutes les trames si l'appelant n'est intéressé que par les quelques premières images de la pile.

Pour trouver la classe de l'appelant immédiat, obtenez d'abord un StackWalker:

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

Ensuite, appelez la getCallerClass():

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

ou walk la StackFrames et obtenez le premier précédent StackFrame:

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

Oneliner:

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

Notez que vous devrez peut-être remplacer le 2 par 1.

12
Andro Secy

Cette méthode fait la même chose, mais un peu plus simplement et peut-être un peu plus, et si vous utilisez la réflexion, elle saute ces images automatiquement. Le seul problème est qu'il peut ne pas être présent dans les machines virtuelles autres que Sun, bien qu'il soit inclus dans les classes d'exécution de JRockit 1.4 -> 1.6. (Le fait est que ce n'est pas une classe publique).

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 ce qui concerne la valeur realFramesToSkip, les versions Sun 1.5 et 1.6 VM de Java.lang.System, il existe une méthode de protection de package appelée getCallerClass () qui appelle Sun.reflect.Reflection.getCallerClass(3), mais J'ai utilisé la classe utilitaire d'aide 4 car il y avait le cadre ajouté de l'invocation de la classe d'assistance.

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

Par exemple, si vous essayez d'obtenir la ligne de méthode appelante à des fins de débogage, vous devez aller au-delà de la classe Utility dans laquelle vous codez ces méthodes statiques:
(ancien code Java1.4, juste pour illustrer une utilisation potentielle 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;
      }
6
VonC

J'ai déjà fait ça avant. Vous pouvez simplement créer une nouvelle exception et saisir la trace de la pile dessus sans la lancer, puis examiner la trace de la pile. Comme l’autre réponse le dit cependant, c’est extrêmement coûteux - ne le faites pas dans une boucle serrée.

Je l'avais déjà fait auparavant pour un utilitaire de journalisation sur une application où les performances importaient peu (les performances importent rarement beaucoup, en fait, tant que vous affichez le résultat sous forme d'action, telle qu'un clic de bouton rapide).

C'était avant que vous puissiez obtenir la trace de la pile, les exceptions ne possédaient que .printStackTrace (). J'ai donc dû rediriger System.out vers un flux de ma propre création, puis (nouvelle Exception ()). PrintStackTrace (); Réacheminez System.out vers l'arrière et analysez le flux. Truc amusant.

6
Bill K
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() );
         }
      }
   }
1
AZ_

Voici une partie du code que j'ai créé à partir des astuces présentées dans cette rubrique. J'espère que ça aide.

(N'hésitez pas à faire des suggestions pour améliorer ce code, merci de me le dire)

Le compteur:

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

    (...)
}

Et l'objet:

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

    (...)



}
0
Pmt

utiliser cette méthode: -

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

Le code de l'appelant de la méthode est ici: -

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
Reegan Miranda
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
Kanagavelu Sugumar