这篇文章不直接回答你的问题,但提供了不同的观点。
让您的客户持续致电的一种方法close
是将他们从这种责任中解放出来。
你怎么能这样做?
使用模板模式。
草图实现
您提到您正在使用 TCP,所以让我们假设您有一个TcpConnection
具有close()
方法的类。
让我们定义TcpConnectionOperations
接口:
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);
}
图书馆现在应该怎么消费?
- 只能通过
TcpConnectionOperations
接口消费。
- 消费者提供行动
例如:
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 框架
这种模式在Spring 框架中被广泛使用。
参见例如:
2/7/19 更新
如何缓存/重用资源?
这是某种池化:
池是随时可用的资源集合,而不是在使用时获取并释放
Java中的一些池:
在实施池时,提出了几个问题:
- 什么时候资源实际上应该是
close
d?
- 资源应该如何在多个线程之间共享?
什么时候资源应该是close
d?
通常池提供一个显式close
方法(它可能有不同的名称,但目的是相同的),它关闭所有持有的资源。
它如何跨多个线程共享?
它取决于一种资源本身。
通常你想确保只有一个线程访问一个资源。
这可以使用某种锁定来完成
演示
请注意,此处提供的代码仅用于演示目的它具有糟糕的性能并且违反了一些 OOP 原则。
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();
}
}
和来自 JUnit ParameterResolver
。AfterAllCallback
@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
with Mockito
),因为“资源”不是短暂的 - 它是测试夹具的一部分。
请参阅Junit 5 用户指南。扩展模型
一如既往地勤奋,我尝试finalize()
在那里实现和测试闭包,但结果finalize()
甚至没有被称为(Java10)。这也被标记为已弃用,我相信这个想法会不受欢迎。
您覆盖finalize
了它以引发异常,但它们被忽略了。
看Object#finalize
如果 finalize 方法抛出未捕获的异常,则忽略该异常并终止该对象的终结。
您可以在这里做的最好的事情是记录资源泄漏和close
资源
需要明确的是,如果应用程序的测试(使用我的库)不调用close()
我的对象,我希望它们失败。
应用程序测试如何使用您的资源?他们是否使用new
运算符对其进行实例化?如果是,那么我认为PowerMock可以帮助您(但我不确定)
如果您在某种工厂背后隐藏了资源的实例化,那么您可以为应用程序测试一些模拟工厂
有兴趣的可以看这个讲座。它是俄语的,但仍然可能会有所帮助(我的部分答案基于此演讲)。