如何使用堆栈跟踪或反射找到方法的调用者?

我需要找到一个方法的调用者。是否可以使用堆栈跟踪或反射?

311424 次浏览

我以前做过。您可以只创建一个新的异常,并在不抛出异常的情况下获取其堆栈跟踪,然后检查堆栈跟踪。正如另一个答案所说,这是非常昂贵的——不要在一个紧密的循环中进行。

我以前在一个性能不太重要的应用程序上为一个日志实用程序做过这样的操作(实际上,性能几乎不太重要——只要你将结果显示给一个动作,比如快速单击按钮)。

这是在你可以得到堆栈跟踪之前,异常只是有. printstacktrace(),所以我必须重定向系统。输出到我自己创建的流,然后(new Exception()).printStackTrace();重定向系统。返回并解析流。有趣的东西。

StackTraceElement[] stackTraceElements = Thread.currentThread().getStackTrace()

根据Javadocs的说法:

数组的最后一个元素表示堆栈的底部,这是序列中最近的方法调用。

A StackTraceElementgetClassName()getFileName()getLineNumber()getMethodName()

你将不得不试验以确定你想要的索引 (可能是stackTraceElements[1][2]).

听起来像是你试图避免将this的引用传递到方法中。传递this比通过当前堆栈跟踪查找调用者要好得多。你不需要知道调用者。必要时传递回调对象。

     /**
* Get the method name for a depth in call stack. <br />
* Utility function
* @param depth depth in the call stack (0 means current method, 1 means call method, ...)
* @return method name
*/
public static String getMethodName(final int depth)
{
final StackTraceElement[] ste = new Throwable().getStackTrace();


//System. out.println(ste[ste.length-depth].getClassName()+"#"+ste[ste.length-depth].getMethodName());
return ste[ste.length - depth].getMethodName();
}

例如,如果您试图获得用于调试目的的调用方法行,您需要通过编写这些静态方法的Utility类 (旧的java1.4代码,只是为了说明StackTraceElement的潜在用法)

        /**
* Returns the first "[class#method(line)]: " of the first class not equal to "StackTraceUtils". <br />
* From the Stack Trace.
* @return "[class#method(line)]: " (never empty, first class past StackTraceUtils)
*/
public static String getClassMethodLine()
{
return getClassMethodLine(null);
}


/**
* Returns the first "[class#method(line)]: " of the first class not equal to "StackTraceUtils" and aclass. <br />
* Allows to get past a certain class.
* @param aclass class to get pass in the stack trace. If null, only try to get past StackTraceUtils.
* @return "[class#method(line)]: " (never empty, because if aclass is not found, returns first class past StackTraceUtils)
*/
public static String getClassMethodLine(final Class aclass)
{
final StackTraceElement st = getCallingStackTraceElement(aclass);
final String amsg = "[" + st.getClassName() + "#" + st.getMethodName() + "(" + st.getLineNumber()
+")] <" + Thread.currentThread().getName() + ">: ";
return amsg;
}


/**
* Returns the first stack trace element of the first class not equal to "StackTraceUtils" or "LogUtils" and aClass. <br />
* Stored in array of the callstack. <br />
* Allows to get past a certain class.
* @param aclass class to get pass in the stack trace. If null, only try to get past StackTraceUtils.
* @return stackTraceElement (never null, because if aClass is not found, returns first class past StackTraceUtils)
* @throws AssertionFailedException if resulting statckTrace is null (RuntimeException)
*/
public static StackTraceElement getCallingStackTraceElement(final Class aclass)
{
final Throwable           t         = new Throwable();
final StackTraceElement[] ste       = t.getStackTrace();
int index = 1;
final int limit = ste.length;
StackTraceElement   st        = ste[index];
String              className = st.getClassName();
boolean aclassfound = false;
if(aclass == null)
{
aclassfound = true;
}
StackTraceElement   resst = null;
while(index < limit)
{
if(shouldExamine(className, aclass) == true)
{
if(resst == null)
{
resst = st;
}
if(aclassfound == true)
{
final StackTraceElement ast = onClassfound(aclass, className, st);
if(ast != null)
{
resst = ast;
break;
}
}
else
{
if(aclass != null && aclass.getName().equals(className) == true)
{
aclassfound = true;
}
}
}
index = index + 1;
st        = ste[index];
className = st.getClassName();
}
if(resst == null)
{
//Assert.isNotNull(resst, "stack trace should null"); //NO OTHERWISE circular dependencies
throw new AssertionFailedException(StackTraceUtils.getClassMethodLine() + " null argument:" + "stack trace should null"); //$NON-NLS-1$
}
return resst;
}


