认识feign(二)请求流程解析

947 阅读7分钟

摘要

大家好,我是新手小编小八(上一篇说错了),上一篇我简要的分享了我学习feign源码了解到的启动内容,这一篇我将分享一下feign发起请求和响应处理的流程的成果!

代理类处理

上篇讲到最后feign客户端的实质最后就是代理对象(不清楚的可以回顾认识feign(一)启动流程),根据是否启用hystrix,他的处理器为可分为FeignInvocationHandler(未启用)和HystrixInvocationHandler(启用);
主要区别:未启用hystrix直接进行请求,而启用了hystrix则是将其请求方法委托到hystrixCommand对象中进行请求处理,我主要讲启动了hystrix的情况

hystrix封装

HystrixInvocationHandler的invoke方法为代理处理方法,前面的处理逻辑和FeignInvocattionHandler的一摸一样,就是简单处理基于object的原生方法,主要的逻辑从下面开始

 // hystrixCommand对象里面进行了hystrix处理和配置,如熔断、隔离等
 // 调用自定义业务方法run()
 // 调用出现各种异常则调用自定义异常方法getFallback
 HystrixCommand<Object> hystrixCommand = new HystrixCommand<Object>(setterMethodMap.get(method)) {
  @Override
  protected Object run() throws Exception {
    try {
      // 从方法集合中获取相应的方法处理器SynchronousMethodHandler开始请求处理
      return HystrixInvocationHandler.this.dispatch.get(method).invoke(args);
    } catch (Exception e) {
      throw e;
    } catch (Throwable t) {
      throw (Error) t;
    }
  }

  @Override
  protected Object getFallback() {
    //
    if (fallbackFactory == null) {
      return super.getFallback();
    }
    try {
      // 获取熔断对象(可自定义FallbackFactory, 一般我们都直接用一个fallback,它默认熔断所有异常,除了HystrixBadRequestException)
      Object fallback = fallbackFactory.create(getExecutionException());
      // 获取熔断返回信息
      Object result = fallbackMethodMap.get(method).invoke(fallback, args);
      // 根据返回值类型进行不同的操作
      if (isReturnsHystrixCommand(method)) {
        return ((HystrixCommand) result).execute();
      } else if (isReturnsObservable(method)) {
        // Create a cold Observable
        return ((Observable) result).toBlocking().first();
      } else if (isReturnsSingle(method)) {
        // Create a cold Observable as a Single
        return ((Single) result).toObservable().toBlocking().first();
      } else if (isReturnsCompletable(method)) {
        ((Completable) result).await();
        return null;
      } else {
        // 直接返回信息
        return result;
      }
    } catch (IllegalAccessException e) {
      // shouldn't happen as method is public due to being an interface
      throw new AssertionError(e);
    } catch (InvocationTargetException e) {
      // Exceptions on fallback are tossed by Hystrix
      throw new AssertionError(e.getCause());
    }
  }
};   

这里hystrixCommand里面有很多知识点,主要是通过RxJava技术实现,大家如果要想去看懂里面的处理逻辑先得学习下RxJava,不然看起来比较困难
我这边就不细讲了,大概的逻辑为(现将讲一下,这里面的基本对象都是RxJava的观察者对象):

  1. 它先去判断是否有缓存信息,有缓存直接拿缓存的;如果没有,则进行请求处理,然后将其放在缓存中
  2. 进行请求的化先隔离处理,主要隔离策略有信号量隔离和线程隔离(默认),先进行信号量隔离的判断,如果是线程隔离默认通过,线程隔离主要通过RxJava的subscribeOn方法实现
  3. 同时hystrix也会判断当前是否开启过期熔断(默认开启),如果开启通过lift方法进行处理

请求参数解析

进到SynchronousMethodHandler对象中,第一步要处理的就是将参数信息拼接到请求信息中,最后组装成request对象供client进行请求,我们从buildTemplateFromArgs.create(argv) 进入,解析信息后可获得一个请求模板对象

