Подтвердить что ты не робот

Как получить класс вызывающего абонента в Java

Я хочу получить класс вызывающего метода, т.е.

class foo{

  bar();

}

В панели методов мне нужно получить имя класса foo, и я нашел этот метод:

Class clazz = sun.reflect.Reflection.getCallerClass(1);

Однако, хотя getCallerClass - public, когда я пытаюсь его назвать, Eclipse говорит:

Ограничение доступа: метод getCallerClass() из типа Отражение недоступно из-за ограничений на требуемую библиотеку C:\Program Files\Java\jre7\lib\rt.jar

Есть ли другие варианты?

4b9b3361

Ответ 1

Вы можете создать трассировку стека и использовать информацию в StackTraceElements.

Например, класс утилиты может вернуть вам имя вызывающего класса:

public class KDebug {
    public static String getCallerClassName() { 
        StackTraceElement[] stElements = Thread.currentThread().getStackTrace();
        for (int i=1; i<stElements.length; i++) {
            StackTraceElement ste = stElements[i];
            if (!ste.getClassName().equals(KDebug.class.getName()) && ste.getClassName().indexOf("java.lang.Thread")!=0) {
                return ste.getClassName();
            }
        }
        return null;
     }
}

Если вы вызываете KDebug.getCallerClassName() из bar(), вы получите "foo".

Теперь предположим, что вы хотите знать класс метода, вызывающий bar (что более интересно и, возможно, то, что вы действительно хотели). Вы можете использовать этот метод:

public static String getCallerCallerClassName() { 
    StackTraceElement[] stElements = Thread.currentThread().getStackTrace();
    String callerClassName = null;
    for (int i=1; i<stElements.length; i++) {
        StackTraceElement ste = stElements[i];
        if (!ste.getClassName().equals(KDebug.class.getName())&& ste.getClassName().indexOf("java.lang.Thread")!=0) {
            if (callerClassName==null) {
                callerClassName = ste.getClassName();
            } else if (!callerClassName.equals(ste.getClassName())) {
                return ste.getClassName();
            }
        }
    }
    return null;
 }

Это для отладки? Если нет, может быть лучшее решение вашей проблемы.

Ответ 2

Чтобы получить имя класса вызывающего/вызываемого абонента, используйте код ниже, он отлично работает для меня.

String callerClassName = new Exception().getStackTrace()[1].getClassName();
String calleeClassName = new Exception().getStackTrace()[0].getClassName();

Ответ 3

StackTrace

Это сильно зависит от того, что вы ищете... Но это должно получить класс и метод, который вызвал этот метод непосредственно в этом объекте.

  • индекс 0 = тема
  • индекс 1 = это
  • индекс 2 = прямой абонент, может быть самостоятельно.
  • index 3... n = классы и методы, которые вызывали друг друга для получения индекса 2 и ниже.

Для класса/метода/имени файла:

Thread.currentThread().getStackTrace()[2].getClassName();
Thread.currentThread().getStackTrace()[2].getMethodName();
Thread.currentThread().getStackTrace()[2].getFileName();

Для класса:

Class.forName(Thread.currentThread().getStackTrace()[2].getClassName())

FYI: Class.forName() генерирует исключение ClassNotFoundException, которое НЕ является средой выполнения. Вам нужно попробовать поймать.

Кроме того, если вы хотите игнорировать вызовы внутри самого класса, вы должны добавить цикл с логикой, чтобы проверить эту конкретную вещь.

Что-то вроде... (я не проверял этот кусок кода, так что будьте осторожны)

StackTraceElement[] stes = Thread.currentThread().getStackTrace();
for(int i=2;i<stes.length;i++)
  if(!stes[i].getClassName().equals(this.getClass().getName()))
    return stes[i].getClassName();

StackWalker

StackWalker StackFrame

Обратите внимание, что это не обширное руководство, а пример возможности.

Печатает класс каждого StackFrame (захватывая ссылку на класс)

StackWalker.getInstance(Option.RETAIN_CLASS_REFERENCE)
    .forEach(frame -> System.out.println(frame.getDeclaringClass()));

Делает то же самое, но сначала собирает поток в список. Просто для демонстрационных целей.

StackWalker.getInstance(Option.RETAIN_CLASS_REFERENCE)
    .walk(stream -> stream.collect(Collectors.toList()))
    .forEach(frame -> System.out.println(frame.getDeclaringClass()));

Ответ 4

SecurityManager имеет защищенный метод getClassContext

Создав класс утилиты, который расширяет SecurityManager, вы можете получить к нему доступ.

public class CallingClass extends SecurityManager {
    public static final CallingClass INSTANCE = new CallingClass();

    public Class[] getCallingClasses() {
        return getClassContext();
    }
}

Используйте CallingClass.INSTANCE.getCallingClasses() для извлечения вызывающих классов.

Существует также небольшая библиотека (отказ от ответственности: мой) WhoCalled, которая раскрывает эту информацию. Он использует Reflection.getCallerClass, когда он доступен, а затем возвращается к SecurityManager.

Ответ 5

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

