Вопрос-ответ

How do I find the caller of a method using stacktrace or reflection?

Как мне найти вызывающего метод с помощью stacktrace или отражения?

Мне нужно найти вызывающего метод. Возможно ли это с помощью stacktrace или отражения?

Переведено автоматически
Ответ 1
StackTraceElement[] stackTraceElements = Thread.currentThread().getStackTrace()

Согласно Javadocs:


Последний элемент массива представляет нижнюю часть стека, которая является наименее последним вызовом метода в последовательности.


У StackTraceElement есть getClassName(), getFileName(), getLineNumber() и getMethodName().

Вам придется поэкспериментировать, чтобы определить, какой индекс вы хотите (вероятно, stackTraceElements[1] или [2]).

Ответ 2

Примечание: если вы используете Java 9 или более поздней версии, вам следует использовать StackWalker.getCallerClass() как описано в ответе Али Дехгани.

Сравнение различных методов, приведенное ниже, в основном интересно по исторической причине.


Альтернативное решение можно найти в комментарии к этому запросу на улучшение. Он использует getClassContext() пользовательский метод SecurityManager и, похоже, работает быстрее, чем метод трассировки стека.

Следующая программа проверяет скорость различных предложенных методов (самая интересная часть находится во внутреннем классе 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.");
}
}

Пример вывода с моего MacBook Intel Core 2 Duo с частотой 2,4 ГГц под управлением Java 1.6.0_17:

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

Метод внутреннего отражения намного быстрее других. Получение трассировки стека из вновь созданного Throwable быстрее, чем из текущего Thread. И среди внешних способов поиска вызывающего класса пользовательский SecurityManager кажется самым быстрым.

Обновить

Как лайоми указывает в этом комментарии, sun.reflect.Reflection.getCallerClass() метод был отключен по умолчанию в обновлении 40 Java 7 и полностью удален в Java 8. Подробнее об этом читайте в этой проблеме в базе данных ошибок Java.

Обновление 2

Как обнаружил zammbi, Oracle была вынуждена отказаться от изменения, которое удалило sun.reflect.Reflection.getCallerClass(). Он по-прежнему доступен в Java 8 (но устарел).

Обновление 3

3 года спустя: обновление по синхронизации с текущей 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.
Ответ 3

Java 9 - JEP 259: Stack-Walking API

JEP 259 предоставляет эффективный стандартный API для обхода стека, который обеспечивает легкую фильтрацию и отложенный доступ к информации в трассировках стека. До появления Stack-Walking API распространенными способами доступа к фреймам стека были:


Throwable::getStackTrace и Thread::getStackTrace возвращает массив StackTraceElement объектов, которые содержат имя класса и имя метода каждого элемента трассировки стека.


SecurityManager::getClassContext это защищенный метод, который позволяет SecurityManager подклассу получать доступ к контексту класса.


JDK-внутренний sun.reflect.Reflection::getCallerClass метод, который вам все равно не следует использовать


Использование этих API обычно неэффективно:


Эти API требуют, чтобы виртуальная машина быстро делала снимок всего стека, и они возвращают информацию, представляющую весь стек. Невозможно избежать затрат на проверку всех фреймов, если вызывающую функцию интересуют только несколько верхних фреймов в стеке.


Чтобы найти класс непосредственного вызывающего объекта, сначала получите StackWalker:

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

Затем либо вызовите getCallerClass():

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

или walk the StackFrames и получить первый предшествующий StackFrame:

walker.walk(frames -> frames
.map(StackWalker.StackFrame::getDeclaringClass)
.skip(1)
.findFirst());
Ответ 4

Звучит так, как будто вы пытаетесь избежать передачи ссылки на this в метод. Передача this намного лучше, чем поиск вызывающей функции по текущей трассировке стека. Рефакторинг для более ООП дизайна еще лучше. Вам не обязательно знать вызывающего. При необходимости передайте объект обратного вызова.

java