static private boolean shouldExamine(String className, Class aclass)
{
final boolean res = StackTraceUtils.class.getName().equals(className) == false && (className.endsWith("LogUtils"
) == false || (aclass !=null && aclass.getName().endsWith("LogUtils")));
return res;
}


static private StackTraceElement onClassfound(Class aclass, String className, StackTraceElement st)
{
StackTraceElement   resst = null;
if(aclass != null && aclass.getName().equals(className) == false)
{
resst = st;
}
if(aclass == null)
{
resst = st;
}
return resst;
}

这个方法做同样的事情,但更简单一点,可能性能更好一点,在你使用反射的情况下,它会自动跳过那些帧。唯一的问题是它可能不会出现在非sun的jvm中,尽管它包含在JRockit 1.4—>1.6的运行时类中。(重点是,它不是公共类)。

sun.reflect.Reflection


/** Returns the class of the method <code>realFramesToSkip</code>
frames up the stack (zero-based), ignoring frames associated
with java.lang.reflect.Method.invoke() and its implementation.
The first frame is that associated with this method, so
<code>getCallerClass(0)</code> returns the Class object for
sun.reflect.Reflection. Frames associated with
java.lang.reflect.Method.invoke() and its implementation are
completely ignored and do not count toward the number of "real"
frames skipped. */
public static native Class getCallerClass(int realFramesToSkip);

至于realFramesToSkip值应该是什么,在Sun 1.5和1.6 VM版本的java.lang.System中,有一个名为getCallerClass()的包保护方法调用sun.reflect.Reflection.getCallerClass(3),但在我的助手实用程序类中,我使用4,因为有添加的助手类调用帧。

请注意:如果你使用的是Java 9或更高版本,你应该使用阿里·德加尼的回答是中描述的StackWalker.getCallerClass()

由于历史原因,下面不同方法的比较非常有趣。


另一个解决方案可以在这个增强的请求的注释中找到。 它使用自定义SecurityManagergetClassContext()方法,似乎比堆栈跟踪方法更快

下面的程序测试不同建议方法的速度(最有趣的部分在内部类SecurityManagerMethod中):

/**
* Test the speed of various methods for getting the caller class name
*/
public class TestGetCallerClassName {


/**
* Abstract class for testing different methods of getting the caller class name
*/
private static abstract class GetCallerClassNameMethod {
public abstract String getCallerClassName(int callStackDepth);
public abstract String getMethodName();
}


/**
* Uses the internal Reflection class
*/
private static class ReflectionMethod extends GetCallerClassNameMethod {
public String getCallerClassName(int callStackDepth) {
return sun.reflect.Reflection.getCallerClass(callStackDepth).getName();
}


public String getMethodName() {
return "Reflection";
}
}


/**
* Get a stack trace from the current thread
*/
private static class ThreadStackTraceMethod extends GetCallerClassNameMethod {
public String  getCallerClassName(int callStackDepth) {
return Thread.currentThread().getStackTrace()[callStackDepth].getClassName();
}


public String getMethodName() {
return "Current Thread StackTrace";
}
}


/**
* Get a stack trace from a new Throwable
*/
private static class ThrowableStackTraceMethod extends GetCallerClassNameMethod {


public String getCallerClassName(int callStackDepth) {
return new Throwable().getStackTrace()[callStackDepth].getClassName();
}


public String getMethodName() {
return "Throwable StackTrace";
}
}


/**
* Use the SecurityManager.getClassContext()
*/
private static class SecurityManagerMethod extends GetCallerClassNameMethod {
public String  getCallerClassName(int callStackDepth) {
return mySecurityManager.getCallerClassName(callStackDepth);
}


public String getMethodName() {
return "SecurityManager";
}


/**
* A custom security manager that exposes the getClassContext() information
*/
static class MySecurityManager extends SecurityManager {
public String getCallerClassName(int callStackDepth) {
return getClassContext()[callStackDepth].getName();
}
}


private final static MySecurityManager mySecurityManager =
new MySecurityManager();
}


/**
* Test all four methods
*/
public static void main(String[] args) {
testMethod(new ReflectionMethod());
testMethod(new ThreadStackTraceMethod());
testMethod(new ThrowableStackTraceMethod());
testMethod(new SecurityManagerMethod());
}


private static void testMethod(GetCallerClassNameMethod method) {
long startTime = System.nanoTime();
String className = null;
for (int i = 0; i < 1000000; i++) {
className = method.getCallerClassName(2);
}
printElapsedTime(method.getMethodName(), startTime);
}


private static void printElapsedTime(String title, long startTime) {
System.out.println(title + ": " + ((double)(System.nanoTime() - startTime))/1000000 + " ms.");
}
}

