注入探针 (Injected Probes)
类似于脚本探针,注入探针为选定的方法定义拦截处理程序。然而,注入探针是在 JProfiler GUI 之外的 IDE 中开发的,并依赖于 JProfiler 提供的注入探针 API。该 API 采用宽松的 Apache License 2.0 许可,使得分发相关工件变得容易。
开始使用注入探针的最佳方式是研究 JProfiler 安装目录中的
api/samples/simple-injected-probe
示例。在该目录中执行 ../gradlew
以编译并运行它。Gradle 构建文件
build.gradle
包含有关该示例的更多信息。api/samples/advanced-injected-probe
中的示例展示了探针系统的更多功能,包括控制对象。
开发环境
开发注入探针所需的探针 API 包含在具有 Maven 坐标的单个工件中
group: com.jprofiler artifact: jprofiler-probe-injected version: <JProfiler version>
本手册对应的 JProfiler 版本为 15.0。
Jar、源代码和 Javadoc 工件发布在以下仓库中
https://maven.ej-technologies.com/repository
您可以使用 Gradle 或 Maven 等构建工具将探针 API 添加到开发类路径中,或者使用 JProfiler 安装中的 JAR 文件
api/jprofiler-probe-injected.jar
。要浏览 Javadoc,请访问
api/javadoc/index.html
该 Javadoc 结合了 JProfiler 发布的所有 API 的 Javadoc。com.jprofiler.api.probe.injected
包的概述是探索 API 的良好起点。
探针结构
注入探针是一个用 com.jprofiler.api.probe.injected.Probe
注解的类。该注解的属性公开了整个探针的配置选项。例如,如果您创建了大量不适合单独检查的探针事件,events
属性允许您禁用探针事件视图并减少开销。
@Probe(name = "Foo", description = "Shows foo server requests", events = "false") public class FooProbe { ... }
在探针类中,您可以添加特别注解的静态方法以定义拦截处理程序。PayloadInterception
注解创建有效负载,而 SplitInterception
注解拆分调用树。处理程序的返回值用作有效负载或拆分字符串,具体取决于注解。与脚本探针一样,如果返回 null
,拦截将无效。拦截方法的时间信息会自动计算。
@Probe(name = "FooBar") public class FooProbe { @PayloadInterception( invokeOn = InvocationType.ENTER, method = @MethodSpec(className = "com.bar.Database", methodName = "processQuery", parameterTypes = {"com.bar.Query"}, returnType = "void")) public static String fooRequest(@Parameter(0) Query query) { return query.getVerbose(); } @SplitInterception( method = @MethodSpec(className = "com.foo.Server", methodName = "handleRequest", parameterTypes = {"com.foo.Request"}, returnType = "void")) public static String barQuery(@Parameter(0) Request request) { return request.getPath(); } }
如上例所示,两个注解都有一个 method
属性,用于使用 MethodSpec
定义被拦截的方法。与脚本探针不同,MethodSpec
可以有一个空的类名,因此所有具有特定签名的方法都会被拦截,而不管类名如何。或者,您可以使用 MethodSpec
的 subtypes
属性来拦截整个类层次结构。
与脚本探针不同,脚本探针会自动提供所有参数,处理程序方法声明参数以请求感兴趣的值。每个参数都用 com.jprofiler.api.probe.injected.parameter
包中的注解进行注解,因此分析代理知道必须将哪个对象或原始值传递给方法。例如,将处理程序方法的参数注解为 @Parameter(0)
会注入被拦截方法的第一个参数。
被拦截方法的方法参数可用于所有拦截类型。如果您告诉分析代理拦截方法的退出而不是进入,@ReturnValue
或 @ExceptionValue
可以访问返回值或抛出的异常。可以通过 PayloadInterception
注解的 invokeOn
属性来实现。
与脚本探针不同,如果您将拦截注解的 reentrant
属性设置为 true
,则注入探针处理程序可以为被拦截方法的递归调用调用。在处理程序方法中使用类型为
ProbeContext
的参数,您可以通过调用 ProbeContext.getOuterPayload()
或
ProbeContext.restartTiming()
来控制探针对嵌套调用的行为。
高级拦截
有时单个拦截不足以收集构建探针字符串所需的所有信息。为此,您的探针可以包含任意数量的用 Interception
注解的拦截处理程序,这些处理程序不会创建有效负载或拆分。信息可以存储在探针类的静态字段中。为了在多线程环境中实现线程安全,您应该使用 ThreadLocal
实例来存储引用类型,并使用
java.util.concurrent.atomic
包中的原子数值类型来维护计数器。
在某些情况下,您需要对方法入口和方法出口进行拦截。一个常见的情况是,如果您维护状态变量,如 inMethodCall
,它会修改另一个拦截的行为。您可以在入口拦截中将
inMethodCall
设置为 true
,这是默认的拦截类型。现在,您可以在该拦截下方直接定义另一个静态方法,并用
@AdditionalInterception(invokeOn = InvocationType.EXIT)
注解它。拦截方法取自上面的拦截处理程序,因此您不必再次指定它。在方法体中,您可以将
inMethodCall
变量设置为 false
。
... private static final ThreadLocal<Boolean> inMethodCall = ThreadLocal.withInitial(() -> Boolean.FALSE); @Interception( invokeOn = InvocationType.ENTER, method = @MethodSpec(className = "com.foo.Server", methodName = "internalCall", parameterTypes = {"com.foo.Request"}, returnType = "void")) public static void guardEnter() { inMethodCall.set(Boolean.TRUE); } @AdditionalInterception(InvocationType.EXIT) public static void guardExit() { inMethodCall.set(Boolean.FALSE); } @SplitInterception( method = @MethodSpec(className = "com.foo.Server", methodName = "handleRequest", parameterTypes = {"com.foo.Request"}, returnType = "void"), reentrant = true) public static String splitRequest(@Parameter(0) Request request) { if (!inMethodCall.get()) { return request.getPath(); } else { return null; } } ...
您可以在 api/samples/advanced-injected-probe/src/main/java/AdvancedAwtEventProbe.java
中看到此用例的工作示例。
控制对象
除非 Probe
注解的 controlObjects
属性设置为
true
,否则控制对象视图不可见。要使用控制对象,您必须通过在处理程序方法中声明该类型的参数来获取
ProbeContext
。下面的示例代码展示了如何打开控制对象并将其与探针事件关联。
@Probe(name = "Foo", controlObjects = true, customTypes = MyEventTypes.class) public class FooProbe { @Interception( invokeOn = InvocationType.EXIT, method = @MethodSpec(className = "com.foo.ConnectionPool", methodName = "createConnection", parameterTypes = {}, returnType = "com.foo.Connection")) public static void openConnection(ProbeContext pc, @ReturnValue Connection c) { pc.openControlObject(c, c.getId()); } @PayloadInterception( invokeOn = InvocationType.EXIT, method = @MethodSpec(className = "com.foo.ConnectionPool", methodName = "createConnection", parameterTypes = {"com.foo.Query", "com.foo.Connection"}, returnType = "com.foo.Connection")) public static Payload handleQuery( ProbeContext pc, @Parameter(0) Query query, @Parameter(1) Connection c) { return pc.createPayload(query.getVerbose(), c, MyEventTypes.QUERY); } ... }
控制对象具有定义的生命周期,探针视图在时间轴和控制对象视图中记录其打开和关闭时间。如果可能,您应该通过调用 ProbeContext.openControlObject()
和
ProbeContext.closeControlObject()
来显式打开和关闭控制对象。否则,您必须声明一个用 @ControlObjectName
注解的静态方法来解析控制对象的显示名称。
如果您的处理程序方法返回 Payload
实例而不是
String
,则探针事件可以与控制对象关联。ProbeContext.createPayload()
方法接受一个控制对象和一个探针事件类型。允许的事件类型的枚举必须通过 Probe
注解的 customTypes
属性注册。
控制对象必须在时间测量开始时指定,这对应于方法入口。在某些情况下,有效负载字符串的名称只有在方法退出时才可用,因为它依赖于返回值或其他拦截。在这种情况下,您可以使用
ProbeContext.createPayloadWithDeferredName()
创建一个没有名称的有效负载对象。在其下方定义一个用
@AdditionalInterception(invokeOn = InvocationType.EXIT)
注解的拦截处理程序,并从该方法返回一个
String
,它将自动用作有效负载字符串。
覆盖线程状态
在测量数据库驱动程序或本机连接器到外部资源的执行时间时,有时需要告诉 JProfiler 将某些方法放入不同的线程状态。例如,将数据库调用放入“Net I/O”线程状态是有用的。如果通信机制不使用标准的 Java I/O 设施,而是使用某种本机机制,这将不会自动发生。
使用 ThreadState.NETIO.enter()
和 ThreadState.exit()
调用对,分析代理会相应地调整线程状态。
... @Interception(invokeOn = InvocationType.ENTER, method = ...) public static void enterMethod(ProbeContext probeContext, @ThisValue JComponent component) { ThreadState.NETIO.enter(); } @AdditionalInterception(InvocationType.EXIT) public static void exitMethod() { ThreadState.exit(); } ...
部署
部署注入探针有两种方式,具体取决于您是否希望将它们放在类路径上。使用 VM 参数
-Djprofiler.probeClassPath=...
分析代理设置了一个单独的探针类路径。探针类路径可以包含目录和类文件,在 Windows 上用 ';' 分隔,在其他平台上用 ':' 分隔。分析代理将扫描探针类路径并找到所有探针定义。
如果您更容易将探针类放在类路径上,可以设置 VM 参数
-Djprofiler.customProbes=...