注射式探针
与脚本探针类似,注入式探针为选定的方法定义拦截处理器。 然而,注入式探针是在IDE中的JProfiler GUI之外开发的, 它依赖于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版本是14.0.5 。
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()
来创建一个没有名称的payload对象。
定义一个注解有@AdditionalInterception(invokeOn = InvocationType.EXIT)
的拦截处理器,
并返回一个String
,它将会自动被用作负载字符串。
覆盖线程状态
在测量数据库驱动程序或外部资源的本地连接器的执行时间时,有时有必要告诉JProfiler将一些方法放入不同的线程状态。 例如,让数据库调用处于"网络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=...