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