运行Java 1.6.0_17的2.4 GHz英特尔酷睿2双核MacBook的输出示例:

Reflection: 10.195 ms.
Current Thread StackTrace: 5886.964 ms.
Throwable StackTrace: 4700.073 ms.
SecurityManager: 1046.804 ms.

内部Reflection方法比其他方法快。从新创建的Throwable中获取堆栈跟踪要比从当前的Thread中获取堆栈跟踪快。在寻找调用者类的非内部方法中,自定义SecurityManager似乎是最快的。

更新

正如lyomi这样的评论中指出的那样,sun.reflect.Reflection.getCallerClass()方法在Java 7更新40中默认禁用,在Java 8中完全删除。在这个问题在Java bug数据库中中阅读更多信息。

更新2

正如zammbi所发现的,Oracle是被迫退出变更,它删除了sun.reflect.Reflection.getCallerClass()。在Java 8中仍然可用(但已弃用)。

更新3

3年后:更新当前JVM的计时。

> java -version
java version "1.8.0"
Java(TM) SE Runtime Environment (build 1.8.0-b132)
Java HotSpot(TM) 64-Bit Server VM (build 25.0-b70, mixed mode)
> java TestGetCallerClassName
Reflection: 0.194s.
Current Thread StackTrace: 3.887s.
Throwable StackTrace: 3.173s.
SecurityManager: 0.565s.
下面是我根据本主题中显示的提示制作的部分代码。

(请随时提出任何建议来改进此代码,请告诉我)

柜台:

