繁体   English   中英

我可以在静态方法中获得对调用类的引用吗?

[英]Can I get a reference to a calling class in a static method?

我有一个静态 Java 方法,我想知道谁是它的调用者。 是否可以在 Java 中获取此信息?

这是可能的,但它很昂贵,而且在任何与正常情况类似的情况下,这是一个非常糟糕的主意。 你几乎肯定想以另一种方式解决你正在解决的问题。 (如果您发布有关您正在解决的问题的新问题,我敢打赌有人会帮助您做到这一点!:-))

生成堆栈跟踪的两种方法:

1) 通过Throwable

...通过抛出和捕获异常,然后使用Exception#getStackTrace

try {
    throw new Exception();
}
catch (Exception e) {
    // Get the stack trace
    StackTraceElement[] entries = e.getStackTrace();
}

...或者正如lscoughlin 指出的那样(请投票给他/她),更直接地通过简单的new Throwable

StackTraceElement[] entries = new Throwable().getStackTrace();

这些StackTraceElement每一个都会为您提供有关堆栈跟踪中该点的信息。 在您的情况下,您可能想查看StackTraceElementgetClassName方法。 如果您确实需要对调用类对象的引用,则可以将该类名字符串传递给Class.forName但请注意,在复杂环境中,如果调用者已完成,您可能会获得该类的不同实例(或没有实例)类加载器有趣的事情。

2)通过使用Thread#getStackTrace

MRalwasser 在下面很有帮助地指出,在 Java 5 或更高版本中(我猜您可能正在使用 Java 5 或更高版本),您可以使用Thread.currentThread().getStackTrace()而不是实际抛出异常。 我不知道它是否会更轻量级(因为获取堆栈跟踪很可能是抛出异常的代价),但它绝对更干净。

在一个快速而肮脏的测试中,与直觉相反,实际上抛出异常似乎比通过Thread更快(并且与new Throwable几乎相同),但是 JVM 应用程序的幼稚分析的变幻莫测有据可查,您的里程可能会有所不同。 ..

但同样,获得堆栈跟踪不仅是一个昂贵的过程,而且使用调用者没有通过方法签名传递给您的有关调用者的信息是一个严重的设计问题,除了(比如)使用的调试器或错误跟踪器只为发展。 这里的主要答案必须是:不要那样做,除非你有充分的理由这样做。

很抱歉复活这个线程,但我多年来一直使用的机制来完成同样的事情,而无需繁琐地使用堆栈跟踪。


class ClassloaderUtil {

    public static Class getCallingClass() {
        return CallerResolver.getCallerClass(2);
    }

    private static final class CallerResolver extends SecurityManager {
        private static final CallerResolver CALLER_RESOLVER = new CallerResolver();
        private static final int CALL_CONTEXT_OFFSET = 3; // may need to change if this class is redesigned
        protected Class[] getClassContext() {
            return super.getClassContext();
        }

        /*
        * Indexes into the current method call context with a given
        * offset.
        */
        private static Class getCallerClass(int callerOffset) {
            return CALLER_RESOLVER.getClassContext()[CALL_CONTEXT_OFFSET + callerOffset];
        }
        private static int getContextSize() {
            return CALLER_RESOLVER.getClassContext().length - CALL_CONTEXT_OFFSET;
        }
    }

}

然后使用它就像这样简单:

public class ClassloaderUtilTest {

    @Test
    public void test() {
        Class c = ClassloaderUtil.getCallingClass();
        Assert.assertNotNull(c);
        c = foo();
        Assert.assertNotNull(c);
    }

    private Class foo() {
        return ClassloaderUtil.getCallingClass();
    }
}

第一个类将是一些 junit 框架类,而 foo() 将返回 ClassloaderUtilTest 作为类。

这绝对不是完美的。 但是,它确实有其随机用途。 我同意已经回答了这个问题的人,因为这是非常昂贵的。

只新建一个 thowable 更容易,也更安全,如下所示:

   Throwable t = new Throwable();
   StackTraceElement directCaller = t.getStackTrace()[1];

但总的来说——这仍然是一个糟糕的主意,而且价格昂贵。

更新:从 java9 开始,你就有了 StackWalker - 所以我现在使用一个看起来像这样的东西。

import java.lang.StackWalker.StackFrame;

public class Backtrace {


    private static final StackWalker stackWalker = StackWalker.getInstance(StackWalker.Option.SHOW_HIDDEN_FRAMES);


    public static StackFrame backTrace() {
        var stackFrame = stackWalker.walk((stream -> stream
                .limit(2)
                .reduce((one, two) -> two)));
        return stackFrame.orElseThrow();
    }

    public static StackFrame backTrace(int framesBack) {
        var stackFrame = stackWalker.walk((stream -> stream
                .limit(2 + framesBack)
                .reduce((one, two) -> two)));

        return stackFrame.orElseThrow();
    }
}

使用 ASM查看此解决方案以解决上一个类似的问题 不幸的是, 我自己的回答并不是那么好。 但我同意坏主意的部分。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM