如何配合OpenFeign优雅的记录请求以及返回的信息

1 篇文章 0 订阅
1 篇文章 0 订阅

结合上一篇 如何配合RestTemplate优雅的记录请求以及返回的信息 我们可以很方便的利用restTemplate提供的Interceptor记录信息,出于经验的问题我们是不是也可以通过OpenFeign找到它的Interceptor然后这么实现的呢?其实不然。

我们可以通过@EnableFeignClients看到@Import(FeignClientsRegistrar.class) @Import注解将指定的类作为Bean注入到Spring容器中。FeignClientsRegistrar主要是根据定义路径加载扫描被FeignClient相应的类,注入bean之后,通过jdk的代理,当请求Feign Client的方法时会被拦截

本文主要分析为什么不能利用openFeign提供的RequestInterceptor 进行处理,因为这个InterceptorRestTemplate#Interceptor区别很大,他只能做请求前的处理(eg: 接口签名、统一标示、认证信息等等)。OpenFeign原理 原理不作为本文的主题。

代码 ReflectiveFeign.class#newInstance(Target<T> target)

public <T> T newInstance(Target<T> target) {
   Map<String, MethodHandler> nameToHandler = targetToHandlersByName.apply(target);
   Map<Method, MethodHandler> methodToHandler = new LinkedHashMap<Method, MethodHandler>();
   List<DefaultMethodHandler> defaultMethodHandlers = new LinkedList<DefaultMethodHandler>();

   for (Method method : target.type().getMethods()) {
     if (method.getDeclaringClass() == Object.class) {
       continue;
     } else if (Util.isDefault(method)) {
       DefaultMethodHandler handler = new DefaultMethodHandler(method);
       defaultMethodHandlers.add(handler);
       methodToHandler.put(method, handler);
     } else {
       methodToHandler.put(method, nameToHandler.get(Feign.configKey(target.type(), method)));
     }
   }
   InvocationHandler handler = factory.create(target, methodToHandler);
   T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(),
       new Class<?>[] {target.type()}, handler);

   for (DefaultMethodHandler defaultMethodHandler : defaultMethodHandlers) {
     defaultMethodHandler.bindTo(proxy);
   }
   return proxy;
 }
 
 
 // ParseHandlersByName.class#apply(Target key) 
  public Map<String, MethodHandler> apply(Target key) {
     List<MethodMetadata> metadata = contract.parseAndValidatateMetadata(key.type());
     Map<String, MethodHandler> result = new LinkedHashMap<String, MethodHandler>();
     for (MethodMetadata md : metadata) {
       BuildTemplateByResolvingArgs buildTemplate;
       if (!md.formParams().isEmpty() && md.template().bodyTemplate() == null) {
         buildTemplate = new BuildFormEncodedTemplateFromArgs(md, encoder, queryMapEncoder);
       } else if (md.bodyIndex() != null) {
         buildTemplate = new BuildEncodedTemplateFromArgs(md, encoder, queryMapEncoder);
       } else {
         buildTemplate = new BuildTemplateByResolvingArgs(md, queryMapEncoder);
       }
       result.put(md.configKey(),
           factory.create(key, md, buildTemplate, options, decoder, errorDecoder));
     }
     return result;
   }

可以看出为每个方法生成一个代理类 factory.create... 每当目标方法调用时都会被SynchronousMethodHandler 进行处理根据参数生成RequestTemplate对象。SynchronousMethodHandler.class#invoke(Object[] argv)

