springboot集成resttemplate時想打印相關請求日誌,設置統一的攔截器
攔截器相關代碼:
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpRequest;
import org.springframework.http.client.ClientHttpRequestExecution;
import org.springframework.http.client.ClientHttpRequestInterceptor;
import org.springframework.http.client.ClientHttpResponse;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
/**
* resttempate日誌記錄
*
* @author qijun
* @date 2020/3/16 0016 17:27
*/
public class RestTemplateLogRecordInterceptor implements ClientHttpRequestInterceptor {
private final static Logger LOGGER = LoggerFactory.getLogger(RestTemplateLogRecordInterceptor.class);
@Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
traceRequest(request, body);
ClientHttpResponse response = execution.execute(request, body);
traceResponse(response);
return response;
}
private void traceRequest(HttpRequest request, byte[] body) throws IOException {
LOGGER.debug("===========================request begin================================================");
LOGGER.debug("URI : {}", request.getURI());
LOGGER.debug("Method : {}", request.getMethod());
LOGGER.debug("Headers : {}", request.getHeaders());
LOGGER.debug("Request body: {}", new String(body, "UTF-8"));
LOGGER.debug("==========================request end================================================");
}
private void traceResponse(ClientHttpResponse response) throws IOException {
StringBuilder inputStringBuilder = new StringBuilder();
InputStream body = response.getBody();
try (BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(body, "UTF-8"))) {
String line = bufferedReader.readLine();
while (line != null) {
inputStringBuilder.append(line);
inputStringBuilder.append('\n');
line = bufferedReader.readLine();
}
}
LOGGER.debug("============================response begin==========================================");
LOGGER.debug("Status code : {}", response.getStatusCode());
LOGGER.debug("Status text : {}", response.getStatusText());
LOGGER.debug("Headers : {}", response.getHeaders());
LOGGER.debug("Response body: {}", inputStringBuilder.toString());
LOGGER.debug("=======================response end=================================================");
}
}
配置resttemplate自定義bean
@Configuration
public class RestTemplateConfiguration {
@Bean
public RestTemplate getRestTemplate() {
SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
//motor服務有的服務響應時間較長,暫定半個小時
factory.setReadTimeout(1800000);
factory.setConnectTimeout(1800000);
RestTemplate restTemplate = new RestTemplate(factory);
restTemplate.getInterceptors().add(new RestTemplateLogRecordInterceptor());
restTemplate.getMessageConverters().set(1, new StringHttpMessageConverter(StandardCharsets.UTF_8));
return restTemplate;
}
}
經過上述運行後,會出現一個問題,問題如下:
試過很多方法依舊找不到原因,不知道爲啥會被close掉
解決方案:懷疑是我加了攔截器的原因,我把攔截器註釋掉,則不會報該錯誤,思考:爲啥加了攔截器就會報錯呢?
只能一步一步跟蹤源碼,發現只要加了攔截器,inputStream中的close屬性就爲true,所以這也是導致爲啥調取接口的時候會is close
接下來就是找到爲啥inputStream中的close爲啥爲true?
後來某大神看了這段代碼說response和request流只能讀取一次,只要讀取完就結束了
private void traceResponse(ClientHttpResponse response) throws IOException {
StringBuilder inputStringBuilder = new StringBuilder();
InputStream body = response.getBody();
try (BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(body, "UTF-8"))) {
String line = bufferedReader.readLine();
while (line != null) {
inputStringBuilder.append(line);
inputStringBuilder.append('\n');
line = bufferedReader.readLine();
}
}
LOGGER.debug("============================response begin==========================================");
LOGGER.debug("Status code : {}", response.getStatusCode());
LOGGER.debug("Status text : {}", response.getStatusText());
LOGGER.debug("Headers : {}", response.getHeaders());
LOGGER.debug("Response body: {}", inputStringBuilder.toString());
LOGGER.debug("=======================response end=================================================");
}
我嘗試了一次,把該代碼註釋掉,果然不出所料,是這裏的原因,那麼接下來就是如何解決流只能讀取一次的問題,上網搜索了一番,網上好多人解決都是說用包裝類可以解決該問題,將該對象緩存下來,就不會有問題,這也就是Servlet中Fileter的實現,Filter調用鏈如果不包裝,也可能會出現該問題
好了,現在找到解決方案了,那麼如何將該返回的ClientHttpResponse 包裝一下呢,又不想自己自定義,後來就在org.springframework.http.client下面找了找,發現有類似的類,但是可用的包裝類都是protected的,那麼就想一下既然他提供了類似的包裝類,那麼看他內部咋調用類似的包裝類,只能一點一點看源碼,最後發現一個類BufferingClientHttpRequestWrapper,但是這個也是內部調用類,外部是無法新建的,只要找調用該類的地方,發現BufferingClientHttpRequestFactory類中有相關的實現,而且這個剛好是我需要的,剛好該類是public修飾,有點意思哈,不得不驚歎作者設計模式(工廠模式)玩的很溜啊
既然找到解決方案,代碼修改如下:
在RestTemplate定義的時候,將BufferingClientHttpRequestFactory傳入,而非傳入SimpleClientHttpRequestFactory,最終解決問題
@Bean
public RestTemplate getRestTemplate() {
SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
BufferingClientHttpRequestFactory bufferingClientHttpRequestFactory = new BufferingClientHttpRequestFactory(factory);
//motor服務有的服務響應時間較長,暫定半個小時
factory.setReadTimeout(1800000);
factory.setConnectTimeout(1800000);
RestTemplate restTemplate = new RestTemplate(bufferingClientHttpRequestFactory);
restTemplate.getInterceptors().add(new RestTemplateLogRecordInterceptor());
restTemplate.getMessageConverters().set(1, new StringHttpMessageConverter(StandardCharsets.UTF_8));
return restTemplate;
}