我需要找到一个方法的调用者。是否可以使用堆栈跟踪或反射?
当前回答
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() );
}
}
}
其他回答
这个方法做同样的事情,但更简单一点,可能性能更好一点,在你使用反射的情况下,它会自动跳过那些帧。唯一的问题是它可能不会出现在非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虚拟机版本的java.lang. lang. js。系统中,有一个名为getCallerClass()的包保护方法,它调用sun.reflect. reflect. getCallerClass(3),但在我的助手实用程序类中,我使用了4,因为有添加的助手类调用的帧。
Java 9 - JEP 259:栈遍历API
JEP 259为堆栈遍历提供了一个有效的标准API,允许对堆栈跟踪中的信息进行轻松过滤和延迟访问。在栈遍历API之前,访问栈帧的常见方法有:
的数组返回 包含类名和方法的StackTraceElement对象 每个堆栈跟踪元素的名称。 SecurityManager::getClassContext是一个受保护的方法 SecurityManager的子类来访问类上下文。 JDK-internal sun.reflect。Reflection::getCallerClass方法,你不应该使用它
使用这些api通常效率很低:
这些api要求VM急切地捕获整个快照 堆栈,它们返回表示整个堆栈的信息。 没有办法避免检查所有帧的代价,如果 调用者只对堆栈上的前几帧感兴趣。
为了找到直接调用者的类,首先获取一个StackWalker:
StackWalker walker = StackWalker
.getInstance(StackWalker.Option.RETAIN_CLASS_REFERENCE);
然后调用getCallerClass():
Class<?> callerClass = walker.getCallerClass();
或者遍历StackFrame并获得前面的第一个StackFrame:
walker.walk(frames -> frames
.map(StackWalker.StackFrame::getDeclaringClass)
.skip(1)
.findFirst());
听起来好像您试图避免将this的引用传递到方法中。传递这个比通过当前堆栈跟踪查找调用者要好得多。重构为更面向对象的设计更好。你不需要认识打电话的人。必要时传递回调对象。
Oneliner:
Thread.currentThread().getStackTrace()[2].getMethodName()
注意,您可能需要将2替换为1。
注意:如果你使用Java 9或更高版本,你应该使用Ali Dehghani的回答中描述的StackWalker.getCallerClass()。
由于历史原因,下面不同方法的比较非常有趣。
可以在此请求的注释中找到替代解决方案。 它使用自定义SecurityManager的getClassContext()方法,似乎比堆栈跟踪方法快。
下面的程序测试了不同建议方法的速度(最有趣的部分是内部类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.
内部反射方法比其他方法快得多。从新创建的Throwable中获取堆栈跟踪比从当前线程中获取堆栈跟踪要快。在寻找调用者类的非内部方法中,自定义SecurityManager似乎是最快的。
更新
正如lyomi在这条评论中指出的那样,sun.reflect. reflect. getcallerclass()方法在Java 7 update 40中默认被禁用,在Java 8中被完全删除。有关这方面的更多信息,请参阅本期Java错误数据库。
更新2
正如zammbi所发现的,Oracle被迫退出删除sun.reflect. reflect. 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.
推荐文章
- 在流中使用Java 8 foreach循环移动到下一项
- 访问限制:'Application'类型不是API(必需库rt.jar的限制)
- 用Java计算两个日期之间的天数
- 如何配置slf4j-simple
- 在Jar文件中运行类
- 带参数的可运行?
- 我如何得到一个字符串的前n个字符而不检查大小或出界?
- 我可以在Java中设置enum起始值吗?
- Java中的回调函数
- c#和Java中的泛型有什么不同?和模板在c++ ?
- 在Java中,流相对于循环的优势是什么?
- Jersey在未找到InjectionManagerFactory时停止工作
- 在Java流是peek真的只是调试?
- Recyclerview不调用onCreateViewHolder
- 将JSON字符串转换为HashMap