这篇文章不直接回答你的问题,但提供了不同的观点。
让您的客户持续致电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 framework中被广泛使用。
参见示例:
2/7/19 更新
如何缓存/重用资源?
这是某种pooling:
池是随时可用的资源集合,而不是在使用时获取并释放
Java 中的一些池:
在实施池时会提出几个问题:
- 什么时候资源实际上应该是
closed?
- 如何在多个线程之间共享资源?
什么时候资源应该是closed?
通常池提供一个显式的 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();
}
}
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 User Guide. Extension model
一如既往地勤奋,我尝试实现finalize() 并在那里测试闭包,但结果证明finalize() 甚至没有被调用(Java10)。这也被标记为已弃用,我相信这个想法会被反对。
您覆盖了finalize,因此它会引发异常,但它们会被忽略。
见Object#finalize
如果 finalize 方法抛出未捕获的异常,则忽略该异常并终止该对象的终结。
您可以在这里做的最好的事情是记录资源泄漏和close资源
明确地说,如果应用程序的测试(使用我的库)没有在我的对象上调用 close(),我希望它们失败。
应用程序测试如何使用您的资源?他们是否使用new 运算符对其进行实例化?
如果是,那么我认为PowerMock 可以帮助您(但我不确定)
如果您在某种工厂背后隐藏了资源的实例化,那么您可以给应用程序测试一些模拟工厂
如果您有兴趣,可以观看此talk。它是俄语的,但仍然可能会有所帮助(我的部分答案基于此演讲)。