这篇文章没有直接回答你的问题,但提供了不同的观点。
让您的客户始终如一地打电话的一种方法是将他们从这种责任中解放出来。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框架中被广泛使用。
例如,请参阅:
更新 2019/2/7
如何缓存/重用资源?
这是某种池化:
池是随时可供使用的资源的集合,而不是在使用时获得和释放
Java中的一些池:
实现池时,会提出几个问题:
- 资源何时实际应为 d?
close
- 如何在多个线程之间共享资源?
何时应关闭
资源 d?
通常,池提供显式关闭
方法(它可能具有不同的名称,但目的是相同的),该方法关闭所有持有的资源。
如何在多个线程之间共享它?
它取决于资源本身的一种。
通常,您希望确保只有一个线程访问一个资源。
这可以使用某种锁定来完成
演示
请注意,此处提供的代码仅用于演示目的 它具有糟糕的性能并违反了某些 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 用户指南。扩展模型
一如既往地勤奋,我尝试在那里实现和测试闭包,但事实证明它甚至没有被称为(Java10)。这也被标记为已弃用,我相信这个想法会不受欢迎。finalize()
finalize()
您覆盖,以便它引发异常,但它们被忽略。finalize
请参阅对象#finalize
如果 finalize 方法引发未捕获的异常,则忽略该异常并终止该对象的终结。
您在这里可以做的最好的事情就是记录资源泄漏和资源close
为了清楚起见,我希望应用程序的测试(使用我的库)在不调用我的对象时失败。close()
应用程序测试如何使用您的资源?他们是否使用运算符实例化它?如果是,那么我认为PowerMock可以帮助你(但我不确定)new
如果你在某种工厂后面隐藏了资源的实例化,那么你可以给应用程序测试一些模拟工厂。
如果你有兴趣,你可以观看这个演讲。它是俄语的,但仍然可能有帮助(我的部分答案是基于这个演讲)。