public class InstanceCount{
private static Map<Integer, CounterInstanceLog> instanceMap = new HashMap<Integer, CounterInstanceLog>();
private CounterInstanceLog counterInstanceLog;




public void count() {
counterInstanceLog= new counterInstanceLog();
if(counterInstanceLog.getIdHashCode() != 0){
try {
if (instanceMap .containsKey(counterInstanceLog.getIdHashCode())) {
counterInstanceLog= instanceMap .get(counterInstanceLog.getIdHashCode());
}


counterInstanceLog.incrementCounter();


instanceMap .put(counterInstanceLog.getIdHashCode(), counterInstanceLog);
}


(...)
}

对象是:

public class CounterInstanceLog{
private int idHashCode;
private StackTraceElement[] arrayStackTraceElements;
private int instanceCount;
private String callerClassName;


private StackTraceElement getProjectClasses(int depth) {
if(depth< 10){
getCallerClassName(sun.reflect.Reflection.getCallerClass(depth).getName());
if(getCallerClassName().startsWith("com.yourproject.model")){
setStackTraceElements(Thread.currentThread().getStackTrace());
setIdHashCode();
return arrayStackTraceElements[depth];
}
//+2 because one new item are added to the stackflow
return getProjectClasses(profundidade+2);
}else{
return null;
}
}


private void setIdHashCode() {
if(getNomeClasse() != null){
this.idHashCode = (getCallerClassName()).hashCode();
}
}


public void incrementaContador() {
this.instanceCount++;
}


//getters and setters


(...)






}
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.PrintWriter;


class DBConnection {
String createdBy = null;


DBConnection(Throwable whoCreatedMe) {
ByteArrayOutputStream os = new ByteArrayOutputStream();
PrintWriter pw = new PrintWriter(os);
whoCreatedMe.printStackTrace(pw);
try {
createdBy = os.toString();
pw.close();
os.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}


public class ThrowableTest {


public static void main(String[] args) {


Throwable createdBy = new Throwable(
"Connection created from DBConnectionManager");
DBConnection conn = new DBConnection(createdBy);
System.out.println(conn.createdBy);
}
}

public static interface ICallback<T> { T doOperation(); }




public class TestCallerOfMethod {


public static <T> T callTwo(final ICallback<T> c){
// Pass the object created at callee to the caller
// From the passed object we can get; what is the callee name like below.
System.out.println(c.getClass().getEnclosingMethod().getName());
return c.doOperation();
}


public static boolean callOne(){
ICallback callBackInstance = new ICallback(Boolean){
@Override
public Boolean doOperation()
{
return true;
}
};
return callTwo(callBackInstance);
}


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

使用方法:-

 StackTraceElement[] stacktrace = Thread.currentThread().getStackTrace();
stackTraceElement e = stacktrace[2];//maybe this number needs to be corrected
System.out.println(e.getMethodName());

方法示例代码的调用者在这里:-

public class TestString {


public static void main(String[] args) {
TestString testString = new TestString();
testString.doit1();
testString.doit2();
testString.doit3();
testString.doit4();
}


public void doit() {
StackTraceElement[] stacktrace = Thread.currentThread().getStackTrace();
StackTraceElement e = stacktrace[2];//maybe this number needs to be corrected
System.out.println(e.getMethodName());
}


public void doit1() {
doit();
}


public void doit2() {
doit();
}


public void doit3() {
doit();
}


public void doit4() {
doit();
}
}
private void parseExceptionContents(
final Exception exception,
final OutputStream out)
{
final StackTraceElement[] stackTrace = exception.getStackTrace();
int index = 0;
for (StackTraceElement element : stackTrace)
{
final String exceptionMsg =
"Exception thrown from " + element.getMethodName()
+ " in class " + element.getClassName() + " [on line number "
+ element.getLineNumber() + " of file " + element.getFileName() + "]";
try
{
out.write((headerLine + newLine).getBytes());
out.write((headerTitlePortion + index++ + newLine).getBytes() );
out.write((headerLine + newLine).getBytes());
out.write((exceptionMsg + newLine + newLine).getBytes());
out.write(
("Exception.toString: " + element.toString() + newLine).getBytes());
}
catch (IOException ioEx)
{
System.err.println(
"IOException encountered while trying to write "
+ "StackTraceElement data to provided OutputStream.\n"
+ ioEx.getMessage() );
}
}
}

Oneliner:

Thread.currentThread().getStackTrace()[2].getMethodName()

注意,您可能需要将2替换为1。

Java 9 - JEP 259:栈遍历API

中259为堆栈遍历提供了一个有效的标准API,允许轻松过滤和延迟访问堆栈跟踪中的信息。在栈遍历API之前,访问栈帧的常见方法有:

Throwable::getStackTraceThread::getStackTrace返回一个数组 StackTraceElement对象,其中包含类名和方法 每个堆栈跟踪元素的名称

SecurityManager::getClassContext是一个受保护的方法,它允许 SecurityManager子类访问类上下文

JDK-internal sun.reflect.Reflection::getCallerClass方法,你不应该使用它

使用这些api通常效率很低:

这些api要求VM急切地捕获整个快照 Stack ,返回代表整个堆栈的信息。 没有办法避免检查所有帧的代价,如果 调用者只对堆栈上的前几帧感兴趣

为了找到直接调用者的类,首先获取StackWalker:

StackWalker walker = StackWalker
.getInstance(StackWalker.Option.RETAIN_CLASS_REFERENCE);

然后调用getCallerClass():

Class<?> callerClass = walker.getCallerClass();

walk StackFrame并获得前面的第一个StackFrame:

walker.walk(frames -> frames
.map(StackWalker.StackFrame::getDeclaringClass)
.skip(1)
.findFirst());