前言

  • 上一章节我们知道SpringCloudFeign基于接口来实现调用,那我们要知道它是怎么实现的,我们可以通过断点来一步步跟进,因为接口是不能具体执行任务,所以我们可以猜测是采用动态代理来实现的,应该和MybatisMapper接口的工作原理差不多

解析

  • 我们先在AuthenticationFilter的下面这行打好断点
1
2
// 调用sso服务鉴权
resModel = ssoClient.checkToken(new TokenMO(token));
  • 然后进入方法体feign.ReflectiveFeign.FeignInvocationHandler#invoke(),果不其然,看见FeignInvocationHandler这个类实现了InvocationHandler接口就可以知道是使用了JDK的动态代理
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
static class FeignInvocationHandler implements InvocationHandler {

private final Target target;
private final Map<Method, MethodHandler> dispatch;

FeignInvocationHandler(Target target, Map<Method, MethodHandler> dispatch) {
this.target = checkNotNull(target, "target");
this.dispatch = checkNotNull(dispatch, "dispatch for %s", target);
}

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 执行equals()方法
if ("equals".equals(method.getName())) {
try {
Object
otherHandler =
args.length > 0 && args[0] != null ? Proxy.getInvocationHandler(args[0]) : null;
return equals(otherHandler);
} catch (IllegalArgumentException e) {
return false;
}
} else if ("hashCode".equals(method.getName())) {
// 执行hashCode()方法
return hashCode();
} else if ("toString".equals(method.getName())) {
// 执行toString()方法
return toString();
}
return dispatch.get(method).invoke(args);
}

@Override
public boolean equals(Object obj) {
if (obj instanceof FeignInvocationHandler) {
FeignInvocationHandler other = (FeignInvocationHandler) obj;
return target.equals(other.target);
}
return false;
}

@Override
public int hashCode() {
return target.hashCode();
}

@Override
public String toString() {
return target.toString();
}
}
  • 可以看到实际上调用了dispatch.get(method).invoke(args);方法,dispatch是个Map里面存的两个MethodHandler就是对应我们上一章节定义的SsoClient的两个方法
1
private final Map<Method, MethodHandler> dispatch;
  • 继续跳入,我们来到feign.SynchronousMethodHandler#invoke()方法,可以看到根据方法参数构造了一个RequestTemplate对象
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@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) {
retryer.continueOrPropagate(e);
if (logLevel != Logger.Level.NONE) {
logger.logRetry(metadata.configKey(), logLevel);
}
continue;
}
}
}
  • 继续跳入executeAndDecode(template);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
Object executeAndDecode(RequestTemplate template) throws Throwable {
// 请求Request对象
Request request = targetRequest(template);

if (logLevel != Logger.Level.NONE) {
logger.logRequest(metadata.configKey(), logLevel, request);
}
// 结果Response对象
Response response;
long start = System.nanoTime();
try {
// 执行请求
response = client.execute(request, options);
// ensure the request is set. TODO: remove in Feign 10
response.toBuilder().request(request).build();
} catch (IOException e) {
if (logLevel != Logger.Level.NONE) {
logger.logIOException(metadata.configKey(), logLevel, e, elapsedTime(start));
}
throw errorExecuting(request, e);
}
long elapsedTime = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start);

boolean shouldClose = true;
try {
if (logLevel != Logger.Level.NONE) {
response =
logger.logAndRebufferResponse(metadata.configKey(), logLevel, response, elapsedTime);
// ensure the request is set. TODO: remove in Feign 10
response.toBuilder().request(request).build();
}
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;
}
// Ensure the response body is disconnected
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()) {
return decode(response);
} else {
throw errorDecoder.decode(metadata.configKey(), response);
}
} catch (IOException e) {
if (logLevel != Logger.Level.NONE) {
logger.logIOException(metadata.configKey(), logLevel, e, elapsedTime);
}
throw errorReading(request, response, e);
} finally {
if (shouldClose) {
ensureClosed(response.body());
}
}
}
  • 我们关注response = client.execute(request, options);方法,这里client我们由上一张图可以看到是LoadBalancerFeignClient,这里的设计和之前的RibbonIClient类似

  • 这个方法位于org.springframework.cloud.netflix.feign.ribbon.LoadBalancerFeignClient,见方法名可以知道是要实现负载均衡

  • 下面的方法我们可以知道主体逻辑是调用com.netflix.client.AbstractLoadBalancerAwareClient#executeWithLoadBalancer(S, com.netflix.client.config.IClientConfig),这个类及方法是不是有点熟悉,没错和我们之前分析Ribbon实现负载均衡的执行的方法是一样的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@Override   
