当应始终正确关闭响应时如何重构代码?
How to refactor the code when the response should always be closed properly?
在我们的一个项目中,我们需要使用httpClient
从后端服务获取一些数据。我们发现遗留代码没有正确关闭响应。
代码如下:
HttpResponse response = httpClient.execute(request);
int statusCode = response.getStatusLine().getStatusCode();
if (statusCode == 200) {
return EntityUtils.toString(response.getEntity());
} else {
throw new InvalidResponseException();
}
当statusCode
为200
时,EntityUtils.toString
会消费响应内容,然后正常关闭。但在其他情况下,响应未关闭,我们将有 http 连接泄漏(一段时间后,httpClient 池 运行 起来,我们无法获取新线程)
代码库中有很多这样的代码,所以我想使用加载设计模式来简化它。
我定义了一个HttpClientWrapper
,比如:
class HttpClientWrapper {
private HttpClient client;
public HttpClientWrapper(HttpClient client) {
this.client = client;
}
public <T> T execute(HttpRequestBase request, WithResponse<T> handler) {
HttpResponse response = null;
try {
response = client.execute(request);
return handler.withResponse(response);
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
if (response != null) {
EntityUtils.consumeQuietly(response.getEntity());
}
}
}
}
interface WithResponse<T> {
T withResponse(HttpResponse response) throws Exception;
}
我在 finally
中使用了包装器中的响应,因此响应将始终正确关闭。我可以愉快地使用它来更改现有代码:
return new HttpClientWrapper(httpClient).execute(request, new WithResponse<String>() {
String withResponse(HttpResponse response) throws Exception {
int statusCode = response.getStatusLine().getStatusCode();
if (statusCode == 200) {
return EntityUtils.toString(response.getEntity());
} else {
throw new InvalidResponseException();
}
}
});
再也不用担心漏水了
但是突然发现了这么一段代码:
Stopwatch stopwatch = new Stopwatch();
try {
stopwatch.start();
HttpResponse response = httpClient.execute( request );
stopwatch.stop();
MDC.put( "backendTime", String.valueOf( stopwatch.elapsed( TimeUnit.MILLISECONDS ) ) );
return EntityUtils.toString(response.getEntity());
} catch( IOException e ) {
throw new RuntimeException( e );
}
它需要检查httpClient 用于获取响应的时间!我不能在这里使用 HttpClientWrapper
,因为我找不到一种方法来衡量每个当前设计的整个过程的一部分。
我现在有两个选择:
不要使用此代码的HttpClientWrapper
,我们需要手动关闭响应。 (但是获取响应的方式已经不一致了)
修改 HttpClientWrapper
,使其足够复杂和灵活以满足此要求。 (不过只有一个地方需要)
我都不喜欢,还有其他更好的解决方案吗?
是什么阻止您在包装器之前初始化 StopWatch
并在回调中停止它?
final Stopwatch stopwatch = new Stopwatch();
stopwatch.start();
new HttpClientWrapper(httpClient).execute(request, new WithResponse<String>() {
String withResponse(HttpResponse response) throws Exception {
stopwatch.stop();
MDC.put( "backendTime", String.valueOf( stopwatch.elapsed( TimeUnit.MILLISECONDS ) ) );
// ...
}
});
在我们的一个项目中,我们需要使用httpClient
从后端服务获取一些数据。我们发现遗留代码没有正确关闭响应。
代码如下:
HttpResponse response = httpClient.execute(request);
int statusCode = response.getStatusLine().getStatusCode();
if (statusCode == 200) {
return EntityUtils.toString(response.getEntity());
} else {
throw new InvalidResponseException();
}
当statusCode
为200
时,EntityUtils.toString
会消费响应内容,然后正常关闭。但在其他情况下,响应未关闭,我们将有 http 连接泄漏(一段时间后,httpClient 池 运行 起来,我们无法获取新线程)
代码库中有很多这样的代码,所以我想使用加载设计模式来简化它。
我定义了一个HttpClientWrapper
,比如:
class HttpClientWrapper {
private HttpClient client;
public HttpClientWrapper(HttpClient client) {
this.client = client;
}
public <T> T execute(HttpRequestBase request, WithResponse<T> handler) {
HttpResponse response = null;
try {
response = client.execute(request);
return handler.withResponse(response);
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
if (response != null) {
EntityUtils.consumeQuietly(response.getEntity());
}
}
}
}
interface WithResponse<T> {
T withResponse(HttpResponse response) throws Exception;
}
我在 finally
中使用了包装器中的响应,因此响应将始终正确关闭。我可以愉快地使用它来更改现有代码:
return new HttpClientWrapper(httpClient).execute(request, new WithResponse<String>() {
String withResponse(HttpResponse response) throws Exception {
int statusCode = response.getStatusLine().getStatusCode();
if (statusCode == 200) {
return EntityUtils.toString(response.getEntity());
} else {
throw new InvalidResponseException();
}
}
});
再也不用担心漏水了
但是突然发现了这么一段代码:
Stopwatch stopwatch = new Stopwatch();
try {
stopwatch.start();
HttpResponse response = httpClient.execute( request );
stopwatch.stop();
MDC.put( "backendTime", String.valueOf( stopwatch.elapsed( TimeUnit.MILLISECONDS ) ) );
return EntityUtils.toString(response.getEntity());
} catch( IOException e ) {
throw new RuntimeException( e );
}
它需要检查httpClient 用于获取响应的时间!我不能在这里使用 HttpClientWrapper
,因为我找不到一种方法来衡量每个当前设计的整个过程的一部分。
我现在有两个选择:
不要使用此代码的
HttpClientWrapper
,我们需要手动关闭响应。 (但是获取响应的方式已经不一致了)修改
HttpClientWrapper
,使其足够复杂和灵活以满足此要求。 (不过只有一个地方需要)
我都不喜欢,还有其他更好的解决方案吗?
是什么阻止您在包装器之前初始化 StopWatch
并在回调中停止它?
final Stopwatch stopwatch = new Stopwatch();
stopwatch.start();
new HttpClientWrapper(httpClient).execute(request, new WithResponse<String>() {
String withResponse(HttpResponse response) throws Exception {
stopwatch.stop();
MDC.put( "backendTime", String.valueOf( stopwatch.elapsed( TimeUnit.MILLISECONDS ) ) );
// ...
}
});