拦截Spring的RestTemplate打印日志
简单使用
@Component
@Slf4j
public class RestTemplateBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if (bean instanceof RestTemplate) {
RestTemplate restTemplate = (RestTemplate) bean;
List<ClientHttpRequestInterceptor> interceptors = new ArrayList<>(restTemplate.getInterceptors());
restTemplate.setInterceptors(Collections.emptyList());
//可以多次调用ClientHttpResponse的getBody()方法
BufferingClientHttpRequestFactory requestFactory = new BufferingClientHttpRequestFactory(restTemplate.getRequestFactory());
restTemplate.setRequestFactory(requestFactory);
//打印日志
interceptors.add(new WebLogInterceptor());
restTemplate.setInterceptors(interceptors);
}
return bean;
}
static class WebLogInterceptor implements ClientHttpRequestInterceptor {
@Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
long start = System.currentTimeMillis();
try {
ClientHttpResponse response = execution.execute(request, body);
RequestInfo requestInfo = createRequestInfo(request, body, response, start);
log.info("Request Info : {}", JSON.toJSONString(requestInfo));
return response;
} catch (Throwable e) {
RequestErrorInfo requestErrorInfo = createRequestErrorInfo(request, body, e);
log.error("Error Request Info : {}", JSON.toJSONString(requestErrorInfo), e);
throw e;
}
}
}
private static RequestInfo createRequestInfo(HttpRequest request, byte[] body, ClientHttpResponse response, long start)
throws IOException {
long cost = System.currentTimeMillis() - start;
String result = IOUtils.toString(response.getBody(), StandardCharsets.UTF_8);
RequestInfo requestInfo = new RequestInfo();
requestInfo.setUrl(request.getURI().toString());
requestInfo.setHttpMethod(request.getMethodValue());
try {
MediaType requestContentType = request.getHeaders().getContentType();
if (Objects.nonNull(requestContentType) &&
requestContentType.toString().startsWith("application/json")) {
requestInfo.setRequestParams(JSON.parseObject(new String(body)));
} else {
requestInfo.setRequestParams(new String(body));
}
MediaType responseContentType = response.getHeaders().getContentType();
if (Objects.nonNull(responseContentType) &&
responseContentType.toString().startsWith("application/json")) {
requestInfo.setResult(JSON.parseObject(result));
} else {
requestInfo.setResult(result);
}
} catch (Exception e) {
log.error("创建RequestInfo错误,", e);
requestInfo.setRequestParams(new String(body));
requestInfo.setResult(result);
}
requestInfo.setTimeCost(cost);
return requestInfo;
}
private static RequestErrorInfo createRequestErrorInfo(HttpRequest request, byte[] body, Throwable throwable) {
RequestErrorInfo requestErrorInfo = new RequestErrorInfo();
requestErrorInfo.setUrl(request.getURI().toString());
requestErrorInfo.setHttpMethod(request.getMethodValue());
requestErrorInfo.setExceptionMessage(throwable.getMessage());
try {
MediaType requestContentType = request.getHeaders().getContentType();
if (Objects.nonNull(requestContentType) &&
requestContentType.toString().startsWith("application/json")) {
requestErrorInfo.setRequestParams(JSON.parseObject(new String(body)));
} else {
requestErrorInfo.setRequestParams(new String(body));
}
} catch (Exception e) {
log.error("创建RequestErrorInfo错误,", e);
requestErrorInfo.setRequestParams(new String(body));
}
return requestErrorInfo;
}
@Data
public static class RequestInfo {
private String url;
private String httpMethod;
private Object requestParams;
private Object result;
private Long timeCost;
}
@Data
public static class RequestErrorInfo {
private String url;
private String httpMethod;
private Object requestParams;
private String exceptionMessage;
}
}
通过后置处理器(BeanPostProcessor)拦截 RestTemplate 的初始化过程,给 RestTemplate 添加拦截器。
遇到的问题
问题1
Caused by: java.io.IOException: Attempted read from closed stream.
主要是因为 response.getBody() 只能获取一次,在后续 RestTemplate 获取输出转化时会再次调用 response.getBody(),所以我们需要包装一下。
//可以多次调用ClientHttpResponse的getBody()方法
BufferingClientHttpRequestFactory requestFactory = new BufferingClientHttpRequestFactory(restTemplate.getRequestFactory());
问题2
我们在调用 restTemplate.getRequestFactory() 前必须先调用 restTemplate.setInterceptors(Collections.emptyList()),不然如果之前已经有拦截器了,那么 interceptingRequestFactory 会被包装两次,所有拦截器都会执行两次。
RestTemplate restTemplate = (RestTemplate) bean;
restTemplate.getInterceptors().add(new TestInterceptor()); // 模拟在其他地方设置了拦截器
List<ClientHttpRequestInterceptor> interceptors = new ArrayList<>(restTemplate.getInterceptors());
// restTemplate.setInterceptors(Collections.emptyList()); // 如果不置空就会有问题
//可以多次调用ClientHttpResponse的getBody()方法
BufferingClientHttpRequestFactory requestFactory = new BufferingClientHttpRequestFactory(restTemplate.getRequestFactory());
restTemplate.setRequestFactory(requestFactory);
//打印日志
interceptors.add(new WebLogInterceptor());
restTemplate.setInterceptors(interceptors);
return restTemplate;