简体   繁体   English

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

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

I have a static Java method, where I want to know who is its caller.我有一个静态 Java 方法,我想知道谁是它的调用者。 Is it possible to get this information in Java?是否可以在 Java 中获取此信息?

It's possible , but it's expensive and in anything remotely like a normal case, it's a very bad idea.这是可能的,但它很昂贵,而且在任何与正常情况类似的情况下,这是一个非常糟糕的主意。 You almost certainly want to approach the problem you're solving in another way.你几乎肯定想以另一种方式解决你正在解决的问题。 (And if you post a new question about the problem you're solving, I bet someone will help you do that! :-) ) (如果您发布有关您正在解决的问题的新问题,我敢打赌有人会帮助您做到这一点!:-))

Two ways you can generate a stack trace:生成堆栈跟踪的两种方法:

1) Via Throwable 1) 通过Throwable

...by throwing and catching an exception, and then using Exception#getStackTrace ...通过抛出和捕获异常,然后使用Exception#getStackTrace

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

...or as lscoughlin points out (please vote him/her up), rather more directly through simply new Throwable : ...或者正如lscoughlin 指出的那样(请投票给他/她),更直接地通过简单的new Throwable

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

Each of those StackTraceElement s then gives you information about that point in the stack trace.这些StackTraceElement每一个都会为您提供有关堆栈跟踪中该点的信息。 In your case, you'd probably want to look at StackTraceElement 's getClassName method.在您的情况下,您可能想查看StackTraceElementgetClassName方法。 If you really need a reference to the calling class object , you can pass that class name string into Class.forName but be warned that in complex environments, you might get a different instance (or no instance) of the class if the caller has done interesting things with class loaders.如果您确实需要对调用类对象的引用,则可以将该类名字符串传递给Class.forName但请注意,在复杂环境中,如果调用者已完成,您可能会获得该类的不同实例(或没有实例)类加载器有趣的事情。

2) By using Thread#getStackTrace 2)通过使用Thread#getStackTrace

MRalwasser helpfully points out below that in Java 5 or higher (and I'm guessing you're probably using Java 5 or higher), you can use Thread.currentThread().getStackTrace() instead of actually throwing an exception. MRalwasser 在下面很有帮助地指出,在 Java 5 或更高版本中(我猜您可能正在使用 Java 5 或更高版本),您可以使用Thread.currentThread().getStackTrace()而不是实际抛出异常。 I don't know whether it would be lighterweight or not (as getting the stack trace may well be what's expensive about throwing an exception), but it's definitely cleaner.我不知道它是否会更轻量级(因为获取堆栈跟踪很可能是抛出异常的代价),但它绝对更干净。

In a quick and dirty test , counter-intuitively actually throwing the exception seemed faster than going via Thread (and was pretty much the same as new Throwable ), but the vagaries of naive profiling of JVM applications are well documented, your mileage may vary...在一个快速而肮脏的测试中,与直觉相反,实际上抛出异常似乎比通过Thread更快(并且与new Throwable几乎相同),但是 JVM 应用程序的幼稚分析的变幻莫测有据可查,您的里程可能会有所不同。 ..

But again, not only is getting a stack trace an expensive process, but using information about the caller that the caller hasn't passed you via the method signature is a serious design issue in anything other than (say) a debugger or error tracer used only for development.但同样,获得堆栈跟踪不仅是一个昂贵的过程,而且使用调用者没有通过方法签名传递给您的有关调用者的信息是一个严重的设计问题,除了(比如)使用的调试器或错误跟踪器只为发展。 The main answer here has to be: Don't do that, unless you have a really good reason to.这里的主要答案必须是:不要那样做,除非你有充分的理由这样做。

Sorry to resurrect this thread but the mechanism I've been using for ages to accomplish this same thing without the cumbersome use of stacktraces.很抱歉复活这个线程,但我多年来一直使用的机制来完成同样的事情,而无需繁琐地使用堆栈跟踪。


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

}

Then using it is as simple as this:然后使用它就像这样简单:

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

The first class will be some junit framework class whereas foo() will return ClassloaderUtilTest as the class.第一个类将是一些 junit 框架类,而 foo() 将返回 ClassloaderUtilTest 作为类。

It is definitely not perfect.这绝对不是完美的。 However, it does have its random uses.但是,它确实有其随机用途。 I agree with the folks that have already answered this question in that this is incredibly expensive.我同意已经回答了这个问题的人,因为这是非常昂贵的。

It's easier and slightly safer to just new a thowable, as in:只新建一个 thowable 更容易,也更安全,如下所示:

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

But Generally speaking -- it's still a terrible idea, and it is expensive.但总的来说——这仍然是一个糟糕的主意,而且价格昂贵。

UPDATE : Since java9 you you have the StackWalker - so I use a thing that looks like this now.更新:从 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();
    }
}

Check out this solution using ASM for a similar previous question . 使用 ASM查看此解决方案以解决上一个类似的问题 Unfortunately my own answer was not quite that good.不幸的是, 我自己的回答并不是那么好。 But I agree with the bad idea part.但我同意坏主意的部分。

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

相关问题 在类上调用静态方法? - Calling static method on a class? 如何使用静态方法(而不是异常)获取类的名称 - How can I get the name of a class, with an static Method (not with Exception) 为什么我不能在泛型类上静态引用内部类的静态方法? - Why can't I statically reference an inner class's static method on a generic class? 我可以从构造函数的主体中调用静态方法来获取实例,而不是调用Super吗? - Can I call a static method from my constructor's body to get at the instance instead of calling Super? 收到“无法对非静态方法进行静态引用”错误,但我尚未将要从中调用的方法声明为静态方法 - getting “cannot make a static reference to the non-static method” error, but I haven't declared the method I'm calling from as static 如何在Java中使用单独的枚举类引用静态类? - How can I reference a static class with a separate enum class in Java? 使用引用变量调用静态方法 - static method calling using reference variable 在没有引用变量的情况下调用静态方法 - Calling static method without a reference variable 我可以部分模拟调用方法类吗 - Can I mock calling class of method partially 如何从一个方法更改静态类变量的值,然后从另一个方法获取它呢? - How can I change the value of a static class variable from a method, then get it from an other method?
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM