我能够钩入
java.net.Socket
和
java.net.ServerSocket
,并监视这些类的
所有新实例。完整代码可以在
源代码库中看到。以下是方法概述:
当实例化Socket或ServerSocket时,其构造函数中的第一件事就是调用
setImpl()
,该函数实例化了真正实现Socket功能的对象。默认实现是
java.net.SocksSocketImpl
的一个实例,但是可以通过设置自定义
java.net.SocketImplFactory
来覆盖它,通过
java.net.Socket#setSocketImplFactory
和
java.net.ServerSocket#setSocketFactory
。
由于
java.net.SocketImpl
的所有实现都是包私有的,但是通过一点反射就不太难了。
private static SocketImpl newSocketImpl() {
try {
Class<?> defaultSocketImpl = Class.forName("java.net.SocksSocketImpl");
Constructor<?> constructor = defaultSocketImpl.getDeclaredConstructor();
constructor.setAccessible(true);
return (SocketImpl) constructor.newInstance();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
用于在创建所有套接字时进行监听的SocketImplFactory实现大致如下:
final List<SocketImpl> allSockets = Collections.synchronizedList(new ArrayList<SocketImpl>());
ServerSocket.setSocketFactory(new SocketImplFactory() {
public SocketImpl createSocketImpl() {
SocketImpl socket = newSocketImpl();
allSockets.add(socket);
return socket;
}
});
请注意,setSocketFactory / setSocketImplFactory只能调用一次,因此您要么需要仅有一个测试执行此操作(就像我所拥有的那样),要么必须创建一个静态单例(令人厌烦!)来保存该spy。
然后问题是如何找出套接字是否关闭?Socket和ServerSocket都有一个isClosed()方法,但是它们使用了一个布尔值来跟踪它们是否被关闭 - SocketImpl实例没有一种简单的方式来检查它是否已关闭。(顺便说一下,Socket和ServerSocket都由SocketImpl支持 - 没有“ServerSocketImpl”)。
幸运的是,SocketImpl引用了它所支持的Socket或ServerSocket。上述的setImpl()方法调用impl.setSocket(this)或impl.setServerSocket(this),可以通过调用java.net.SocketImpl#getSocket或java.net.SocketImpl#getServerSocket来获取该引用。
再次强调,这些方法是包私有的,因此需要进行一些反射:
private static Socket getSocket(SocketImpl impl) {
try {
Method getSocket = SocketImpl.class.getDeclaredMethod("getSocket");
getSocket.setAccessible(true);
return (Socket) getSocket.invoke(impl);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
private static ServerSocket getServerSocket(SocketImpl impl) {
try {
Method getServerSocket = SocketImpl.class.getDeclaredMethod("getServerSocket");
getServerSocket.setAccessible(true);
return (ServerSocket) getServerSocket.invoke(impl);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
请注意,在SocketImplFactory中可能无法调用getSocket/getServerSocket,因为Socket/ServerSocket仅在从那里返回SocketImpl之后才设置它们。
现在我们已经准备好所有必要的基础设施,可以在测试中检查有关Socket/ServerSocket的任何内容。
for (SocketImpl impl : allSockets) {
assertIsClosed(getSocket(impl));
}
完整的源代码在这里。