Ответ 1
Мне удалось подключиться к java.net.Socket
и java.net.ServerSocket
и просмотреть все новые экземпляры этих классов. Полный код можно увидеть в исходном репозитории. Вот краткий обзор подхода:
При создании экземпляра Socket или ServerSocket первым делом в его конструкторе является вызов setImpl()
, который создает экземпляр объекта, который действительно реализует функцию сокета. Реализация по умолчанию представляет собой экземпляр 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 можно вызывать только один раз, поэтому вам нужно либо иметь только один тест, который делает это (например, у меня есть), либо вы должны создать статический синглтон (yuck!) для хранения этого шпиона.
Тогда возникает вопрос, что как узнать, закрыт ли сокет? У Socket и ServerSocket есть метод isClosed()
, но он использует логическое внутреннее для этих классов для отслеживания того, закрыт ли он - экземпляр SocketImpl не имеет простого способа проверить, закрыт ли он. (BTW, как 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);
}
}
Обратите внимание, что getSocket/getServerSocket не может быть вызван внутри SocketImplFactory, потому что Socket/ServerSocket устанавливает их только после возвращения SocketImpl оттуда.
Теперь есть вся инфраструктура, необходимая для проверки в наших тестах, что мы хотим о Socket/ServerSocket:
for (SocketImpl impl : allSockets) {
assertIsClosed(getSocket(impl));
}
Полный исходный код здесь.