// 从请求模板中复制请求信息
RequestTemplate mutable = new RequestTemplate(metadata.template());
  // 根据参数对url进行拼接,没怎么用过
  if (metadata.urlIndex() != null) {
    int urlIndex = metadata.urlIndex();
    checkArgument(argv[urlIndex] != null, "URI parameter %s was null", urlIndex);
    mutable.insert(0, String.valueOf(argv[urlIndex]));
  }
  // 根据参数名(@RequestParam的value值),对参数的信息进行分组
  Map<String, Object> varBuilder = new LinkedHashMap<String, Object>();
  for (Entry<Integer, Collection<String>> entry : metadata.indexToName().entrySet()) {
    int i = entry.getKey();
    Object value = argv[entry.getKey()];
    if (value != null) { // Null values are skipped.
      if (indexToExpander.containsKey(i)) {
        value = expandElements(indexToExpander.get(i), value);
      }
      for (String name : entry.getValue()) {
        varBuilder.put(name, value);
      }
    }
  }
  // 1.对复合型对象参数用EnCoder编码器解析成为请求的body体(如果复合型对象用了@RequestParam进行装饰,则不进行解析)
  // 2.对上面的简单参数对象进行请求拼接
  RequestTemplate template = resolve(argv, mutable, varBuilder);
  // 对用了@RequestParam进行装饰的复合型对象进行地址拼接处理
  if (metadata.queryMapIndex() != null) {
    template = addQueryMapQueryParameters((Map<String, Object>) argv[metadata.queryMapIndex()], template);
  }
  // 进行@ReqyestHeader参数的进行header的添加
  if (metadata.headerMapIndex() != null) {
    template = addHeaderMapHeaders((Map<String, Object>) argv[metadata.headerMapIndex()], template);
  }
  return template;
}

关于编码器如何解析复合型对象,在resolve(argv, mutable, varBuilder)进行复合对象的解析,除了一开始的校验代码,定位到 encoder.encode(body, metadata.bodyType(), mutable)这一行,请求的实现类为 SpringEncoder, 主要是匹配相应的HttpMessageConverters转换器(跟mvc共用)进行对象解析,一般我们都匹配到jackson或者fastjson的转换器

请求处理

获得请求模板后,先是用克隆的方式生成一个Retryer重试器对象(默认不重试,可自定义),在循环体中进行请求和响应解析,报错了调用重试器进行重试处理,(不重试的处理策略为直接报错),不报错的会继续进行重试。接下来进入请求方法executeAndDecode
第一步:获取请求对象,targetRequest(template)

Request targetRequest(RequestTemplate template) {
    // 拦截器链式调用,对请求信息进行最后处理
    for (RequestInterceptor interceptor : requestInterceptors) {
      // RequestInterceptor接口只有apply一个方法
      interceptor.apply(template);
    }
    // 获取请求对象,target的实现类为HardCodedTarget,对请求地址进行最后处理
    return target.apply(new RequestTemplate(template));
 }

第二步:调用client的execute方法进行请求,委托给ribbon或者client对象处理(如果用了服务名,需要委托给ribbon,反之用了url真实地址,直接委托给client.Default)

public Response execute(Request request, Request.Options options) throws IOException {
    try {
        // 获取请求地址
        URI asUri = URI.create(request.url());
        // 获取服务名称
        String clientName = asUri.getHost();
        // 清除服务名称 ,例如:http://user-service/test -> http:///test
        URI uriWithoutHost = cleanUrl(request.url(), clientName);
        // 获取ribbon请求对象
        FeignLoadBalancer.RibbonRequest ribbonRequest = new FeignLoadBalancer.RibbonRequest(
                        this.delegate, request, uriWithoutHost);
        // 获取ribbon客户端配置对象
        IClientConfig requestConfig = getClientConfig(options, clientName);
        return
                // 获取ribbon客户端
                lbClient(clientName)
                // 委托给ribbon进行请求处理
                .executeWithLoadBalancer(ribbonRequest, requestConfig)
                // 返回响应
                .toResponse();
    }
    catch (ClientException e) {
        IOException io = findIOException(e);
        if (io != null) {
                throw io;
        }
        throw new RuntimeException(e);
    }
}

第三步:ribbon调用executeWithLoadBalancer进行请求处理

 // 构建ribbon执行器(主要是进行一些配置,如ribbon的重试机制,有兴趣可以去学习ribbon) 
