【问题标题】:How to get the caller class in Java [duplicate]如何在Java中获取调用者类[重复]
【发布时间】:2012-07-03 15:25:44
【问题描述】:

我想获取方法的调用者类,即

class foo{

  bar();

}

在方法栏中,我需要获取类名foo,我找到了这个方法:

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

但是,即使 getCallerClasspublic,当我尝试调用它时,Eclipse 会说:

访问限制:方法 getCallerClass() 来自类型 由于所需库的限制,无法访问反射 C:\Program Files\Java\jre7\lib\rt.jar

还有其他选择吗?

【问题讨论】:

  • 这有点令人困惑。你想获取 caller 类还是定义了bar() 的类?
  • Reflection.getCallerClass(1); Reflection 类?
  • Java 9 及更高版本:StackWalker.getInstance(StackWalker.Option.RETAIN_CLASS_REFERENCE).getCallerClass()

标签: java


【解决方案1】:

要让调用者/被调用的类名使用下面的代码,它对我来说很好。

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

【讨论】:

  • 我认为您的索引值应该大一。
  • 这并不完全等同于Reflection.getCallerClass(1),因为它给出了类的名称,而不是实际的类。假设我想知道哪个类加载器加载了我的调用者? Reflection.getCallerClass(1).getClassLoader() 可以告诉我,这种方法不行。
  • @SimonKissane Reflection.getCallerClass 已弃用 :-(
  • 尽管它已被弃用,但它仍然可以使用......对我来说很好。谢谢
【解决方案2】:

堆栈跟踪

这高度取决于您要查找的内容...但这应该直接在此对象中获取调用此方法的类和方法。

  • 索引 0 = 线程
  • 索引 1 = 这个
  • 索引 2 = 直接调用者,可以是自己。
  • 索引 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())

仅供参考:Class.forName() 抛出一个非运行时的 ClassNotFoundException。你需要try catch。

此外,如果您希望忽略类本身的调用,则必须添加一些带有逻辑的循环来检查该特定事物。

类似...(我没有测试过这段代码,所以要小心)

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

StackWalkerStackFrame

请注意,这不是一个详尽的指南,而是一个可能性示例。

打印每个 StackFrame 的 Class(通过获取 Class 引用)

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

【讨论】:

  • 从堆栈跟踪中获取信息是否昂贵?
  • 我其实不知道,我没有测试和比较方法。我想相信从 StackWalker 获取 Class 引用会比从名称中查找要快。
  • 答案就在这条评论里:stackoverflow.com/questions/11306811/…
【解决方案3】:

在下面找到一个简单示例,说明如何获取类和方法名。

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()...

【讨论】:

  • 你不需要创建一个异常对象来获取堆栈跟踪;见 Thread.currentThread().getStackTrace()
  • 抛出和捕获异常是个坏主意。如果这个想法是获取调用者堆栈。而是使用 Thread.currentThread().getStackTrace();如上所述。
  • 它正在返回我 -> sun.reflect.NativeMethodAccessorImpl
【解决方案4】:

我正在使用以下方法从堆栈跟踪中获取特定类的调用者:

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

}

