当应始终正确关闭响应时如何重构代码?

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();
}

statusCode200时,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,因为我找不到一种方法来衡量每个当前设计的整个过程的一部分。

我现在有两个选择:

  1. 不要使用此代码的HttpClientWrapper,我们需要手动关闭响应。 (但是获取响应的方式已经不一致了)

  2. 修改 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 ) ) );

        // ...
    }
});