LoadBalancerCommand<T> command = buildLoadBalancerCommand(request, requestConfig);
try {
    return command.submit(
        new ServerOperation<T>() {
            @Override
            public Observable<T> call(Server server) {
                // 获取本次请求的服务地址
                URI finalUri = reconstructURIWithServer(server, request.getUri());
                // 将服务地址插入到之前的地址中去
                S requestForServer = (S) request.replaceUri(finalUri);
                try {
                    // 调用请求
                    return Observable.just(AbstractLoadBalancerAwareClient.this.execute(requestForServer, requestConfig));
                } 
                catch (Exception e) {
                    return Observable.error(e);
                }
            }
        })
        .toBlocking()
        .single();
} catch (Exception e) {
    Throwable t = e.getCause();
    if (t instanceof ClientException) {
        throw (ClientException) t;
    } else {
        throw new ClientException(e);
    }
}

第四步:ribbon调用execute进行请求

public RibbonResponse execute(RibbonRequest request, IClientConfig configOverride)
                        throws IOException {
    Request.Options options;
    // 设置ribbon请求参数设置,连接超时时间和读取超时时间
    if (configOverride != null) {
            RibbonProperties override = RibbonProperties.from(configOverride);
            options = new Request.Options(
                            override.connectTimeout(this.connectTimeout),
                            override.readTimeout(this.readTimeout));
    }
    else {
            options = new Request.Options(this.connectTimeout, this.readTimeout);
    }
    // 这里调用了client对象进行请求,这里的client才是真正的client.Default对象
    Response response = request.client().execute(request.toRequest(), options);
    return new RibbonResponse(request.getUri(), response);
}

第五步(用真实直接跳到这一步):client的execute方法请求处理

public Response execute(Request request, Options options) throws IOException {
  // 将请求对象是信息转换成HttpUrlConnection对象所需要的信息,并进行请求 (不多讲解,就是一些处理赋值,有兴趣可以看看)
  HttpURLConnection connection = convertAndSend(request, options);
  // 获取响应信息,并封装
  return convertResponse(connection).toBuilder().request(request).build();
}

到此就算已经成功向目标地址发出了请求,并收到了响应信息

响应处理

拿到响应信息后,我们回到请求方法executeAndDecode,直接定位到请求后的处理流程

 try {
  if (logLevel != Logger.Level.NONE) {
    // 打印响应信息
  }
  // 判断响应对象是否为Response
  if (Response.class == metadata.returnType()) {
    if (response.body() == null) {
      return response;
    }
    // 无需解码或者无法解码直接返回
    if (response.body().length() == null ||
            response.body().length() > MAX_RESPONSE_BUFFER_SIZE) {
      shouldClose = false;
      return response;
    }
    // 字节数据返回
    byte[] bodyData = Util.toByteArray(response.body().asInputStream());
    return response.toBuilder().body(bodyData).build();
  }
  // 判断响应状态是否为正确响应
  if (response.status() >= 200 && response.status() < 300) {
    if (void.class == metadata.returnType()) {
      return null;
    } else {
      // 成功响应返回正确数据,则用解码器进行数据解码(常用)
      return decode(response);
    }
  } else if (decode404 && response.status() == 404 && void.class != metadata.returnType()) {
     // 如果响应状态为404,但是设置为需要解码,正常用解码器进行数据解码
    return decode(response);
  } else {
    // 进行错误信息的解码,默认为报错(常用)
    throw errorDecoder.decode(metadata.configKey(), response);
  }
} catch (IOException e) {
  // 日志输出
  throw errorReading(request, response, e);
} finally {
  if (shouldClose) {
    ensureClosed(response.body());
  }
}

Decoder解码器的最终调用类为SpringDncoder,解码逻辑就是跟编码差不多,也是匹配相应的HttpMessageConverters转换器对响应数据里面的body进行解析,但是这里不是直接用HttpMessageConverter进行解析,而是靠一个新的对象HttpMessageConverterExtractor,这个对象专门是用于从response转换出具体的对象,这里不做太多说明,有兴趣可以去了解下

结束词

小编的第二篇原理分享到这里就结束了,这次讲的主要还是请求处理流程,很多细节的处理没怎么讲,有问题的可以评论区跟我互动,共同进步哦!希望能帮到大家!!!