/**
     * Get the caller class.
     * @param level The level of the caller class.
     *              For example: If you are calling this class inside a method and you want to get the caller class of that method,
     *                           you would use level 2. If you want the caller of that class, you would use level 3.
     *
     *              Usually level 2 is the one you want.
     * @return The caller class.
     * @throws ClassNotFoundException We failed to find the caller class.
     */
    public static Class getCallerClass(int level) throws ClassNotFoundException {
        StackTraceElement[] stElements = Thread.currentThread().getStackTrace();
        String rawFQN = stElements[level+1].toString().split("\\(")[0];
        return Class.forName(rawFQN.substring(0, rawFQN.lastIndexOf('.')));
    }

Ответ 6

Это самый эффективный способ получить только класс вызывающих. Другие подходы берут полный дамп стека и дают вам только имя класса.

Однако этот класс находится под sun.*, который действительно используется для внутреннего использования. Это означает, что он может не работать на других платформах Java или даже в других версиях Java. Вы должны решить, является ли это проблемой или нет.

Ответ 7

Найдите ниже простой пример, иллюстрирующий, как получить имена классов и методов.

public static void main(String args[])
   {
      callMe();
   }

   void callMe()
   {
      try
      {
         throw new Exception("Who called me?");
      }
      catch( Exception e )
      {
         System.out.println( "I was called by " + 
                             e.getStackTrace()[1].getClassName() + 
                             "." +
                             e.getStackTrace()[1].getMethodName() + 
                             "()!" );
      }
   }

e имеет getClassName(), getFileName(), getLineNumber() и getMethodName()...

Ответ 8

Сообщение об ошибке, с которым сталкивается OP, является просто функцией Eclipse. Если вы хотите связать код с конкретным производителем (и даже версией) JVM, вы можете эффективно использовать метод sun.reflect.Reflection.getCallerClass(). Затем вы можете скомпилировать код вне Eclipse или настроить его, чтобы не считать эту диагностическую ошибку.

Худшая конфигурация Eclipse заключается в том, чтобы отключить все ошибки в:

Project Properties/Java Compiler/Errors/Warnings/Enable project specific settings установить на отметку / Deprecated and restrited API/Forbidden reference (access rules) на Warning или Ignore.

Лучшая конфигурация Eclipse - отключить определенное появление ошибки:

Project Properties/Java Build Path/Libraries/JRE System Library expand/ Access rules: выбрать /Edit.../Add.../Resolution: установить в Discouraged или Accessible/Rule Pattern установить до sun/reflect/Reflection.

Ответ 9

Я использую следующий метод, чтобы получить вызывающий объект для определенного класса из stacktrace:

package test.log;

public class CallerClassTest {

    public static void main(final String[] args) {
        final Caller caller = new Caller(new Callee());
        caller.execute();
    }

    private static class Caller {

        private final Callee c;

        public Caller(final Callee c) {
            this.c = c;
        }

        void execute() {
            c.call();
        }
    }

    static class Callee {

        void call() {
            System.out.println(getCallerClassName(this.getClass()));
        }
    }

    /**
     * Searches the current threads stacktrace for the class that called the given class. Returns {@code null} if the
     * calling class could not be found.
     * 
     * @param clazz
     *            the class that has been called
     * 
     * @return the caller that called the class or {@code null}
     */
    public static String getCallerClassName(final Class<?> clazz) {
        final StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
        final String className = clazz.getName();
        boolean classFound = false;
        for (int i = 1; i < stackTrace.length; i++) {
            final StackTraceElement element = stackTrace[i];
            final String callerClassName = element.getClassName();
            // check if class name is the requested class
            if (callerClassName.equals(className)) classFound = true;
            else if (classFound) return callerClassName;
        }
        return null;
    }

}

Ответ 10

Поскольку в настоящее время у меня такая же проблема, вот что я делаю:

  • Я предпочитаю com.sun.Reflection вместо stackTrace, поскольку трассировка стека создает имя не самого класса (включая самого загрузчика).

  • Метод устарел, но все еще находится в Java 8 SDK.

//Дескриптор метода № 124 (I) Ljava/lang/Class; (Устаревшее)  // Подпись: (I) Ljava/lang/Class < * > ;  @java.lang.Deprecated  public static native java.lang.Class getCallerClass (int arg0);

  1. Метод без аргумента int не устарел

//дескриптор метода # 122() Ljava/lang/Class;  // Подпись:() Ljava/lang/Class < * > ;  @sun.reflect.CallerSensitive  public static native java.lang.Class getCallerClass();

Поскольку я должен быть независимым от платформы bla bla, включая ограничения безопасности, я просто создаю гибкий метод:

  • Проверьте, доступно ли com.sun.Reflection(исключения безопасности запрещают этот механизм)

  • Если 1 да, то получите метод с int или no int аргументом.

  • Если 2 да, вызовите его.

Если 3. не было достигнуто, я использую трассировку стека, чтобы вернуть имя. Я использую специальный объект результата, который содержит либо класс, либо строку, и этот объект точно сообщает, что это такое и почему.

[Резюме] Я использую stacktrace для резервного копирования и обходит предупреждения компилятора eclipse. Я использую отражения. Работает очень хорошо. Сохраняет код в чистоте, работает как шарм, а также указывает на корректные проблемы.

Я использую это довольно долгое время, и сегодня я искал связанный с ним вопрос, поэтому