【讨论】:

    【解决方案5】:

    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。

    【讨论】:

    • 有趣。 Reflection.getCallerClass() 在 J8 中已弃用,但我很想知道您是否已经对 SecurityManager 子类方法与 Thread.currentThread().getStackTrace()[n] 进行了性能测试?
    • @mwoodman 添加了 JMH 基准。结果在这里:github.com/nallar/WhoCalled/issues/1#issuecomment-180750822
    • TL;基准测试的 DR:Thread.currentThread().getStackTrace() 比 SecurityManager.getClassContext() 慢 20 倍,后者比 Reflection.getCallingClass() 慢 5 倍,需要 ~85ns/操作.
    • 增加对使用 Java 9 的 StackWalker 的支持(如果可用)将是一个很好的举措。
    【解决方案6】:

    您可以生成堆栈跟踪并使用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;
         }
    }
    

    如果您从bar() 呼叫KDebug.getCallerClassName(),您将收到"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;
     }
    

    那是为了调试吗?如果没有,您的问题可能会有更好的解决方案。

    【讨论】:

    • 这个答案远远优于公认的答案,我相信,它更多地解决了原始问题的意图。至少,它解决了把我带到这里的问题。谢谢。
    • getCallerCallerClassName() 帮助我使用 log4j 完美地生成日志。 Logger.getLogger(getCallerCallerClassName()).info("expected info") 非常感谢。
    • 您能否详细说明获取调用者的方法。我确实看到 element[0] 可能是 Thread 类,并且里面的检查是在以前有另一个类的情况下。但是为什么要删掉不是当前而不是线程的第一个类呢?当前类调用自己的方法呢?
    • @benez 第一个堆栈元素将是 getCallerCallerClassName() 封闭类。在调试器中运行它,你会很快看到。
    【解决方案7】:

    因为我目前有同样的问题,所以我就是这样做的:

    1. 我更喜欢 com.sun.Reflection 而不是 stackTrace,因为堆栈跟踪只产生名称而不是类(包括类加载器)本身。

    2. 该方法已被弃用,但在 Java 8 SDK 中仍然存在。

    // 方法描述符 #124 (I)Ljava/lang/Class; (已弃用) // 签名:(I)Ljava/lang/Class; @java.lang.已弃用 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();

    由于我必须独立于平台,包括安全限制,我只是创建一个灵活的方法:

    1. 检查 com.sun.Reflection 是否可用(安全异常禁用此机制)

    2. 如果 1 为 yes,则获取带 int 或不带 int 参数的方法。

    3. 如果 2 是,则调用它。

    如果 3. 从未达到,我使用堆栈跟踪返回名称。我使用了一个包含类或字符串的特殊结果对象,该对象准确地说明了它是什么以及为什么。

    [总结] 我使用堆栈跟踪进行备份并绕过我使用反射的 eclipse 编译器警告。效果很好。保持代码干净,像魅力一样工作,并且正确地说明所涉及的问题。

    我用了很长时间,今天我搜索了一个相关的问题,所以

    【讨论】:

      【解决方案8】:

      我知道这是一个老问题,但我相信提问者想要的是课程,而不是课程名称。我写了一个小方法来获取实际的类。这有点作弊,可能并不总是有效,但有时当您需要实际课程时,您将不得不使用此方法...

      /**
           * 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('.')));
          }
      

      【讨论】:

      • 提问者写道:“...我需要获取班级名称...”
      【解决方案9】:

      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) 设置为WarningIgnore

      更好的 Eclipse 配置是通过以下方式禁用特定的错误发生:

      Project Properties/Java Build Path/Libraries/JRE System Library展开/Access rules:选择/Edit.../Add.../Resolution:设置为DiscouragedAccessible@设置到sun/reflect/Reflection

      【讨论】:

      • 我不知道这是否正确,我认为问题可能与符号文件(stackoverflow.com/questions/4065401/…)有关,我在 intelliJ 中遇到了类似的错误/警告。我认为问题不是eclipse而是javac。 我不建议您使用 -Dignore.symbol.file 选项,但我确实认为它值得了解,它可能允许您访问该方法。
      • @Grosstav:Eclipse 不使用 javac 而是使用它自己的编译器,所以它不会受到它的影响。也许您的意思是该功能也存在于 javac 中。您提供的链接显示了如何在 javac 中禁用该功能,就像我的回答显示如何在 Eclipse(特别是它的 java 编译器)中禁用它一样。
      【解决方案10】:

      这是获取调用者类的最有效方法。其他方法采用整个堆栈转储,只给你类名。

      但是,这个类在sun.* 下,它确实是供内部使用的。这意味着它可能无法在其他 Java 平台甚至其他 Java 版本上运行。你必须决定这是否是一个问题。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2018-05-23
        • 2023-03-23
        • 2014-05-02
        • 2014-03-11
        • 2013-11-09
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多