如何find使用堆栈跟踪或reflection的方法的调用者?
我需要find一个方法的调用者。 是否有可能使用堆栈跟踪或reflection?
StackTraceElement[] stackTraceElements = Thread.currentThread().getStackTrace()
根据Javadocs:
数组的最后一个元素表示堆栈的底部,这是序列中最近的方法调用。
StackTraceElement
具有getClassName()
, getFileName()
, getLineNumber()
和getMethodName()
。
你将不得不尝试确定你想要的索引(可能是stackTraceElements[1]
或[2]
)。
在这个改进请求的评论中可以find一个替代解决scheme。 它使用自定义SecurityManager
的getClassContext()
方法,似乎比堆栈跟踪方法更快。
以下程序testing不同build议方法的速度(最有趣的是在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 Intel Core 2 Duo MacBook的输出示例:
Reflection: 10.195 ms. Current Thread StackTrace: 5886.964 ms. Throwable StackTrace: 4700.073 ms. SecurityManager: 1046.804 ms.
内部的Reflection方法比其他方法快得多。 从新创build的Throwable
获取堆栈跟踪比从当前Thread
获取堆栈跟踪要快。 而在寻找调用者类的非内部方法中,自定义SecurityManager
似乎是最快的。
更新
正如lyomi在这个评论中所指出的那样, sun.reflect.Reflection.getCallerClass 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.
听起来就像你试图避免将这个引用传递给方法。 通过this
方法比通过当前堆栈跟踪find调用者要好得多。 重构更多的面向对象devise甚至更好。 你不需要知道来电者。 必要时传递一个callback对象。
这个方法做同样的事情,但更简单一点,可能更高性能,如果你使用reflection,它会自动跳过这些帧。 唯一的问题是它可能不存在于非Sun JVM中,尽pipe它包含在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 sun.reflect.Reflection.getCallerClass(3)
,但是在我的助手实用程序类我使用4,因为有助手类调用的添加框架。
/** * 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(); }
例如,如果尝试获取调用方法行以进行debugging,则需要通过编写这些静态方法的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; }
我以前做过。 你可以创build一个新的exception,并抓取堆栈跟踪而不扔它,然后检查堆栈跟踪。 然而,另一个答案却说,这是非常昂贵的 – 不要在紧张的环境中这样做。
我之前做过一个应用程序的日志实用程序,其性能并不重要(性能实际上几乎不重要,实际上只要将结果显示为快速button等操作即可)。
之前你可以得到堆栈跟踪,例外只是.printStackTrace(),所以我不得不重新定向System.out到我自己创build的stream,然后(新的Exception())。printStackTrace(); redirectSystem.out返回并parsingstream。 好玩的东西。
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()
请注意,您可能需要用1replace2。
以下是我在本主题中提出的基础代码中的一部分。 希望它有帮助。
(随时提出任何build议,以改善此代码,请告诉我)
柜台:
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(); } }
Java 9 – JEP 259:Stack-Walking API
JEP 259为堆栈行走提供了一个高效的标准API,允许对堆栈跟踪信息进行简单过滤和延迟访问。 在Stack-Walking API之前,访问堆栈帧的常见方法是:
Throwable::getStackTrace
和Thread::getStackTrace
返回一个StackTraceElement
对象数组,它包含每个stack-trace元素的类名和方法名。
SecurityManager::getClassContext
是一个受保护的方法,它允许SecurityManager
子类访问类上下文。JDK内部的
sun.reflect.Reflection::getCallerClass
方法,你不应该使用它
使用这些API通常是低效的:
这些API需要VM急切地捕获整个堆栈的快照 ,并且它们返回表示整个堆栈的信息。 如果调用者只对堆栈中的前几个帧感兴趣,则无法避免检查所有帧的成本。
为了find直接调用者的类,首先获取一个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());