前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >给RestTemplate加全局日志打印

给RestTemplate加全局日志打印

原创
作者头像
eeaters
发布2023-08-08 11:30:32
8540
发布2023-08-08 11:30:32
举报
文章被收录于专栏:阿杰

项目中几乎所有的rpc调用都用了RestTemplate,日志并不完善, 同事要对所有请求增加一个日志和响应的日志输出

选用了Interceptor进行RestTemplate的增强,碰到了流只能用一次的问题. 网上有两种解决的方向:

  1. 用拦截器 ClientHttpRequestInterceptor , 但是大多数文章没有解决流的一次性问题.
  2. 用工厂BufferingClientHttpRequestFactory , 但是代码限定了包装的是apache的HttpComponentsClientHttpRequestFactory .我没有这个包, 也不想莫名其妙的就引入这个包

因此决定撸一下源码给一个有理有据的解决方案

看下源码

RestTemplate

代码语言:javascript
复制
@Nullable
protected <T> T doExecute(URI url, @Nullable HttpMethod method, @Nullable RequestCallback requestCallback,
        @Nullable ResponseExtractor<T> responseExtractor) throws RestClientException {
    //...
    try {
        //核心是createRequest构建的request中包含什么逻辑
        ClientHttpRequest request = createRequest(url, method);
        //执行的代码在request.execute()
        response = request.execute();
        handleResponse(url, method, response);
        return (responseExtractor != null ? responseExtractor.extractData(response) : null);
    }
    //...
}
​
protected ClientHttpRequest createRequest(URI url, HttpMethod method) throws IOException {
    ClientHttpRequest request = getRequestFactory().createRequest(url, method);
}

InterceptingHttpAccessor

RestTemplate继承的是该类.

代码语言:javascript
复制
@Override
    public ClientHttpRequestFactory getRequestFactory() {
        List<ClientHttpRequestInterceptor> interceptors = getInterceptors();
        if (!CollectionUtils.isEmpty(interceptors)) {
            ClientHttpRequestFactory factory = this.interceptingRequestFactory;
            if (factory == null) {
                factory = new InterceptingClientHttpRequestFactory(super.getRequestFactory(), interceptors);
                this.interceptingRequestFactory = factory;
            }
            return factory;
        }
        else {
            return super.getRequestFactory();
        }
    }

InterceptingClientHttpRequestFactory

requestTemplate进行远程调用时, 会走到InterceptingClientHttpRequest.execute方法,内部再链到executeInternal上

代码语言:javascript
复制
class InterceptingClientHttpRequest extends AbstractBufferingClientHttpRequest {
​
    private final ClientHttpRequestFactory requestFactory;
​
    private final List<ClientHttpRequestInterceptor> interceptors;
    
    //父类中执行execute时,会调用到子类的executeInternal方法
    @Override
    protected final ClientHttpResponse executeInternal(HttpHeaders headers, byte[] bufferedOutput) throws IOException {
        InterceptingRequestExecution requestExecution = new InterceptingRequestExecution();
        return requestExecution.execute(this, bufferedOutput);
    }
​
}

InterceptingRequestExecution

代码语言:javascript
复制
private class InterceptingRequestExecution implements ClientHttpRequestExecution {
​
    private final Iterator<ClientHttpRequestInterceptor> iterator;
​
    public InterceptingRequestExecution() {
        this.iterator = interceptors.iterator();
    }
​
    @Override
    public ClientHttpResponse execute(HttpRequest request, byte[] body) throws IOException {
        if (this.iterator.hasNext()) {
            ClientHttpRequestInterceptor nextInterceptor = this.iterator.next();
            return nextInterceptor.intercept(request, body, this);
        }
        else {
            HttpMethod method = request.getMethod();
            Assert.state(method != null, "No standard HTTP method");
            ClientHttpRequest delegate = requestFactory.createRequest(request.getURI(), method);
            //...
            return delegate.execute();
        }
    }
}
​

执行时序图

执行调用的时序图
执行调用的时序图

选择方案-RequestFactory

如果需要对响应流进行包装实现可重用. 最佳的位置有两处: 最后一个interceptor或者delegate本身返回的就是可重用流

这里就想到了网上提到的 BufferingClientHttpRequestFactory , 这是源码自身提供的支持, 只不过由于这个功能应该不是通用的功能但是会多占用内存, 因此提供支持, 但是是否用就看开发者本身了

RestTemplateBuilder

代码语言:javascript
复制
public ClientHttpRequestFactory buildRequestFactory() {
        ClientHttpRequestFactory requestFactory = null;
        if (this.requestFactory != null) {
            requestFactory = this.requestFactory.get();
        }
        else if (this.detectRequestFactory) {
            requestFactory = new ClientHttpRequestFactorySupplier().get();
        }
        if (requestFactory != null) {
            if (this.requestFactoryCustomizer != null) {
                this.requestFactoryCustomizer.accept(requestFactory);
            }
        }
        return requestFactory;
    }

ClientHttpRequestFactorySupplier

代码语言:javascript
复制
public class ClientHttpRequestFactorySupplier implements Supplier<ClientHttpRequestFactory> {
​
    private static final Map<String, String> REQUEST_FACTORY_CANDIDATES;
​
    static {
        Map<String, String> candidates = new LinkedHashMap<>();
        candidates.put("org.apache.http.client.HttpClient",
                "org.springframework.http.client.HttpComponentsClientHttpRequestFactory");
        candidates.put("okhttp3.OkHttpClient", "org.springframework.http.client.OkHttp3ClientHttpRequestFactory");
        REQUEST_FACTORY_CANDIDATES = Collections.unmodifiableMap(candidates);
    }
​
    //根据引入的依赖决定加载的是什么工厂
    @Override
    public ClientHttpRequestFactory get() {
        for (Map.Entry<String, String> candidate : REQUEST_FACTORY_CANDIDATES.entrySet()) {
            ClassLoader classLoader = getClass().getClassLoader();
            if (ClassUtils.isPresent(candidate.getKey(), classLoader)) {
                Class<?> factoryClass = ClassUtils.resolveClassName(candidate.getValue(), classLoader);
                return (ClientHttpRequestFactory) BeanUtils.instantiateClass(factoryClass);
            }
        }
        return new SimpleClientHttpRequestFactory();
    }
}

最终测试代码

代码语言:javascript
复制
 @Test
    public void test() {
        RestTemplate restTemplate = new RestTemplate();
        ClientHttpRequestFactory clientHttpRequestFactory = new ClientHttpRequestFactorySupplier().get();
        restTemplate.setRequestFactory(new BufferingClientHttpRequestFactory(clientHttpRequestFactory));
        restTemplate.getInterceptors().add(new LoggingInterceptor());
​
        String forObject = restTemplate.getForObject("http://www.baidu.com", String.class);
        System.out.println("forObject = " + forObject);
    }
​
    //这时候所有的拦截器都可以将body获取到随便用了
    public class LoggingInterceptor implements ClientHttpRequestInterceptor {
        @Override
        public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
            ClientHttpResponse execute = execution.execute(request, body);
            System.out.println("body = " + new String(execute.getBody().readAllBytes()));
            System.out.println("body = " + new String(execute.getBody().readAllBytes()));
            return execute;
        }
    }
​

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 看下源码
    • RestTemplate
      • InterceptingHttpAccessor
        • InterceptingClientHttpRequestFactory
          • InterceptingRequestExecution
          • 执行时序图
          • 选择方案-RequestFactory
            • RestTemplateBuilder
              • ClientHttpRequestFactorySupplier
              • 最终测试代码
              领券
              问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档