Этот пост не дает прямого ответа на ваш вопрос, но дает другую точку зрения.
Один из способов заставить ваших клиентов постоянно звонить close
- это освободить их от этой ответственности.
Как вы можете это сделать?
Использовать шаблон шаблона.
Реализация эскиза
Вы упомянули, что работаете с TCP, поэтому давайте предположим, чтоу вас есть класс TcpConnection
, который имеет метод close()
.
Давайте определим TcpConnectionOpertaions
интерфейс:
public interface TcpConnectionOperations {
<T> T doWithConnection(TcpConnectionAction<T> action);
}
и реализуем его:
public class TcpConnectionTemplate implements TcpConnectionOperations {
@Override
public <T> T doWithConnection(TcpConnectionAction<T> action) {
try (TcpConnection tcpConnection = getConnection()) {
return action.doWithConnection(tcpConnection);
}
}
}
TcpConnectionAction
- это просто обратный вызов, ничего особенного.
public interface TcpConnectionAction<T> {
T doWithConnection(TcpConnection tcpConnection);
}
Как использовать библиотеку сейчас?
- Она должна потребляться только через *Интерфейс 1034 *.
- Действия потребителей:
Например:
String s = tcpConnectionOperations.doWithConnection(connection -> {
// do what we with with the connection
// returning to string for example
return connection.toString();
});
Плюсы
- Клиентам не нужнобеспокойство о:
- Вы управляете созданием соединений:
- множество других вариантов использования ...
- В тестах вы можете указать макет
TcpConnectionOperations
и макет TcpConnections
и выдвинуть против них утверждение
Минусы
Этот подход может не работать, если жизненный цикл ресурсадлиннее action
.Например, клиенту необходимо хранить ресурс в течение более длительного времени.
Тогда вы можете захотеть углубиться в ReferenceQueue
/ Cleaner
(начиная с Java 9) и связанных API.
Вдохновлен Spring Framework
Этот шаблон широко используется в Spring Framework .
См. например:
Обновление 2/7/19
Как я могу кешировать / повторно использовать ресурс?
Это что-то вродеиз пул :
пул - это набор ресурсов, которые хранятся готовыми к использованию, а не приобретаются при использовании и высвобождаются
некоторые пулы в Java:
WhПри реализации пула возникает несколько вопросов:
- Когда ресурс действительно должен быть
close
d? - Как ресурс должен быть распределен между несколькими потоками?
Когда ресурс должен быть close
d?
Обычно пулы предоставляют явный метод close
(он может иметь другое имя, ноцель та же), которая закрывает все имеющиеся ресурсы.
Какон может быть разделен между несколькими потоками?
Это зависит от вида самого ресурса.
Обычно требуется, чтобы только один поток обращался к одному ресурсу.
Это можно сделать с помощью некоторого типа блокировки
Демо
Обратите внимание, что приведенный здесь код предназначен только для демонстрационных целей. Он имеет ужасную производительность и нарушает некоторые принципы ООП.
IpAndPort.java
@Value
public class IpAndPort {
InetAddress address;
int port;
}
TcpConnection.java
@Data
public class TcpConnection {
private static final AtomicLong counter = new AtomicLong();
private final IpAndPort ipAndPort;
private final long instance = counter.incrementAndGet();
public void close() {
System.out.println("Closed " + this);
}
}
CachingTcpConnectionTemplate.java
public class CachingTcpConnectionTemplate implements TcpConnectionOperations {
private final Map<IpAndPort, TcpConnection> cache
= new HashMap<>();
private boolean closed;
public CachingTcpConnectionTemplate() {
System.out.println("Created new template");
}
@Override
public synchronized <T> T doWithConnectionTo(IpAndPort ipAndPort, TcpConnectionAction<T> action) {
if (closed) {
throw new IllegalStateException("Closed");
}
TcpConnection tcpConnection = cache.computeIfAbsent(ipAndPort, this::getConnection);
try {
System.out.println("Executing action with connection " + tcpConnection);
return action.doWithConnection(tcpConnection);
} finally {
System.out.println("Returned connection " + tcpConnection);
}
}
private TcpConnection getConnection(IpAndPort ipAndPort) {
return new TcpConnection(ipAndPort);
}
@Override
public synchronized void close() {
if (closed) {
throw new IllegalStateException("closed");
}
closed = true;
for (Map.Entry<IpAndPort, TcpConnection> entry : cache.entrySet()) {
entry.getValue().close();
}
System.out.println("Template closed");
}
}
Тестирование инфраструктуры
TcpConnectionOperationsParameterResolver.java
public class TcpConnectionOperationsParameterResolver implements ParameterResolver, AfterAllCallback {
private final CachingTcpConnectionTemplate tcpConnectionTemplate = new CachingTcpConnectionTemplate();
@Override
public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) throws ParameterResolutionException {
return parameterContext.getParameter().getType().isAssignableFrom(CachingTcpConnectionTemplate.class)
&& parameterContext.isAnnotated(ReuseTemplate.class);
}
@Override
public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) throws ParameterResolutionException {
return tcpConnectionTemplate;
}
@Override
public void afterAll(ExtensionContext context) throws Exception {
tcpConnectionTemplate.close();
}
}
ParameterResolver
и AfterAllCallback
от JUnit.
@ReuseTemplate
- это пользовательская аннотация
ReuseTemplate.java
:
@Retention(RetentionPolicy.RUNTIME)
public @interface ReuseTemplate {
}
Наконец, тест:
@ExtendWith(TcpConnectionOperationsParameterResolver.class)
public class Tests2 {
private final TcpConnectionOperations tcpConnectionOperations;
public Tests2(@ReuseTemplate TcpConnectionOperations tcpConnectionOperations) {
this.tcpConnectionOperations = tcpConnectionOperations;
}
@Test
void google80() throws UnknownHostException {
tcpConnectionOperations.doWithConnectionTo(new IpAndPort(InetAddress.getByName("google.com"), 80), tcpConnection -> {
System.out.println("Using " + tcpConnection);
return tcpConnection.toString();
});
}
@Test
void google80_2() throws Exception {
tcpConnectionOperations.doWithConnectionTo(new IpAndPort(InetAddress.getByName("google.com"), 80), tcpConnection -> {
System.out.println("Using " + tcpConnection);
return tcpConnection.toString();
});
}
@Test
void google443() throws Exception {
tcpConnectionOperations.doWithConnectionTo(new IpAndPort(InetAddress.getByName("google.com"), 443), tcpConnection -> {
System.out.println("Using " + tcpConnection);
return tcpConnection.toString();
});
}
}
Запуск:
$ mvn test
Выход:
Created new template
[INFO] Running Tests2
Executing action with connection TcpConnection(ipAndPort=IpAndPort(address=google.com/74.125.131.102, port=80), instance=1)
Using TcpConnection(ipAndPort=IpAndPort(address=google.com/74.125.131.102, port=80), instance=1)
Returned connection TcpConnection(ipAndPort=IpAndPort(address=google.com/74.125.131.102, port=80), instance=1)
Executing action with connection TcpConnection(ipAndPort=IpAndPort(address=google.com/74.125.131.102, port=443), instance=2)
Using TcpConnection(ipAndPort=IpAndPort(address=google.com/74.125.131.102, port=443), instance=2)
Returned connection TcpConnection(ipAndPort=IpAndPort(address=google.com/74.125.131.102, port=443), instance=2)
Executing action with connection TcpConnection(ipAndPort=IpAndPort(address=google.com/74.125.131.102, port=80), instance=1)
Using TcpConnection(ipAndPort=IpAndPort(address=google.com/74.125.131.102, port=80), instance=1)
Returned connection TcpConnection(ipAndPort=IpAndPort(address=google.com/74.125.131.102, port=80), instance=1)
Closed TcpConnection(ipAndPort=IpAndPort(address=google.com/74.125.131.102, port=80), instance=1)
Closed TcpConnection(ipAndPort=IpAndPort(address=google.com/74.125.131.102, port=443), instance=2)
Template closed
Ключевое наблюдение здесь заключается в том, что соединения используются повторно (см. "instance=
")
Это упрощенный пример того, что можно сделать.Конечно, в реальном мире объединение соединений не так просто.Пул не должен расти бесконечно, соединения могут быть сохранены только в течение определенного периода времени и так далее.Обычно некоторые проблемы решаются наличием чего-то в фоновом режиме.
Возвращаясь к вопросу
Я не вижу, как использовать try-with-resources statement
в контексте тестов(Я использую JUnit5
с Mockito
), поскольку «ресурс» не является недолговечным - он является частью тестового устройства.
См. Пользователь Junit 5Руководство.Модель расширения
Будучи старательным, как всегда, я пытался реализовать finalize()
и тестировать на замыкание там, но оказывается, что finalize()
даже не вызывается (Java10).Это также помечено как устаревшее, и я уверен, что эта идея будет отвергнута.
Вы перегрузили finalize
, так что это вызывает исключение, но они игнорируются.
См.Object#finalize
Если метод finalize создает необработанное исключение, оно игнорируется и завершается завершение этого объекта.
лучшее, что вы можете здесь сделать - это зарегистрировать утечку ресурса и close
ресурс
Чтобы было ясно, я хочу, чтобы тесты приложения (использующие мою библиотеку) не выполнялись, если они не вызывают close()
на моих объектах.
Как тесты приложений используют ваш ресурс?Они создают его, используя оператор new
?Если да, то я думаю, что PowerMock может помочь вам (но я не уверен)
Если у вас есть скрытая реализация ресурса за какой-то фабрикой, то вы можете дать тесты приложениякакая-то фальшивая фабрика
Если вам интересно, вы можете посмотреть этот разговор .Это на русском языке, но все еще может быть полезным (часть моего ответа основана на этом разговоре).