public Response execute(Request request, Request.Options options) throws IOException {
try {
URI asUri = URI.create(request.url());
String clientName = asUri.getHost();
URI uriWithoutHost = cleanUrl(request.url(), clientName);
FeignLoadBalancer.RibbonRequest ribbonRequest = new FeignLoadBalancer.RibbonRequest(
this.delegate, request, uriWithoutHost);

IClientConfig requestConfig = getClientConfig(options, clientName);
// 开始负载均衡处理了
return lbClient(clientName).executeWithLoadBalancer(ribbonRequest,
requestConfig).toResponse();
}
catch (ClientException e) {
IOException io = findIOException(e);
if (io != null) {
throw io;
}
throw new RuntimeException(e);
}
}
  • 我们这里关注lbClient(clientName)方法,这个方法是构造FeignLoadBalancer
1
2
3
private FeignLoadBalancer lbClient(String clientName) {
return this.lbClientFactory.create(clientName);
}
  • FeignLoadBalancer

    • 类成员变量及主要方法

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      public class FeignLoadBalancer extends
      AbstractLoadBalancerAwareClient<FeignLoadBalancer.RibbonRequest, FeignLoadBalancer.RibbonResponse> {

      protected int connectTimeout;
      protected int readTimeout;
      protected IClientConfig clientConfig;
      protected ServerIntrospector serverIntrospector;

      public FeignLoadBalancer(ILoadBalancer lb, IClientConfig clientConfig,
      ServerIntrospector serverIntrospector) {
      super(lb, clientConfig);
      this.setRetryHandler(RetryHandler.DEFAULT);
      this.clientConfig = clientConfig;
      this.connectTimeout = clientConfig.get(CommonClientConfigKey.ConnectTimeout);
      this.readTimeout = clientConfig.get(CommonClientConfigKey.ReadTimeout);
      this.serverIntrospector = serverIntrospector;
      }

      @Override
      public RibbonResponse execute(RibbonRequest request, IClientConfig configOverride)
      throws IOException {
      Request.Options options;
      if (configOverride != null) {
      options = new Request.Options(
      configOverride.get(CommonClientConfigKey.ConnectTimeout,
      this.connectTimeout),
      (configOverride.get(CommonClientConfigKey.ReadTimeout,
      this.readTimeout)));
      }
      else {
      options = new Request.Options(this.connectTimeout, this.readTimeout);
      }
      // 执行请求
      Response response = request.client().execute(request.toRequest(), options);
      return new RibbonResponse(request.getUri(), response);
      }

      ...
    • 类继承关系图

  • 由上图我们看到FeignLoadBalancer继承了RibbonAbstractLoadBalancerAwareClient, 这个类是Ribbon执行请求客户端

  • 我们现在来看是怎么构建FeignLoadBalancer

1
2
3
4
5
6
7
8
9
10
11
12
13
public FeignLoadBalancer create(String clientName) {
// 这里做了缓存处理
if (this.cache.containsKey(clientName)) {
return this.cache.get(clientName);
}
IClientConfig config = this.factory.getClientConfig(clientName);
ILoadBalancer lb = this.factory.getLoadBalancer(clientName);
ServerIntrospector serverIntrospector = this.factory.getInstance(clientName, ServerIntrospector.class);
FeignLoadBalancer client = enableRetry ? new RetryableFeignLoadBalancer(lb, config, serverIntrospector,
loadBalancedRetryPolicyFactory, loadBalancedBackOffPolicyFactory, loadBalancedRetryListenerFactory) : new FeignLoadBalancer(lb, config, serverIntrospector);
this.cache.put(clientName, client);
return client;
}
  • 由上图我们可以看到之前设置的连接超时时间及读取超时时间及重试参数,同时还构造了RibbonIClientConfig客户端配置ILoadBalancer负载均衡器对象,I字母开头
  • 继续跳入executeWithLoadBalancer方法,进入到com.netflix.client.AbstractLoadBalancerAwareClient类中,到这里Feign就把负载均衡及重试工作给Ribbon
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public T executeWithLoadBalancer(final S request, final IClientConfig requestConfig) throws ClientException {
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);
}
}

}
  • 上面的return Observable.just(AbstractLoadBalancerAwareClient.this.execute(requestForServer, requestConfig));会调用我们的FeignLoadBalancerexecute()方法,这里完成请求处理
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Override
public RibbonResponse execute(RibbonRequest request, IClientConfig configOverride)
throws IOException {
Request.Options options;
if (configOverride != null) {
options = new Request.Options(
configOverride.get(CommonClientConfigKey.ConnectTimeout,
this.connectTimeout),
(configOverride.get(CommonClientConfigKey.ReadTimeout,
this.readTimeout)));
}
else {
options = new Request.Options(this.connectTimeout, this.readTimeout);
}
// 执行请求
Response response = request.client().execute(request.toRequest(), options);
return new RibbonResponse(request.getUri(), response);
}
  • 进入feign.Client.Default#execute()方法,可以看到使用了java.net.HttpURLConnection发送请求
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
@Override
public Response execute(Request request, Options options) throws IOException {
// 包装参数及发送请求
HttpURLConnection connection = convertAndSend(request, options);
return convertResponse(connection).toBuilder().request(request).build();
}

