快速访问来电者信息
Accessing caller information quickly
我正在处理 aspectj 方面,它需要知道它是从哪里调用的。目前我正在使用
new Throwable().getStackTrace();
访问此信息,但每个方面都需要几百微秒才能到达 运行。
我查看了 SecurityManager,但它似乎只能让我获得 class 名称。
还有我错过的其他选择吗?
更新
我对@apangin 的回答的评论中提到的 JMH 基准测试结果:
Benchmark Mode Cnt Score Error Units
MyBenchmark.javalangaccess13i avgt 100 2025.865 ± 8.133 ns/op
MyBenchmark.javalangaccess2i avgt 100 2648.598 ± 24.369 ns/op
MyBenchmark.throwable1 avgt 100 12706.978 ± 84.651 ns/op
基准代码:
@Benchmark
public StackTraceElement[] throwable1() {
return new Throwable().getStackTrace();
}
@SuppressWarnings("restriction")
@Benchmark
public static StackTraceElement javalangaccess2i() {
Exception e = new Exception();
return sun.misc.SharedSecrets.getJavaLangAccess().getStackTraceElement(e, 2);
}
@SuppressWarnings("restriction")
@Benchmark
public static StackTraceElement javalangaccess13i() {
Exception e = new Exception();
return sun.misc.SharedSecrets.getJavaLangAccess().getStackTraceElement(e, 13);
}
在戴尔 XPS13 9343 (i5-5200U @ 2.2GHz)Windows 10、JDK 1.8.0_112 下测试 运行
不幸的是,Throwable.getStackTrace()
似乎是在纯 Java 8.
中获取调用方框架的唯一可行选择
但是,有一个 JDK 特有的技巧可以只访问一个选定的堆栈帧。
它使用非标准 sun.misc.SharedSecrets
API.
public static StackTraceElement getCaller() {
Exception e = new Exception();
return sun.misc.SharedSecrets.getJavaLangAccess().getStackTraceElement(e, 2);
}
这里2是所需帧的索引
这在最新的 JDK 8 之前工作正常,但是在 JDK 9 中将无法访问私有 API。好消息是 Java 9 将具有新标准 Stack-Walking API。以下是在 Java 9.
中执行相同操作的方法
public static StackWalker.StackFrame getCaller() {
return StackWalker.getInstance(Collections.emptySet(), 3)
.walk(s -> s.skip(2).findFirst())
.orElse(null);
}
另一种对 Java 的旧版本和新版本都适用的选项是 JVMTI GetStackTrace 函数。不过它需要链接本机代码。
你说的是 AspectJ。所以你不需要任何反射,但可以只使用板载 AspectJ 手段,例如 thisEnclosingJoinPointStaticPart.getSignature()
结合 call()
切入点:
驱动申请:
package de.scrum_master.app;
public class Application {
private static final long NUM_LOOPS = 1000 * 1000;
public static void main(String[] args) {
Application application = new Application();
long startTime = System.nanoTime();
for (long i = 0; i < NUM_LOOPS; i++)
application.doSomething();
System.out.printf(
"%-40s | %8.3f ms%n",
"AspectJ thisEnclosingJoinPointStaticPart",
(System.nanoTime() - startTime) / 1.0e6
);
startTime = System.nanoTime();
for (long i = 0; i < NUM_LOOPS; i++)
application.doSomething2();
System.out.printf(
"%-40s | %8.3f ms%n",
"Throwable.getStackTrace",
(System.nanoTime() - startTime) / 1.0e6
);
startTime = System.nanoTime();
for (long i = 0; i < NUM_LOOPS; i++)
application.doSomething3();
System.out.printf(
"%-40s | %8.3f ms%n",
"SharedSecrets.getJavaLangAccess",
(System.nanoTime() - startTime) / 1.0e6
);
}
public void doSomething() {}
public void doSomething2() {}
public void doSomething3() {}
}
看点:
package de.scrum_master.aspect;
import de.scrum_master.app.Application;
import sun.misc.SharedSecrets;
public aspect MyAspect {
before() : call(* Application.doSomething()) {
Object o = thisEnclosingJoinPointStaticPart.getSignature();
//System.out.println(o);
}
before() : call(* Application.doSomething2()) {
Object o = new Throwable().getStackTrace()[1];
//System.out.println(o);
}
before() : call(* Application.doSomething3()) {
Object o = SharedSecrets.getJavaLangAccess().getStackTraceElement(new Throwable(), 1);
//System.out.println(o);
}
}
控制台日志:
AspectJ thisEnclosingJoinPointStaticPart | 7,246 ms
Throwable.getStackTrace | 1852,895 ms
SharedSecrets.getJavaLangAccess | 1043,050 ms
如您所见,AspectJ 比次佳的基于反射的方法快大约 140 倍。
顺便说一句,如果您取消注释方面中的打印语句,您会看到这三种类型的输出:
void de.scrum_master.app.Application.main(String[])
de.scrum_master.app.Application.main(Application.java:16)
de.scrum_master.app.Application.main(Application.java:21)
尽情享受吧!
我正在处理 aspectj 方面,它需要知道它是从哪里调用的。目前我正在使用
new Throwable().getStackTrace();
访问此信息,但每个方面都需要几百微秒才能到达 运行。
我查看了 SecurityManager,但它似乎只能让我获得 class 名称。
还有我错过的其他选择吗?
更新
我对@apangin 的回答的评论中提到的 JMH 基准测试结果:
Benchmark Mode Cnt Score Error Units
MyBenchmark.javalangaccess13i avgt 100 2025.865 ± 8.133 ns/op
MyBenchmark.javalangaccess2i avgt 100 2648.598 ± 24.369 ns/op
MyBenchmark.throwable1 avgt 100 12706.978 ± 84.651 ns/op
基准代码:
@Benchmark
public StackTraceElement[] throwable1() {
return new Throwable().getStackTrace();
}
@SuppressWarnings("restriction")
@Benchmark
public static StackTraceElement javalangaccess2i() {
Exception e = new Exception();
return sun.misc.SharedSecrets.getJavaLangAccess().getStackTraceElement(e, 2);
}
@SuppressWarnings("restriction")
@Benchmark
public static StackTraceElement javalangaccess13i() {
Exception e = new Exception();
return sun.misc.SharedSecrets.getJavaLangAccess().getStackTraceElement(e, 13);
}
在戴尔 XPS13 9343 (i5-5200U @ 2.2GHz)Windows 10、JDK 1.8.0_112 下测试 运行
不幸的是,Throwable.getStackTrace()
似乎是在纯 Java 8.
但是,有一个 JDK 特有的技巧可以只访问一个选定的堆栈帧。
它使用非标准 sun.misc.SharedSecrets
API.
public static StackTraceElement getCaller() {
Exception e = new Exception();
return sun.misc.SharedSecrets.getJavaLangAccess().getStackTraceElement(e, 2);
}
这里2是所需帧的索引
这在最新的 JDK 8 之前工作正常,但是在 JDK 9 中将无法访问私有 API。好消息是 Java 9 将具有新标准 Stack-Walking API。以下是在 Java 9.
中执行相同操作的方法public static StackWalker.StackFrame getCaller() {
return StackWalker.getInstance(Collections.emptySet(), 3)
.walk(s -> s.skip(2).findFirst())
.orElse(null);
}
另一种对 Java 的旧版本和新版本都适用的选项是 JVMTI GetStackTrace 函数。不过它需要链接本机代码。
你说的是 AspectJ。所以你不需要任何反射,但可以只使用板载 AspectJ 手段,例如 thisEnclosingJoinPointStaticPart.getSignature()
结合 call()
切入点:
驱动申请:
package de.scrum_master.app;
public class Application {
private static final long NUM_LOOPS = 1000 * 1000;
public static void main(String[] args) {
Application application = new Application();
long startTime = System.nanoTime();
for (long i = 0; i < NUM_LOOPS; i++)
application.doSomething();
System.out.printf(
"%-40s | %8.3f ms%n",
"AspectJ thisEnclosingJoinPointStaticPart",
(System.nanoTime() - startTime) / 1.0e6
);
startTime = System.nanoTime();
for (long i = 0; i < NUM_LOOPS; i++)
application.doSomething2();
System.out.printf(
"%-40s | %8.3f ms%n",
"Throwable.getStackTrace",
(System.nanoTime() - startTime) / 1.0e6
);
startTime = System.nanoTime();
for (long i = 0; i < NUM_LOOPS; i++)
application.doSomething3();
System.out.printf(
"%-40s | %8.3f ms%n",
"SharedSecrets.getJavaLangAccess",
(System.nanoTime() - startTime) / 1.0e6
);
}
public void doSomething() {}
public void doSomething2() {}
public void doSomething3() {}
}
看点:
package de.scrum_master.aspect;
import de.scrum_master.app.Application;
import sun.misc.SharedSecrets;
public aspect MyAspect {
before() : call(* Application.doSomething()) {
Object o = thisEnclosingJoinPointStaticPart.getSignature();
//System.out.println(o);
}
before() : call(* Application.doSomething2()) {
Object o = new Throwable().getStackTrace()[1];
//System.out.println(o);
}
before() : call(* Application.doSomething3()) {
Object o = SharedSecrets.getJavaLangAccess().getStackTraceElement(new Throwable(), 1);
//System.out.println(o);
}
}
控制台日志:
AspectJ thisEnclosingJoinPointStaticPart | 7,246 ms
Throwable.getStackTrace | 1852,895 ms
SharedSecrets.getJavaLangAccess | 1043,050 ms
如您所见,AspectJ 比次佳的基于反射的方法快大约 140 倍。
顺便说一句,如果您取消注释方面中的打印语句,您会看到这三种类型的输出:
void de.scrum_master.app.Application.main(String[])
de.scrum_master.app.Application.main(Application.java:16)
de.scrum_master.app.Application.main(Application.java:21)
尽情享受吧!