@Override
 public Object invoke(Object[] argv) throws Throwable {
   RequestTemplate template = buildTemplateFromArgs.create(argv);
   Retryer retryer = this.retryer.clone();
   while (true) {
     try {
       return executeAndDecode(template);
     } catch (RetryableException e) {
       try {
         retryer.continueOrPropagate(e);
       } catch (RetryableException th) {
         Throwable cause = th.getCause();
         if (propagationPolicy == UNWRAP && cause != null) {
           throw cause;
         } else {
           throw th;
         }
       }
       if (logLevel != Logger.Level.NONE) {
         logger.logRetry(metadata.configKey(), logLevel);
       }
       continue;
     }
   }
 }
 
 Object executeAndDecode(RequestTemplate template) throws Throwable {
   Request request = targetRequest(template);
   if (logLevel != Logger.Level.NONE) {
     logger.logRequest(metadata.configKey(), logLevel, request);
   }

   Response response;
   long start = System.nanoTime();
   try {
     response = client.execute(request, options);
   ...
}
 Request targetRequest(RequestTemplate template) {
   for (RequestInterceptor interceptor : requestInterceptors) {
     interceptor.apply(template);
   }
   return target.apply(template);
 }
 

看到RequestInterceptor 被调用的地方,所以FeignRequestInterceptor是在请求前在创建RequestTemplate的时候。

那么怎么才能让OpenFeign记录请求和响应日志呢。

我们看到封装完请求信息。response = client.execute(request, options);才是执行request请求以及接收response响应。Client.java。重写这个client,spring 容器启动的时候创建我们重写的client 便可以实现。那么是没有大问题。但是为什么要自己重写的呢?毕竟不一定都会有这种需求。官方也没给出解释,只是建议重写client。根据上一篇restTemplate记录信息,我们是不是可以按照restTemplate写法(责任链模式)进行封装扩展造轮子呢?结果可能让你很失望。因为我们没有类似BufferingClientHttpRequestFactory东西进行流copy,因为feign提供的Response.class是final类型的,我们没有办法通过自己进行流copy,这个准备提个issues问问。自己重写Client 代码如下:

/**
* @author liweigao
* @date 2019/8/26 上午10:17
*/
@Slf4j
public class SuperClient extends Client.Default {

   private static final String CONTENT_TYPE = "Content-Type";

   /**
    * Null parameters imply platform defaults.
    *
    * @param sslContextFactory
    * @param hostnameVerifier
    */
   public SuperClient(SSLSocketFactory sslContextFactory, HostnameVerifier hostnameVerifier) {
       super(sslContextFactory, hostnameVerifier);
   }

   @Override
   public Response execute(Request request, Request.Options options) throws IOException {

       StopWatch stopWatch = new StopWatch();
       stopWatch.start();
       String errorMsg = null;

       BufferingFeignClientResponse bufferingFeignClientResponse = null;
       try {
           bufferingFeignClientResponse = new BufferingFeignClientResponse(super.execute(request, options));
       } catch (Exception e) {
           log.error(e.getMessage(), e);
           errorMsg = e.getMessage();
           throw e;
       } finally {
           stopWatch.stop();

           // request
           Map reqMap = null;
           byte[] body = request.body();
           Charset charset = Objects.isNull(request.charset()) ? Charset.defaultCharset() : request.charset();
           HttpHeaders httpHeaders = convert(request.headers());
           String reqStr = Strings.EMPTY;
           MediaType reqMediaType;
           if (Objects.nonNull(reqMediaType = httpHeaders.getContentType())) {
               if (reqMediaType.includes(MediaType.MULTIPART_FORM_DATA)) {
                   body = new byte[]{0};
               }
               if (Objects.nonNull(body)) {
                   reqStr = new String(body, charset);
               }
               if ((reqMediaType.includes(MediaType.APPLICATION_JSON_UTF8) || reqMediaType.includes(MediaType.APPLICATION_JSON))) {
                   //json format paramters
                   try {
                       reqMap = JSON.parseObject(reqStr);
                       reqStr = null;
                       //no care this exception
                   } catch (JSONException e) {
                   }
               }
           }

           //response
           Map respMap = null;
           String respStr = null;
           int resStatus;
           Collection<String> collection;
           if (Objects.nonNull(bufferingFeignClientResponse)) {
               if (Objects.nonNull(bufferingFeignClientResponse.getHeaders()) && !CollectionUtils.isEmpty(collection =
                       bufferingFeignClientResponse.getHeaders().get(CONTENT_TYPE))) {

                   StringBuilder resBody = new StringBuilder();
                   try (BufferedReader bufferedReader =
                                new BufferedReader(new InputStreamReader(bufferingFeignClientResponse.getBody(),
                                        charset))) {
                       String line = bufferedReader.readLine();
                       while (line != null) {
                           resBody.append(line);
                           line = bufferedReader.readLine();
                       }
                   }
                   if (!collection.contains(MediaType.MULTIPART_FORM_DATA_VALUE)) {
                       respStr = resBody.toString();
                   }
                   if (collection.contains(MediaType.APPLICATION_JSON_VALUE) || collection.contains(MediaType.APPLICATION_JSON)) {
                       try {
                           respMap = JSON.parseObject(reqStr);
                           respStr = null;
                           //no care this exception
                       } catch (JSONException e) {
                       }
                   }
               }
               resStatus = bufferingFeignClientResponse.getRawStatusCode();
           } else {
               resStatus = HttpStatus.INTERNAL_SERVER_ERROR.value();
               respStr = errorMsg;
           }

           RestLog.builder().costTime(stopWatch.getLastTaskTimeMillis()).headers(httpHeaders)
                   .method(request.method()).reqBody(reqStr).resJson(respMap).reqJson(reqMap).reqUrl(request.url())
                   .resBody(respStr).resStatus(resStatus).build().print();
       }

       Response response = bufferingFeignClientResponse.getResponse().toBuilder()
               .body(bufferingFeignClientResponse.getBody(),
                       bufferingFeignClientResponse.getResponse().body().length()).build();
       bufferingFeignClientResponse.close();

       return response;
   }


   private HttpHeaders convert(Map<String, Collection<String>> headers) {

       HttpHeaders httpHeaders = new HttpHeaders();
       if (Objects.nonNull(headers)) {
           headers.forEach((k, v) -> {
               httpHeaders.set(k, convert(v));
           });
       }
       return httpHeaders;
   }

   private String convert(Collection<String> strings) {

       StringBuilder builder = new StringBuilder();

       strings.forEach(s -> {
           builder.append(s).append(",");
       });

       //去除末尾逗号
       if (builder.length() > 0) {
           builder.delete(builder.length() - 1, builder.length());
       }
       return builder.toString();
   }

final class BufferingFeignClientResponse implements Closeable {

   private Response response;

   @Nullable
   private byte[] body;

   public BufferingFeignClientResponse(Response response) {
       this.response = response;
   }

   public HttpStatus getStatusCode() {
       return HttpStatus.valueOf(this.response.status());
   }

   public Response getResponse() {
       return this.response;
   }

   public int getRawStatusCode() {
       return this.response.status();
   }


   public String getStatusText() {
       return HttpStatus.valueOf(this.response.status()).name();
   }


   public Map<String, Collection<String>> getHeaders() {
       return this.response.headers();
   }

   public InputStream getBody() throws IOException {
       if (this.body == null) {
           this.body = StreamUtils.copyToByteArray(this.response.body().asInputStream());
       }
       return new ByteArrayInputStream(this.body);
   }

   @Override
   public void close() {
       this.response.close();
   }

}
记录一下遇到的问题,以及解决的办法。
  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值