HttpURLConnection convertAndSend(Request request, Options options) throws IOException {
final HttpURLConnection
connection =
(HttpURLConnection) new URL(request.url()).openConnection();
if (connection instanceof HttpsURLConnection) {
HttpsURLConnection sslCon = (HttpsURLConnection) connection;
if (sslContextFactory != null) {
sslCon.setSSLSocketFactory(sslContextFactory);
}
if (hostnameVerifier != null) {
sslCon.setHostnameVerifier(hostnameVerifier);
}
}
connection.setConnectTimeout(options.connectTimeoutMillis());
connection.setReadTimeout(options.readTimeoutMillis());
connection.setAllowUserInteraction(false);
connection.setInstanceFollowRedirects(true);
connection.setRequestMethod(request.method());

Collection<String> contentEncodingValues = request.headers().get(CONTENT_ENCODING);
boolean
gzipEncodedRequest =
contentEncodingValues != null && contentEncodingValues.contains(ENCODING_GZIP);
boolean
deflateEncodedRequest =
contentEncodingValues != null && contentEncodingValues.contains(ENCODING_DEFLATE);

boolean hasAcceptHeader = false;
Integer contentLength = null;
for (String field : request.headers().keySet()) {
if (field.equalsIgnoreCase("Accept")) {
hasAcceptHeader = true;
}
for (String value : request.headers().get(field)) {
if (field.equals(CONTENT_LENGTH)) {
if (!gzipEncodedRequest && !deflateEncodedRequest) {
contentLength = Integer.valueOf(value);
connection.addRequestProperty(field, value);
}
} else {
connection.addRequestProperty(field, value);
}
}
}
// Some servers choke on the default accept string.
if (!hasAcceptHeader) {
connection.addRequestProperty("Accept", "*/*");
}

if (request.body() != null) {
if (contentLength != null) {
connection.setFixedLengthStreamingMode(contentLength);
} else {
connection.setChunkedStreamingMode(8196);
}
connection.setDoOutput(true);
OutputStream out = connection.getOutputStream();
if (gzipEncodedRequest) {
out = new GZIPOutputStream(out);
} else if (deflateEncodedRequest) {
out = new DeflaterOutputStream(out);
}
try {
out.write(request.body());
} finally {
try {
out.close();
} catch (IOException suppressed) { // NOPMD
}
}
}
return connection;
}
  • 由上面convertAndSend(request, options)方法只看到了组装参数,但是发现并没有执行发送请求,而且responseCode = -1,那么是在哪里调用的,查看网上资料发现HttpURLConnection的使用有点特殊

    • 关于要不要显示调用connect()方法的问题:
      • 1、不需要显示调用connect方法
      • 2、必须调用getResponseCode()方法
        • 试了一下在不调用getResponseCode()方法的时候,无论是否调用connect()方法,请求都是不能成功的,调用connect()方法只是建立连接,并不会向服务器传递数据
        • 只有调用getRespconseCode()方法时,才会向服务器传递数据(有博文说是getInputStream()才会向服务器传递数据,getResponseCode中会调用getInputStream方法)。跟着getResponseCode()源码发现里面调用了getInputStream()方法,在getInputStream()方法中会判断当前是否连接,如果没有连接,则调用connect()方法建立连接。
          1
          2
          3
          4
          5
          6
          7
          8
          9
          10
          11
          12
          13
          14
          15
          16
          17
          18
          19
          20
          21
          22
          23
          24
          25
          26
          27
          28
          29
          30
          31
          32
          33
          34
          35
          36
          37
          38
          39
          40
          41
          42
          43
          44
          45
          46
          47
          48
          49
          50
          51
          52
          53
          54
          55
          56
          57
          58
          59
          60
          61
          62
          63
          64
          65
          66
          67
          public int getResponseCode() throws IOException {
          /*
          * We're got the response code already
          */
          if (responseCode != -1) {
          return responseCode;
          }

          /*
          * Ensure that we have connected to the server. Record
          * exception as we need to re-throw it if there isn't
          * a status line.
          */
          Exception exc = null;
          try {
          // 这里面会建立连接并发送请求
          getInputStream();
          } catch (Exception e) {
          exc = e;
          }

          /*
          * If we can't a status-line then re-throw any exception
          * that getInputStream threw.
          */
          String statusLine = getHeaderField(0);
          if (statusLine == null) {
          if (exc != null) {
          if (exc instanceof RuntimeException)
          throw (RuntimeException)exc;
          else
          throw (IOException)exc;
          }
          return -1;
          }

          /*
          * Examine the status-line - should be formatted as per
          * section 6.1 of RFC 2616 :-
          *
          * Status-Line = HTTP-Version SP Status-Code SP Reason-Phrase
          *
          * If status line can't be parsed return -1.
          */
          if (statusLine.startsWith("HTTP/1.")) {
          int codePos = statusLine.indexOf(' ');
          if (codePos > 0) {

          int phrasePos = statusLine.indexOf(' ', codePos+1);
          if (phrasePos > 0 && phrasePos < statusLine.length()) {
          responseMessage = statusLine.substring(phrasePos+1);
          }

          // deviation from RFC 2616 - don't reject status line
          // if SP Reason-Phrase is not included.
          if (phrasePos < 0)
          phrasePos = statusLine.length();

          try {
          responseCode = Integer.parseInt
          (statusLine.substring(codePos+1, phrasePos));
          return responseCode;
          } catch (NumberFormatException e) { }
          }
          }
          return -1;
          }
  • 执行完成convertAndSend(request, options)方法之后进入convertResponse(connection)方法,可以看到这里调用了connection.getResponseCode();方法,执行完成之后就得到了200的返回码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
