Comment est-ce que je trouve l’appelant d’une méthode utilisant le stacktrace ou la reflection?

J’ai besoin de trouver l’appelant d’une méthode. Est-il possible d’utiliser le stacktrace ou la reflection?

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

Selon les Javadocs:

Le dernier élément du tableau représente le bas de la stack, qui est l’invocation la moins récente de la séquence.

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

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

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

Le programme suivant teste la vitesse des différentes méthodes suggérées (le bit le plus intéressant se trouve 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 Ssortingng getCallerClassName(int callStackDepth); public abstract Ssortingng getMethodName(); } /** * Uses the internal Reflection class */ private static class ReflectionMethod extends GetCallerClassNameMethod { public Ssortingng getCallerClassName(int callStackDepth) { return sun.reflect.Reflection.getCallerClass(callStackDepth).getName(); } public Ssortingng getMethodName() { return "Reflection"; } } /** * Get a stack trace from the current thread */ private static class ThreadStackTraceMethod extends GetCallerClassNameMethod { public Ssortingng getCallerClassName(int callStackDepth) { return Thread.currentThread().getStackTrace()[callStackDepth].getClassName(); } public Ssortingng getMethodName() { return "Current Thread StackTrace"; } } /** * Get a stack trace from a new Throwable */ private static class ThrowableStackTraceMethod extends GetCallerClassNameMethod { public Ssortingng getCallerClassName(int callStackDepth) { return new Throwable().getStackTrace()[callStackDepth].getClassName(); } public Ssortingng getMethodName() { return "Throwable StackTrace"; } } /** * Use the SecurityManager.getClassContext() */ private static class SecurityManagerMethod extends GetCallerClassNameMethod { public Ssortingng getCallerClassName(int callStackDepth) { return mySecurityManager.getCallerClassName(callStackDepth); } public Ssortingng getMethodName() { return "SecurityManager"; } /** * A custom security manager that exposes the getClassContext() information */ static class MySecurityManager extends SecurityManager { public Ssortingng getCallerClassName(int callStackDepth) { return getClassContext()[callStackDepth].getName(); } } private final static MySecurityManager mySecurityManager = new MySecurityManager(); } /** * Test all four methods */ public static void main(Ssortingng[] args) { testMethod(new ReflectionMethod()); testMethod(new ThreadStackTraceMethod()); testMethod(new ThrowableStackTraceMethod()); testMethod(new SecurityManagerMethod()); } private static void testMethod(GetCallerClassNameMethod method) { long startTime = System.nanoTime(); Ssortingng 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 sortie de mon MacBook Intel Core 2 Duo à 2,4 GHz sous 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 reflection interne est beaucoup plus rapide que les autres. Obtenir une trace de stack à partir d'un object Throwable nouvellement créé est plus rapide que de l'obtenir à partir du Thread actuel. Et parmi les moyens non internes de trouver la classe appelant, le SecurityManager personnalisé semble être le plus rapide.

Mettre à jour

Comme le signale lyomi dans ce commentaire, la méthode sun.reflect.Reflection.getCallerClass() a été désactivée par défaut dans Java 7 Update 40 et complètement supprimée en Java 8. Pour en savoir plus, sun.reflect.Reflection.getCallerClass() la sun.reflect.Reflection.getCallerClass() Base de données des bogues Java .

Mise à jour 2

Comme zammbi l' a constaté, Oracle a été contraint de se retirer du changement qui a supprimé sun.reflect.Reflection.getCallerClass() . Il est toujours disponible dans Java 8 (mais il est obsolète).

Mise à jour 3

3 ans après: mise à jour sur le 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. 

On dirait que vous essayez d’éviter de faire référence à this dans la méthode. Passer this est bien mieux que de trouver l’appelant via la trace de stack actuelle. Refactoring à un design plus OO est encore mieux. Vous ne devriez pas avoir besoin de connaître l’appelant. Passez un object de rappel si nécessaire.

Java 9 – JEP 259: API Stack-Walking

JEP 259 fournit une API standard efficace pour le cheminement de stack qui permet de filtrer et d’accéder facilement aux informations contenues dans les traces de stack. Avant l’API Stack-Walking, les moyens les plus courants d’accéder aux frameworks de stack étaient les suivants:

Throwable::getStackTrace et Thread::getStackTrace renvoient un tableau d’objects StackTraceElement contenant le nom de la classe et le nom de la méthode de chaque élément stack-trace.

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

sun.reflect.Reflection::getCallerClass interne à 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 la machine virtuelle capture avec empressement un instantané de la stack entière et renvoie des informations représentant la stack entière. Il n’y a aucun moyen d’éviter le coût de l’examen de toutes les images si l’appelant n’est intéressé que par les premières images de la stack.

Pour trouver la classe de l’appelant immédiat, commencez par obtenir un StackWalker :

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

Ensuite, appelez getCallerClass() :

 Class callerClass = walker.getCallerClass(); 

ou walk le StackFrame s et obtenir le premier précédent StackFrame :

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

Cette méthode fait la même chose mais un peu plus simplement et peut-être un peu plus performante et dans le cas où vous utilisez la reflection, elle saute automatiquement ces images. Le seul problème est qu’il peut ne pas être présent dans les machines virtuelles Java non Sun, bien qu’il soit inclus dans les classes d’exécution de JRockit 1.4 -> 1.6. (Le point est, ce n’est pas un cours public ).

 sun.reflect.Reflection /** Returns the class of the method realFramesToSkip 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 getCallerClass(0) 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 realFramesToSkip valeur de realFramesToSkip , les versions de Sun 1.5 et 1.6 VM de java.lang.System , il existe une méthode protégée par un paquet appelée getCallerClass () qui appelle sun.reflect.Reflection.getCallerClass(3) , mais dans mon utilitaire de classe utilitaire j’ai utilisé 4 car il y a le cadre ajouté de l’invocation de la classe d’assistance.

Oneliner :

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

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

  /** * Get the method name for a depth in call stack. 
* Utility function * @param depth depth in the call stack (0 means current method, 1 means call method, ...) * @return method name */ public static Ssortingng 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 la méthode appelante à des fins de débogage, vous devez passer 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". 
* From the Stack Trace. * @return "[class#method(line)]: " (never empty, first class past StackTraceUtils) */ public static Ssortingng getClassMethodLine() { return getClassMethodLine(null); } /** * Returns the first "[class#method(line)]: " of the first class not equal to "StackTraceUtils" and aclass.
* 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 Ssortingng getClassMethodLine(final Class aclass) { final StackTraceElement st = getCallingStackTraceElement(aclass); final Ssortingng 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.
* Stored in array of the callstack.
* 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]; Ssortingng 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; }

J’ai déjà fait ça. Vous pouvez simplement créer une nouvelle exception et y saisir la trace de la stack sans la jeter, puis examiner la trace de la stack. Comme le dit l’autre réponse, cela coûte très cher – ne le faites pas en boucle.

Je l’ai déjà fait pour un utilitaire de journalisation sur une application où les performances importaient peu (en réalité, les performances importent peu – du moment que vous affichez le résultat dans une action telle qu’un clic sur un bouton).

C’était avant que vous puissiez obtenir la trace de la stack, les exceptions avaient juste .printStackTrace () donc j’ai dû redirect System.out vers un stream de ma propre création, puis (nouvelle Exception ()). PrintStackTrace (); Redirection de System.out et parsing le stream. Truc amusant.

 private void parseExceptionContents( final Exception exception, final OutputStream out) { final StackTraceElement[] stackTrace = exception.getStackTrace(); int index = 0; for (StackTraceElement element : stackTrace) { final Ssortingng 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.toSsortingng: " + element.toSsortingng() + newLine).getBytes()); } catch (IOException ioEx) { System.err.println( "IOException encountered while trying to write " + "StackTraceElement data to provided OutputStream.\n" + ioEx.getMessage() ); } } } 

Voici une partie du code que j’ai créé en fonction des indications présentées dans cette rubrique. J’espère que cela aide.

(N’hésitez pas à faire des suggestions pour améliorer ce code, s’il vous plaît dites-moi)

Le compteur:

 public class InstanceCount{ private static Map instanceMap = new HashMap(); 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’object:

 public class CounterInstanceLog{ private int idHashCode; private StackTraceElement[] arrayStackTraceElements; private int instanceCount; private Ssortingng 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 { Ssortingng createdBy = null; DBConnection(Throwable whoCreatedMe) { ByteArrayOutputStream os = new ByteArrayOutputStream(); PrintWriter pw = new PrintWriter(os); whoCreatedMe.printStackTrace(pw); try { createdBy = os.toSsortingng(); pw.close(); os.close(); } catch (IOException e) { e.printStackTrace(); } } } public class ThrowableTest { public static void main(Ssortingng[] args) { Throwable createdBy = new Throwable( "Connection created from DBConnectionManager"); DBConnection conn = new DBConnection(createdBy); System.out.println(conn.createdBy); } } 

OU

 public static interface ICallback { T doOperation(); } public class TestCallerOfMethod { public static  T callTwo(final ICallback 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(Ssortingng[] args) { callOne(); } } 

utilisez cette méthode: –

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

Appelant de la méthode exemple Code est ici: –

 public class TestSsortingng { public static void main(Ssortingng[] args) { TestSsortingng testSsortingng = new TestSsortingng(); testSsortingng.doit1(); testSsortingng.doit2(); testSsortingng.doit3(); testSsortingng.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(); } } 

J’aime les new Exception("some message to find the exception easier").printStackTrace() plus.