在 Java 系统 类 中记录方法调用
Log Method Calls in Java System Classes
我正在寻找一种方法来记录对 java.nio.ByteBuffer
中所有方法的调用。
我只想知道调用了哪些方法。
这在 JMockit 中是可能的,但是从 1.47 版开始,一些非常聪明的人决定删除对私有方法的支持,而 1.46 版在 JDK 9 及更高版本中不能很好地工作。
谁能推荐一个工具?它不一定需要是一个单元测试框架,但它应该在 Eclipse 中工作。
我至少需要支持 JDK 11(最好是 JDK 13)
仅作记录,以下是适用于 JMockit 1.46 和 JDK 1.8 的代码:
import java.nio.ByteBuffer;
import org.junit.Test;
import org.slf4j.*;
import mockit.*;
public class TestFakeByteBufferAdvice {
private static final Logger LOG = LoggerFactory.getLogger(TestFakeByteBufferAdvice.class);
public static final class FakeByteBuffer extends MockUp<ByteBuffer> {
@Mock
public Object $advice(Invocation invocation) {
LOG.info("$advice.....: {} {}", invocation.getInvokedMember(), invocation);
return invocation.proceed();
}
}
@Test
public void getFakeByteBuffer() {
final ByteBuffer real = ByteBuffer.wrap("abc".getBytes());
LOG.info("Real........: {} {}", real, real.array());
LOG.info("MockUp......: {}", new FakeByteBuffer());
final ByteBuffer fake = ByteBuffer.wrap("def".getBytes());
LOG.info("Fake........: {} {}", fake, fake.array());
}
}
如果这是为了学习或分析,为什么不直接使用调试器呢?我将在 IntelliJ IDEA 中向您展示一个示例:
给定 您的 main
方法或测试中某处的代码:
final ByteBuffer real = ByteBuffer.wrap("abc".getBytes());
LOG.info("Real........: {} {}", real, real.array());
当在调试器中定义一个方法断点时:
和这样定义它的属性(断点不挂起运行程序但记录方法入口,其他过滤条件和日志信息也是可以的):
然后你会得到这样的控制台日志(因为它很长所以缩短了):
Connected to the target VM, address: '127.0.0.1:54734', transport: 'socket'
Method 'java.nio.ByteBuffer.allocate()' entered at java.nio.ByteBuffer.allocate(ByteBuffer.java:333)
Method 'java.nio.ByteBuffer.<init>()' entered at java.nio.ByteBuffer.<init>(ByteBuffer.java:281)
Method 'java.nio.ByteBuffer.hasArray()' entered at java.nio.ByteBuffer.hasArray(ByteBuffer.java:970)
Method 'java.nio.ByteBuffer.array()' entered at java.nio.ByteBuffer.array(ByteBuffer.java:993)
Method 'java.nio.ByteBuffer.arrayOffset()' entered at java.nio.ByteBuffer.arrayOffset(ByteBuffer.java:1021)
(...)
Method 'java.nio.ByteBuffer.wrap()' entered at java.nio.ByteBuffer.wrap(ByteBuffer.java:396)
Method 'java.nio.ByteBuffer.wrap()' entered at java.nio.ByteBuffer.wrap(ByteBuffer.java:373)
Method 'java.nio.ByteBuffer.<init>()' entered at java.nio.ByteBuffer.<init>(ByteBuffer.java:281)
Method 'java.nio.ByteBuffer.array()' entered at java.nio.ByteBuffer.array(ByteBuffer.java:993)
Method 'java.nio.ByteBuffer.toString()' entered at java.nio.ByteBuffer.toString(ByteBuffer.java:1085)
08:23:16.943 [main] INFO d.s.s.q.TestFakeByteBufferAdvice - Real........: java.nio.HeapByteBuffer[pos=0 lim=3 cap=3] [97, 98, 99]
Method 'java.nio.ByteBuffer.hasArray()' entered at java.nio.ByteBuffer.hasArray(ByteBuffer.java:970)
Method 'java.nio.ByteBuffer.array()' entered at java.nio.ByteBuffer.array(ByteBuffer.java:993)
(...)
Method 'java.nio.ByteBuffer.arrayOffset()' entered at java.nio.ByteBuffer.arrayOffset(ByteBuffer.java:1021)
Method 'java.nio.ByteBuffer.array()' entered at java.nio.ByteBuffer.array(ByteBuffer.java:993)
Method 'java.nio.ByteBuffer.arrayOffset()' entered at java.nio.ByteBuffer.arrayOffset(ByteBuffer.java:1021)
Disconnected from the target VM, address: '127.0.0.1:54734', transport: 'socket'
Process finished with exit code 0
尝试使用断点选项并找到最能帮助您的设置。例如,您可以过滤掉某些不感兴趣的方法或指定其他条件、评估表达式并记录它们或您想到的任何内容。
2020-05-17 更新: JetBrains 花了几周时间才做出反应,但他们同意改进文档并修复一个错误:
好吧,我设法找到了解决方案。
首先,JMockit 不能很好地与 JDK13 配合使用,因此我将 JDK13 Http Client 反向移植到 JDK8。这是一个邪恶的 hack,但足以满足我的测试用例。
然后我使用了 JMockit 的稍旧版本 (1.46),因为一些喜剧演员决定不允许任何人使用私有方法伪造 类。
然后我将要跟踪的对象存储在一个列表中,这使得可以通过身份 (==) 比较从日志记录中排除不需要的对象。
JMockit 仍然可能会在某些方法上崩溃(在下面的示例中,我已将它们注释掉),因此在异常之后抑制日志记录是一个想法。
我已将此报告给 JMockit 人员:
https://github.com/jmockit/jmockit1/issues/667
同样,在构建测试用例时抑制日志记录以降低输出量是有意义的。我为此使用了 AtomicBoolean。
跟踪仍然没有完全解决它,但投入 Stacktrace 使我找到了解决方案:以下调用链正在读取我的 ByteBuffers:
sun.nio.ch.write(ByteBuffer[] srcs, int offset, int length)
sun.nio.ch.write(FileDescriptor fd, ByteBuffer src, long position, NativeDispatcher nd)
java.nio.DirectByteBuffer.put(ByteBuffer 源)
DirectByteBuffer 使用了一些巧妙的技巧来读出我的 ByteBuffer。
该解决方案仅适用于我的 Http Client hack,但无论如何,仅供参考。也许其中一些会帮助其他人调试其他 类:
package http.jmockit;
import java.net.*;
import java.net.http.*;
import java.net.http.HttpRequest.BodyPublisher;
import java.nio.ByteBuffer;
import java.util.*;
import java.util.concurrent.atomic.AtomicBoolean;
import org.slf4j.*;
import mockit.*;
public class TestHttpIdentity {
private static final Logger LOG = LoggerFactory.getLogger(TestHttpIdentity.class);
private static final AtomicBoolean LOG_ENABLED = new AtomicBoolean();
private static final List<Object> TRACKED = new ArrayList<>();
public static final class FakeByteBuffer extends MockUp<ByteBuffer> {
@Mock
public Object $advice(final Invocation invocation) {
if (TRACKED.stream().noneMatch(tracked -> tracked == invocation.getInvokedInstance())) {
return invocation.proceed();
}
if (LOG_ENABLED.get()) {
LOG .info("$advice.invokedInstance.: {}", invocation.getInvokedInstance(), "" /* (makes signature unique)*/);
LOG .info("$advice.invokedMember...: {}", invocation.getInvokedMember(), "" /* (makes signature unique)*/);
// Thread.dumpStack(); // Use Stack Trace as last measure if needs be
}
Object result = "Not available due to Exception in invocation.proceed()";
try {
/**/ result = invocation.proceed();
return result;
}
catch (final Throwable e) {
for (final Object arg : invocation.getInvokedArguments()) {
LOG.info("$advice.arg.............: {} class={}", arg, arg == null ? "?" : arg.getClass());
}
LOG .info("$advice.Result..........: {}", result);
LOG_ENABLED.set(false); // Disable Logging when JMockit fails
try {Thread.sleep(100);} catch (final InterruptedException shortDelayToSyncLoggingOutput) {}
e.printStackTrace();
throw e;
}
}
}
public static void main(final String[] args) throws Exception {
LOG.info("MockUp..................: {}", new FakeByteBuffer());
final ByteBuffer[] byteBuffers = TestBytes.asWrappedByteBuffers();
for (final ByteBuffer byteBuffer : byteBuffers) {
LOG.info("byteBuffer..............: {}", byteBuffer);
final int limit = byteBuffer.limit();
final int position = byteBuffer.position();
TRACKED.add(byteBuffer); // Track Objects via their Identity (==)
LOG.info("Test Bytes..............: {}", byteBuffers, "");
LOG.info("byteBuffer0.array().....: {}", byteBuffer.array());
LOG.info("byteBuffer0.capacity()..: {}", byteBuffer.capacity());
LOG.info("byteBuffer0.get().......: {}", byteBuffer.get());
// LOG.info("byteBuffer0.get(byte[]).: {}", byteBuffers0.get(new byte[5])); // ClassCastException
LOG.info("byteBuffer0.get(byte[]->) {}", byteBuffer.get(new byte[5], 0, 5));
LOG.info("byteBuffer0.get(0)......: {}", byteBuffer.get(0));
LOG.info("byteBuffer0.hasArray()..: {}", byteBuffer.hasArray());
LOG.info("byteBuffer0.hasRemaining: {}", byteBuffer.hasRemaining());
LOG.info("byteBuffer0.isDirect()..: {}", byteBuffer.isDirect());
LOG.info("byteBuffer0.isReadOnly(): {}", byteBuffer.isReadOnly());
LOG.info("byteBuffer0.limit().....: {}", limit);
LOG.info("byteBuffer0.limit(0)....: {}", byteBuffer.limit(limit));
LOG.info("byteBuffer0.mark(0).....: {}", byteBuffer.mark());
LOG.info("byteBuffer0.order().....: {}", byteBuffer.order());
LOG.info("byteBuffer0.position()..: {}", position);
LOG.info("byteBuffer0.position(99): {}", byteBuffer.position(99));
LOG.info("byteBuffer0.remaining().: {}", byteBuffer.remaining());
// LOG.info("byteBuffer0.reset().....: {}", byteBuffers0.reset()); // -> InvalidMarkException
LOG.info("byteBuffer0.rewind()....: {}", byteBuffer.rewind());
LOG.info("byteBuffer0.slice().....: {}", byteBuffer.slice());
byteBuffer.rewind();
byteBuffer.position(position);
byteBuffer.limit (limit);
LOG.info("byteBuffer..............: {}", byteBuffer);
}
final BodyPublisher pub = new ByteArrayBodyPublisherIterator(byteBuffers);
LOG_ENABLED.set(false); // Enable Logging now we've got things set up.
final HttpRequest request = HttpRequest.newBuilder()
.uri(new URI("http://localhost:631"))
.headers("Content-Type", "application/ipp")
.POST(pub)
.build();
HttpResponse<byte[]> response = HttpClient
.newBuilder()
.build()
.send(request, HttpResponse.BodyHandlers.ofByteArray());
LOG.info("Result......: {} {}", response, response.body());
}
}
我正在寻找一种方法来记录对 java.nio.ByteBuffer
中所有方法的调用。
我只想知道调用了哪些方法。
这在 JMockit 中是可能的,但是从 1.47 版开始,一些非常聪明的人决定删除对私有方法的支持,而 1.46 版在 JDK 9 及更高版本中不能很好地工作。
谁能推荐一个工具?它不一定需要是一个单元测试框架,但它应该在 Eclipse 中工作。
我至少需要支持 JDK 11(最好是 JDK 13)
仅作记录,以下是适用于 JMockit 1.46 和 JDK 1.8 的代码:
import java.nio.ByteBuffer;
import org.junit.Test;
import org.slf4j.*;
import mockit.*;
public class TestFakeByteBufferAdvice {
private static final Logger LOG = LoggerFactory.getLogger(TestFakeByteBufferAdvice.class);
public static final class FakeByteBuffer extends MockUp<ByteBuffer> {
@Mock
public Object $advice(Invocation invocation) {
LOG.info("$advice.....: {} {}", invocation.getInvokedMember(), invocation);
return invocation.proceed();
}
}
@Test
public void getFakeByteBuffer() {
final ByteBuffer real = ByteBuffer.wrap("abc".getBytes());
LOG.info("Real........: {} {}", real, real.array());
LOG.info("MockUp......: {}", new FakeByteBuffer());
final ByteBuffer fake = ByteBuffer.wrap("def".getBytes());
LOG.info("Fake........: {} {}", fake, fake.array());
}
}
如果这是为了学习或分析,为什么不直接使用调试器呢?我将在 IntelliJ IDEA 中向您展示一个示例:
给定 您的 main
方法或测试中某处的代码:
final ByteBuffer real = ByteBuffer.wrap("abc".getBytes());
LOG.info("Real........: {} {}", real, real.array());
当在调试器中定义一个方法断点时:
和这样定义它的属性(断点不挂起运行程序但记录方法入口,其他过滤条件和日志信息也是可以的):
然后你会得到这样的控制台日志(因为它很长所以缩短了):
Connected to the target VM, address: '127.0.0.1:54734', transport: 'socket'
Method 'java.nio.ByteBuffer.allocate()' entered at java.nio.ByteBuffer.allocate(ByteBuffer.java:333)
Method 'java.nio.ByteBuffer.<init>()' entered at java.nio.ByteBuffer.<init>(ByteBuffer.java:281)
Method 'java.nio.ByteBuffer.hasArray()' entered at java.nio.ByteBuffer.hasArray(ByteBuffer.java:970)
Method 'java.nio.ByteBuffer.array()' entered at java.nio.ByteBuffer.array(ByteBuffer.java:993)
Method 'java.nio.ByteBuffer.arrayOffset()' entered at java.nio.ByteBuffer.arrayOffset(ByteBuffer.java:1021)
(...)
Method 'java.nio.ByteBuffer.wrap()' entered at java.nio.ByteBuffer.wrap(ByteBuffer.java:396)
Method 'java.nio.ByteBuffer.wrap()' entered at java.nio.ByteBuffer.wrap(ByteBuffer.java:373)
Method 'java.nio.ByteBuffer.<init>()' entered at java.nio.ByteBuffer.<init>(ByteBuffer.java:281)
Method 'java.nio.ByteBuffer.array()' entered at java.nio.ByteBuffer.array(ByteBuffer.java:993)
Method 'java.nio.ByteBuffer.toString()' entered at java.nio.ByteBuffer.toString(ByteBuffer.java:1085)
08:23:16.943 [main] INFO d.s.s.q.TestFakeByteBufferAdvice - Real........: java.nio.HeapByteBuffer[pos=0 lim=3 cap=3] [97, 98, 99]
Method 'java.nio.ByteBuffer.hasArray()' entered at java.nio.ByteBuffer.hasArray(ByteBuffer.java:970)
Method 'java.nio.ByteBuffer.array()' entered at java.nio.ByteBuffer.array(ByteBuffer.java:993)
(...)
Method 'java.nio.ByteBuffer.arrayOffset()' entered at java.nio.ByteBuffer.arrayOffset(ByteBuffer.java:1021)
Method 'java.nio.ByteBuffer.array()' entered at java.nio.ByteBuffer.array(ByteBuffer.java:993)
Method 'java.nio.ByteBuffer.arrayOffset()' entered at java.nio.ByteBuffer.arrayOffset(ByteBuffer.java:1021)
Disconnected from the target VM, address: '127.0.0.1:54734', transport: 'socket'
Process finished with exit code 0
尝试使用断点选项并找到最能帮助您的设置。例如,您可以过滤掉某些不感兴趣的方法或指定其他条件、评估表达式并记录它们或您想到的任何内容。
2020-05-17 更新: JetBrains 花了几周时间才做出反应,但他们同意改进文档并修复一个错误:
好吧,我设法找到了解决方案。
首先,JMockit 不能很好地与 JDK13 配合使用,因此我将 JDK13 Http Client 反向移植到 JDK8。这是一个邪恶的 hack,但足以满足我的测试用例。
然后我使用了 JMockit 的稍旧版本 (1.46),因为一些喜剧演员决定不允许任何人使用私有方法伪造 类。
然后我将要跟踪的对象存储在一个列表中,这使得可以通过身份 (==) 比较从日志记录中排除不需要的对象。
JMockit 仍然可能会在某些方法上崩溃(在下面的示例中,我已将它们注释掉),因此在异常之后抑制日志记录是一个想法。
我已将此报告给 JMockit 人员:
https://github.com/jmockit/jmockit1/issues/667
同样,在构建测试用例时抑制日志记录以降低输出量是有意义的。我为此使用了 AtomicBoolean。
跟踪仍然没有完全解决它,但投入 Stacktrace 使我找到了解决方案:以下调用链正在读取我的 ByteBuffers:
sun.nio.ch.write(ByteBuffer[] srcs, int offset, int length)
sun.nio.ch.write(FileDescriptor fd, ByteBuffer src, long position, NativeDispatcher nd)
java.nio.DirectByteBuffer.put(ByteBuffer 源)
DirectByteBuffer 使用了一些巧妙的技巧来读出我的 ByteBuffer。
该解决方案仅适用于我的 Http Client hack,但无论如何,仅供参考。也许其中一些会帮助其他人调试其他 类:
package http.jmockit;
import java.net.*;
import java.net.http.*;
import java.net.http.HttpRequest.BodyPublisher;
import java.nio.ByteBuffer;
import java.util.*;
import java.util.concurrent.atomic.AtomicBoolean;
import org.slf4j.*;
import mockit.*;
public class TestHttpIdentity {
private static final Logger LOG = LoggerFactory.getLogger(TestHttpIdentity.class);
private static final AtomicBoolean LOG_ENABLED = new AtomicBoolean();
private static final List<Object> TRACKED = new ArrayList<>();
public static final class FakeByteBuffer extends MockUp<ByteBuffer> {
@Mock
public Object $advice(final Invocation invocation) {
if (TRACKED.stream().noneMatch(tracked -> tracked == invocation.getInvokedInstance())) {
return invocation.proceed();
}
if (LOG_ENABLED.get()) {
LOG .info("$advice.invokedInstance.: {}", invocation.getInvokedInstance(), "" /* (makes signature unique)*/);
LOG .info("$advice.invokedMember...: {}", invocation.getInvokedMember(), "" /* (makes signature unique)*/);
// Thread.dumpStack(); // Use Stack Trace as last measure if needs be
}
Object result = "Not available due to Exception in invocation.proceed()";
try {
/**/ result = invocation.proceed();
return result;
}
catch (final Throwable e) {
for (final Object arg : invocation.getInvokedArguments()) {
LOG.info("$advice.arg.............: {} class={}", arg, arg == null ? "?" : arg.getClass());
}
LOG .info("$advice.Result..........: {}", result);
LOG_ENABLED.set(false); // Disable Logging when JMockit fails
try {Thread.sleep(100);} catch (final InterruptedException shortDelayToSyncLoggingOutput) {}
e.printStackTrace();
throw e;
}
}
}
public static void main(final String[] args) throws Exception {
LOG.info("MockUp..................: {}", new FakeByteBuffer());
final ByteBuffer[] byteBuffers = TestBytes.asWrappedByteBuffers();
for (final ByteBuffer byteBuffer : byteBuffers) {
LOG.info("byteBuffer..............: {}", byteBuffer);
final int limit = byteBuffer.limit();
final int position = byteBuffer.position();
TRACKED.add(byteBuffer); // Track Objects via their Identity (==)
LOG.info("Test Bytes..............: {}", byteBuffers, "");
LOG.info("byteBuffer0.array().....: {}", byteBuffer.array());
LOG.info("byteBuffer0.capacity()..: {}", byteBuffer.capacity());
LOG.info("byteBuffer0.get().......: {}", byteBuffer.get());
// LOG.info("byteBuffer0.get(byte[]).: {}", byteBuffers0.get(new byte[5])); // ClassCastException
LOG.info("byteBuffer0.get(byte[]->) {}", byteBuffer.get(new byte[5], 0, 5));
LOG.info("byteBuffer0.get(0)......: {}", byteBuffer.get(0));
LOG.info("byteBuffer0.hasArray()..: {}", byteBuffer.hasArray());
LOG.info("byteBuffer0.hasRemaining: {}", byteBuffer.hasRemaining());
LOG.info("byteBuffer0.isDirect()..: {}", byteBuffer.isDirect());
LOG.info("byteBuffer0.isReadOnly(): {}", byteBuffer.isReadOnly());
LOG.info("byteBuffer0.limit().....: {}", limit);
LOG.info("byteBuffer0.limit(0)....: {}", byteBuffer.limit(limit));
LOG.info("byteBuffer0.mark(0).....: {}", byteBuffer.mark());
LOG.info("byteBuffer0.order().....: {}", byteBuffer.order());
LOG.info("byteBuffer0.position()..: {}", position);
LOG.info("byteBuffer0.position(99): {}", byteBuffer.position(99));
LOG.info("byteBuffer0.remaining().: {}", byteBuffer.remaining());
// LOG.info("byteBuffer0.reset().....: {}", byteBuffers0.reset()); // -> InvalidMarkException
LOG.info("byteBuffer0.rewind()....: {}", byteBuffer.rewind());
LOG.info("byteBuffer0.slice().....: {}", byteBuffer.slice());
byteBuffer.rewind();
byteBuffer.position(position);
byteBuffer.limit (limit);
LOG.info("byteBuffer..............: {}", byteBuffer);
}
final BodyPublisher pub = new ByteArrayBodyPublisherIterator(byteBuffers);
LOG_ENABLED.set(false); // Enable Logging now we've got things set up.
final HttpRequest request = HttpRequest.newBuilder()
.uri(new URI("http://localhost:631"))
.headers("Content-Type", "application/ipp")
.POST(pub)
.build();
HttpResponse<byte[]> response = HttpClient
.newBuilder()
.build()
.send(request, HttpResponse.BodyHandlers.ofByteArray());
LOG.info("Result......: {} {}", response, response.body());
}
}