Response convertResponse(HttpURLConnection connection) throws IOException {
// 这里会触发请求
int status = connection.getResponseCode();
String reason = connection.getResponseMessage();

if (status < 0) {
throw new IOException(format("Invalid status(%s) executing %s %s", status,
connection.getRequestMethod(), connection.getURL()));
}

Map<String, Collection<String>> headers = new LinkedHashMap<String, Collection<String>>();
for (Map.Entry<String, List<String>> field : connection.getHeaderFields().entrySet()) {
// response message
if (field.getKey() != null) {
headers.put(field.getKey(), field.getValue());
}
}

Integer length = connection.getContentLength();
if (length == -1) {
length = null;
}
InputStream stream;
if (status >= 400) {
stream = connection.getErrorStream();
} else {
stream = connection.getInputStream();
}
return Response.builder()
.status(status)
.reason(reason)
.headers(headers)
.body(stream, length)
.build();
}
  • 执行完请求之后就是根据 statusreasonheadersInputStream请求构造了Response对象,这里使用了建造者设计模式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
public Builder toBuilder(){
return new Builder(this);
}

public static Builder builder(){
return new Builder();
}

public static final class Builder {
int status;
String reason;
Map<String, Collection<String>> headers;
Body body;
Request request;

Builder() {
}

Builder(Response source) {
this.status = source.status;
this.reason = source.reason;
this.headers = source.headers;
this.body = source.body;
this.request = source.request;
}

/** @see Response#status*/
public Builder status(int status) {
this.status = status;
return this;
}

/** @see Response#reason */
public Builder reason(String reason) {
this.reason = reason;
return this;
}

/** @see Response#headers */
public Builder headers(Map<String, Collection<String>> headers) {
this.headers = headers;
return this;
}

/** @see Response#body */
public Builder body(Body body) {
this.body = body;
return this;
}

/** @see Response#body */
public Builder body(InputStream inputStream, Integer length) {
this.body = InputStreamBody.orNull(inputStream, length);
return this;
}

/** @see Response#body */
public Builder body(byte[] data) {
this.body = ByteArrayBody.orNull(data);
return this;
}

/** @see Response#body */
public Builder body(String text, Charset charset) {
this.body = ByteArrayBody.orNull(text, charset);
return this;
}

/** @see Response#request
*
* NOTE: will add null check in version 10 which may require changes
* to custom feign.Client or loggers
*/
public Builder request(Request request) {
this.request = request;
return this;
}

public Response build() {
return new Response(this);
}
}
  • 现在我们得到了Response对象,现在回到feign.SynchronousMethodHandler#executeAndDecode()方法,得到结果之后就是要将Http的响应流转化成实体类对象了
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
Object executeAndDecode(RequestTemplate template) throws Throwable {
// 请求Request对象
Request request = targetRequest(template);

if (logLevel != Logger.Level.NONE) {
logger.logRequest(metadata.configKey(), logLevel, request);
}
// 结果Response对象
Response response;
long start = System.nanoTime();
try {
// 执行请求
response = client.execute(request, options);
// ensure the request is set. TODO: remove in Feign 10
response.toBuilder().request(request).build();
} catch (IOException e) {
if (logLevel != Logger.Level.NONE) {
logger.logIOException(metadata.configKey(), logLevel, e, elapsedTime(start));
}
throw errorExecuting(request, e);
}
long elapsedTime = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start);

