415 votes

Comment puis-je trouver de l'appelant d'une méthode à l'aide de stacktrace ou de réflexion?

J'ai besoin de trouver à l'appelant d'une méthode. Est-il possible à l'aide de stacktrace ou de réflexion?

435voto

Adam Paynter Points 22056
StackTraceElement[] stackTraceElements = Thread.currentThread().getStackTrace()

Selon la documentation Javadoc:

Le dernier élément du tableau représente le bas de la pile, qui est la moins récente d'invocation de méthode dans la séquence.

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

Vous aurez à expérimenter pour déterminer les index que vous souhaitez (probablement en stackTraceElements[1] ou [2]).

221voto

Johan Kaving Points 2364

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

Le programme suivant teste la vitesse des différentes méthodes proposées (le plus intéressant bits est dans l'intérieur de la classe 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 exemple de la sortie de mon 2.4 GHz Intel Core 2 Duo MacBook cours d'exécution 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 Réflexion interne de la méthode est beaucoup plus rapide que les autres. Obtenir une trace de pile d'un nouveau Throwable est plus rapide que de l'obtenir à partir de l'actuel Thread. Et parmi les non-interne manières de trouver de l'appelant classe personnalisée SecurityManager semble être le plus rapide.

Mise à jour

Comme lyomi points dans ce commentaire l' sun.reflect.Reflection.getCallerClass() méthode a été désactivé par défaut en Java 7 update 40 et complètement supprimé dans Java 8. Lire plus à ce sujet dans cette question dans le bug de Java de base de données.

Mise à jour 2

Comme zammbi a trouvé, Oracle a été forcé de se retirer du changement , qui a supprimé l' sun.reflect.Reflection.getCallerClass(). Il est toujours disponible dans Java 8 (mais c'est déconseillé).

36voto

Craig P. Motlin Points 11814

On dirait que vous êtes en essayant d'éviter de passer une référence à l' this dans la méthode. En passant this est beaucoup mieux que de trouver l'appelant par l'état courant de la pile. Refactorisation pour une plus OO design, c'est encore mieux. Vous ne devriez pas besoin de savoir les appelant. Passer un objet de rappel si nécessaire.

10voto

Nicholas Points 8468

Cette méthode fait la même chose mais un peu plus tout simplement et peut-être un peu plus performant et dans le cas où vous êtes à l'aide de la réflexion, il ignore les images automatiquement. Le seul problème est qu'il ne peut pas être présent dans le non-Jvm Sun, bien qu'il soit inclus dans les classes d'exécution de JRockit 1.4-->1.6. (Le Point est, il n'est pas un public de classe).

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

Aussi loin que ce que l' realFramesToSkip de la valeur doit être, le Soleil 1.5 et 1.6 VM versions d' java.lang.System, il y a un paquet protégé méthode appelée getCallerClass() qui appelle sun.reflect.Reflection.getCallerClass(3), mais à mon aide de l'utilitaire de classe, j'ai utilisé 4 car il y a la cadre de la classe helper invocation.

9voto

VonC Points 414372
     /**
       * 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 méthode d'appel en ligne pour les fins de débogage, vous devez passer par l'Utilitaire de la classe dans laquelle vous code de ces méthodes statiques:
(ancien java1.4 code, juste à illustrer le potentiel de StackTraceElement utilisation)

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

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X