it-swarm.com.de

Wie finde ich den Aufrufer einer Methode mithilfe von Stacktrace oder Reflection?

Ich muss den Aufrufer einer Methode finden. Ist es möglich, Stacktrace oder Reflexion zu verwenden?

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

Nach den Javadocs:

Das letzte Element des Arrays stellt den unteren Teil des Stapels dar. Dies ist der zuletzt aufgerufene Methodenaufruf in der Sequenz.

Ein StackTraceElement hat getClassName(), getFileName(), getLineNumber() und getMethodName().

Sie müssen experimentieren, um den gewünschten Index zu bestimmen (wahrscheinlich stackTraceElements[1] Oder [2]).

398
Adam Paynter

Eine alternative Lösung finden Sie in einem Kommentar zu diese Bitte um Verbesserung . Es verwendet die getClassContext() -Methode eines benutzerdefinierten SecurityManager und scheint schneller zu sein als die Stack-Trace-Methode.

Das folgende Programm testet die Geschwindigkeit der verschiedenen vorgeschlagenen Methoden (das interessanteste Bit befindet sich in der inneren Klasse 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.");
  }
}

Ein Beispiel für die Ausgabe von meinem 2,4 GHz Intel Core 2 Duo MacBook mit Java 1.6.0_17:

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

Die interne Reflektionsmethode ist viel schneller als die anderen. Das Abrufen eines Stack-Trace von einem neu erstellten Throwable ist schneller als das Abrufen des aktuellen Thread. Und unter den nicht-internen Möglichkeiten, die Aufruferklasse zu finden, scheint die benutzerdefinierte SecurityManager die schnellste zu sein.

Aktualisieren

Wie lyomi in diesem Kommentar ausführt, wurde die Sun.reflect.Reflection.getCallerClass() -Methode in Java 7 Update 40 und vollständig entfernt in Java 8. Lesen Sie mehr darüber in dieses Problem in der Java Bug-Datenbank .

Update 2

Wie zammbi herausgefunden hat, war Oracle gezwungen, die Änderung rückgängig zu machen , das die Sun.reflect.Reflection.getCallerClass() entfernte. Es ist noch in Java 8 verfügbar (aber veraltet).

Update 3

3 Jahre später: Aktualisierung des Timings mit der aktuellen JVM.

> 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

Klingt so, als würden Sie vermeiden, einen Verweis auf this an die Methode zu übergeben. Die Übergabe von this ist weitaus besser, als den Aufrufer über den aktuellen Stack-Trace zu finden. Das Umgestalten auf ein mehr OO Design ist noch besser. Sie sollten den Anrufer nicht kennen müssen. Übergeben Sie gegebenenfalls ein Rückrufobjekt.

34
Craig P. Motlin

Java 9 - JEP 259: Stack-Walking-API

JEP 259 bietet eine effiziente Standard-API für das Stack-Walking, mit der Informationen in Stack-Traces einfach gefiltert werden können und auf die nur schleppend zugegriffen werden kann. Vor der Stack-Walking-API waren die folgenden Möglichkeiten für den Zugriff auf Stack-Frames gebräuchlich:

Throwable::getStackTrace Und Thread::getStackTrace Geben ein Array von StackTraceElement Objekten zurück, die den Klassennamen und den Methodennamen jedes Stack-Trace-Elements enthalten.

SecurityManager::getClassContext Ist eine geschützte Methode, mit der eine Unterklasse SecurityManager auf den Klassenkontext zugreifen kann.

JDK-interne Sun.reflect.Reflection::getCallerClass Methode, die Sie sowieso nicht verwenden sollten

Die Verwendung dieser APIs ist normalerweise ineffizient:

Für diese APIs ist es erforderlich, dass VM) eifrig eine Momentaufnahme des gesamten Stapels erstellt und Informationen zurückgibt, die den gesamten Stapel darstellen Es gibt keine Möglichkeit, die Kosten für die Prüfung aller Frames zu umgehen, wenn der Aufrufer nur an den obersten Frames auf dem Stapel interessiert ist.

Um die Klasse des unmittelbaren Aufrufers zu finden, erhalten Sie zuerst ein StackWalker:

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

Dann rufen Sie entweder getCallerClass() auf:

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

oder walk das StackFrames und erhalte das erste vorhergehende StackFrame:

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

Oneliner:

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

Beachten Sie, dass Sie möglicherweise die 2 durch 1 ersetzen müssen.

12
Andro Secy

Diese Methode macht das Gleiche, aber etwas einfacher und möglicherweise etwas performanter. Wenn Sie Reflection verwenden, werden diese Frames automatisch übersprungen. Das einzige Problem ist, dass es in Nicht-Sun-JVMs möglicherweise nicht vorhanden ist, obwohl es in den Laufzeitklassen von JRockit 1.4 -> 1.6 enthalten ist. (Punkt ist, es ist keine öffentliche Klasse).

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

Was den realFramesToSkip -Wert betrifft, so gibt es in Sun 1.5 und 1.6 VM Versionen von Java.lang.System) Eine paketgeschützte Methode namens getCallerClass (), die ruft Sun.reflect.Reflection.getCallerClass(3) auf, aber in meiner Helfer-Utility-Klasse habe ich 4 verwendet, da der hinzugefügte Frame des Helfer-Klassenaufrufs vorhanden ist.

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

Wenn Sie beispielsweise versuchen, die aufrufende Methodenzeile für Debug-Zwecke abzurufen, müssen Sie die Utility-Klasse überwinden, in der Sie diese statischen Methoden codieren:
(alter Java1.4-Code zur Veranschaulichung einer möglichen Verwendung von 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

Ich habe das schon mal gemacht. Sie können einfach eine neue Ausnahme erstellen und den Stack-Trace darauf abrufen, ohne ihn auszulösen, und dann den Stack-Trace untersuchen. Wie die andere Antwort schon sagt, ist es extrem kostspielig - machen Sie es nicht in einer engen Schleife.

Ich habe es zuvor für ein Protokollierungsdienstprogramm in einer App gemacht, in der die Leistung keine große Rolle spielt (Leistung spielt eigentlich selten eine große Rolle - solange Sie das Ergebnis für eine Aktion wie einen schnellen Klick auf eine Schaltfläche anzeigen).

Bevor Sie den Stack-Trace abrufen konnten, hatten Ausnahmen nur .printStackTrace (), sodass ich System.out zu einem Stream meiner eigenen Erstellung umleiten musste, also (new Exception ()). PrintStackTrace (); Leiten Sie System.out zurück und analysieren Sie den Stream. Lustige Sachen.

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_

Hier ist ein Teil des Codes, den ich basierend auf den in diesem Thema gezeigten Hinweisen erstellt habe. Ich hoffe es hilft.

(Zögern Sie nicht, Vorschläge zur Verbesserung dieses Codes zu machen. Bitte teilen Sie mir dies mit.)

Der Zähler:

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

    (...)
}

Und das Objekt:

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

verwenden Sie diese Methode: -

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

Aufrufer des Methodenbeispiels Code ist hier: -

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