boolean shouldClose = true;
try {
if (logLevel != Logger.Level.NONE) {
response =
logger.logAndRebufferResponse(metadata.configKey(), logLevel, response, elapsedTime);
// ensure the request is set. TODO: remove in Feign 10
response.toBuilder().request(request).build();
}
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;
}
// Ensure the response body is disconnected
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()) {
return decode(response);
} else {
throw errorDecoder.decode(metadata.configKey(), response);
}
} catch (IOException e) {
if (logLevel != Logger.Level.NONE) {
logger.logIOException(metadata.configKey(), logLevel, e, elapsedTime);
}
throw errorReading(request, response, e);
} finally {
if (shouldClose) {
ensureClosed(response.body());
}
}
}
  • 关注下面这块代码可以看到是对返回码200-300进行了处理
1
2
3
4
5
6
7
8
if (response.status() >= 200 && response.status() < 300) {
if (void.class == metadata.returnType()) {
return null;
} else {
// 解析结果
return decode(response);
}
}
  • 继续跳入 decode(response)方法直到进入org.springframework.cloud.netflix.feign.support.SpringDecoder#decode(final Response response, Type type)方法,可以看到和Spring MVC解析输入参数及输出结果一样使用了HttpMessageConverter进行转化
1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Override
public Object decode(final Response response, Type type)
throws IOException, FeignException {
if (type instanceof Class || type instanceof ParameterizedType
|| type instanceof WildcardType) {
@SuppressWarnings({ "unchecked", "rawtypes" })
HttpMessageConverterExtractor<?> extractor = new HttpMessageConverterExtractor(
type, this.messageConverters.getObject().getConverters());

return extractor.extractData(new FeignResponseAdapter(response));
}
throw new DecodeException(
"type is not an instance of Class or ParameterizedType: " + type);
}
  • 到这里Feign请求就执行完成了

总结

  • SpringCloudFeign采用JDK动态代理基于接口来实现调用,SpringCloudFeign主要做的就是解析@FeignClient注解及其方法定义信息,一个方法封装成一个MethodHandler,由此对象来完成方法执行逻辑 ,同时集成了Ribbon来实现负载均衡处理,专人做专事,具体发送请求是由自身FeignLoadBalancerexecute()方法来执行的,这里涉及到请求参数及响应参数的解析