簡體   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