源码篇--SpringCloud-OpenFeign--http请求的发出


前言

我们已经知道了feign 接口是通过FeignClientFactoryBean 来生成代理类的,那么一个真正的http 请求是如果进行构建的呢?


一、feign http 请求的构建:

1.1 RequestTemplate 模版构建:

我们知道 代理类的最终生成 是在Feign 中的build() 方法:

public <T> T target(Target<T> target) {
	return this.build().newInstance(target);
}

public Feign build() {
    super.enrich();
    SynchronousMethodHandler.Factory synchronousMethodHandlerFactory = new SynchronousMethodHandler.Factory(this.client, this.retryer, this.requestInterceptors, this.responseInterceptor, this.logger, this.logLevel, this.dismiss404, this.closeAfterDecode, this.propagationPolicy, this.forceDecoding);
    ReflectiveFeign.ParseHandlersByName handlersByName = new ReflectiveFeign.ParseHandlersByName(this.contract, this.options, this.encoder, this.decoder, this.queryMapEncoder, this.errorDecoder, synchronousMethodHandlerFactory);
    return new ReflectiveFeign(handlersByName, this.invocationHandlerFactory, this.queryMapEncoder);
}

SynchronousMethodHandler 是对http 请求方法的处理,ReflectiveFeign 会调到其内部的 invoke 方法中,最终又调到 SynchronousMethodHandler 的 invoke 方法中

ReflectiveFeign invoke :

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
   if (!"equals".equals(method.getName())) {
        if ("hashCode".equals(method.getName())) {
            return this.hashCode();
        } else {
        
            return "toString".equals(method.getName()) ? this.toString() : ((InvocationHandlerFactory.MethodHandler)this.dispatch.get(method)).invoke(args);
        }
    } else {
        try {
            Object otherHandler = args.length > 0 && args[0] != null ? Proxy.getInvocationHandler(args[0]) : null;
            return this.equals(otherHandler);
        } catch (IllegalArgumentException var5) {
            return false;
        }
    }
}

方法本身不是 equals/hashCode/toString 则会进入到SynchronousMethodHandler 的 invoke 方法中;

public Object invoke(Object[] argv) throws Throwable {
	// 构建http request 请求的模版, buildTemplateFromArgs 中对要执行的方法的参数 进行解析 
    RequestTemplate template = this.buildTemplateFromArgs.create(argv);
    // request 的配置项获取,包括连接超时和读取超时的设置
     Request.Options options = this.findOptions(argv);
     // request  重试机制
     Retryer retryer = this.retryer.clone();

     while(true) {
         try {
         	// 构建请求,完成发送,和对返回数据进行解析
             return this.executeAndDecode(template, options);
         } catch (RetryableException var9) {
             RetryableException e = var9;

             try {
             	// 重试机制判断,达到一定次数 则直接抛出异常
                 retryer.continueOrPropagate(e);
             } catch (RetryableException var8) {
                 Throwable cause = var8.getCause();
                 if (this.propagationPolicy == ExceptionPropagationPolicy.UNWRAP && cause != null) {
                     throw cause;
                 }

                 throw var8;
             }

             if (this.logLevel != Level.NONE) {
                 this.logger.logRetry(this.metadata.configKey(), this.logLevel);
             }
         }
     }
 }

this.buildTemplateFromArgs.create(argv) 进入到ReflectiveFeign 对 RequestTemplate 模版的创建:

public RequestTemplate create(Object[] argv) {
	//  this.metadata.template() 对方法的原数据进行解析后 创建 一个新的 RequestTemplate  并返回
  	RequestTemplate mutable = RequestTemplate.from(this.metadata.template());
    mutable.feignTarget(this.target)	;
    if (this.metadata.urlIndex() != null) {
        int urlIndex = this.metadata.urlIndex();
        Util.checkArgument(argv[urlIndex] != null, "URI parameter %s was null", new Object[]{urlIndex});
        mutable.target(String.valueOf(argv[urlIndex]));
    }
	// 遍历对 参数进行解析 ,
    Map<String, Object> varBuilder = new LinkedHashMap();
    Iterator var4 = this.metadata.indexToName().entrySet().iterator();

    while(true) {
        Map.Entry entry;
        int i;
        Object value;
        do {
            if (!var4.hasNext()) {
                RequestTemplate template = this.resolve(argv, mutable, varBuilder);
                Object value;
                Map headerMap;
                if (this.metadata.queryMapIndex() != null) {
                    value = argv[this.metadata.queryMapIndex()];
                    headerMap = this.toQueryMap(value);
                    // 添加请求参数
                    template = this.addQueryMapQueryParameters(headerMap, template);
                }

                if (this.metadata.headerMapIndex() != null) {
                    value = argv[this.metadata.headerMapIndex()];
                    headerMap = this.toQueryMap(value);
                      // 添加请求header 头
                    template = this.addHeaderMapHeaders(headerMap, template);
                }

                return template;
            }

            entry = (Map.Entry)var4.next();
            i = (Integer)entry.getKey();
            value = argv[(Integer)entry.getKey()];
        } while(value == null);

        if (this.indexToExpander.containsKey(i)) {
            value = this.expandElements((Param.Expander)this.indexToExpander.get(i), value);
        }

        Iterator var8 = ((Collection)entry.getValue()).iterator();

        while(var8.hasNext()) {
            String name = (String)var8.next();
            varBuilder.put(name, value);
        }
    }
}

1.2 Request 请求构建:

executeAndDecode 方法中对其请求的构建:

Object executeAndDecode(RequestTemplate template, Request.Options options) throws Throwable {
   // 构建 request   http 请求
   Request request = this.targetRequest(template);
    if (this.logLevel != Level.NONE) {
    	// feign log 日志
        this.logger.logRequest(this.metadata.configKey(), this.logLevel, request);
    }

    long start = System.nanoTime();

    Response response;
    try {
    	// 发送http 请求 并获取到结果
        response = this.client.execute(request, options);
        response = response.toBuilder().request(request).requestTemplate(template).build();
    } catch (IOException var13) {
        if (this.logLevel != Level.NONE) {
            this.logger.logIOException(this.metadata.configKey(), this.logLevel, var13, this.elapsedTime(start));
        }

        throw FeignException.errorExecuting(request, var13);
    }

    long elapsedTime = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start);
    // 请求结果进行解析
    if (this.decoder != null) {
    
        return this.responseInterceptor.aroundDecode(new InvocationContext(this.decoder, this.metadata.returnType(), response));
    } else {
        CompletableFuture<Object> resultFuture = new CompletableFuture();
        this.asyncResponseHandler.handleResponse(resultFuture, this.metadata.configKey(), response, this.metadata.returnType(), elapsedTime);

        try {
            if (!resultFuture.isDone()) {
                throw new IllegalStateException("Response handling not done");
            } else {
                return resultFuture.join();
            }
        } catch (CompletionException var12) {
            Throwable cause = var12.getCause();
            if (cause != null) {
                throw cause;
            } else {
                throw var12;
            }
        }
    }
}

this.targetRequest(template) 请求构建:

Request targetRequest(RequestTemplate template) {
	// 拦截器调用
   Iterator var2 = this.requestInterceptors.iterator();

    while(var2.hasNext()) {
        RequestInterceptor interceptor = (RequestInterceptor)var2.next();
        interceptor.apply(template);
    }
	// 构建 Request  http 请求
    return this.target.apply(template);
}

target.apply(template) 根据 RequestTemplate 组装http 请求:

public Request request() {
   if (!this.resolved) {
        throw new IllegalStateException("template has not been resolved.");
    } else {
        return Request.create(this.method, this.url(), this.headers(), this.body, this);
    }
}

最终通过 Request 构造方法进行属性填充:

public static Request create(HttpMethod httpMethod, String url, Map<String, Collection<String>> headers, Body body, RequestTemplate requestTemplate) {
    return new Request(httpMethod, url, headers, body, requestTemplate);
 }

 Request(HttpMethod method, String url, Map<String, Collection<String>> headers, Body body, RequestTemplate requestTemplate) {
     this.httpMethod = (HttpMethod)Util.checkNotNull(method, "httpMethod of %s", new Object[]{method.name()});
     this.url = (String)Util.checkNotNull(url, "url", new Object[0]);
     this.headers = (Map)Util.checkNotNull(headers, "headers of %s %s", new Object[]{method, url});
     this.body = body;
     this.requestTemplate = requestTemplate;
     this.protocolVersion = Request.ProtocolVersion.HTTP_1_1;
 }

二、feign http 请求的发送&结果解析:

在构建http 的 RequestTemplate 模版后在executeAndDecode 方法中通过 this.client.execute(request, options) 进行请求的发送;

2.1. 使用默认的HttpURLConnection:

2.1.1 请求发送:

public Response execute(Request request, Request.Options options) throws IOException {
	// 创建http 请求并发送
 	 HttpURLConnection connection = this.convertAndSend(request, options);
 	 // 获取http 请求结果并返回
     return this.convertResponse(connection, request);
 }

this.convertAndSend(request, options) 请求发送:

 HttpURLConnection convertAndSend(Request request, Request.Options options) throws IOException {
   // 创建 URL 对象
   URL url = new URL(request.url());
   // 打开连接:
    HttpURLConnection connection = this.getConnection(url);
    if (connection instanceof HttpsURLConnection) {
    	// https 处理
        HttpsURLConnection sslCon = (HttpsURLConnection)connection;
        if (this.sslContextFactory != null) {
            sslCon.setSSLSocketFactory(this.sslContextFactory);
        }

        if (this.hostnameVerifier != null) {
            sslCon.setHostnameVerifier(this.hostnameVerifier);
        }
    }
	// 连接和读取超时
    connection.setConnectTimeout(options.connectTimeoutMillis());
    connection.setReadTimeout(options.readTimeoutMillis());
    connection.setAllowUserInteraction(false);
    connection.setInstanceFollowRedirects(options.isFollowRedirects());
    // 设置请求方法(默认为 GET)
    connection.setRequestMethod(request.httpMethod().name());
    Collection<String> contentEncodingValues = (Collection)request.headers().get("Content-Encoding");
    boolean gzipEncodedRequest = this.isGzip(contentEncodingValues);
    boolean deflateEncodedRequest = this.isDeflate(contentEncodingValues);
    boolean hasAcceptHeader = false;
    Integer contentLength = null;
    Iterator var10 = request.headers().keySet().iterator();

    while(var10.hasNext()) {
        String field = (String)var10.next();
        if (field.equalsIgnoreCase("Accept")) {
            hasAcceptHeader = true;
        }

        Iterator var12 = ((Collection)request.headers().get(field)).iterator();

        while(var12.hasNext()) {
            String value = (String)var12.next();
            if (field.equals("Content-Length")) {
                if (!gzipEncodedRequest && !deflateEncodedRequest) {
                    contentLength = Integer.valueOf(value);
                    connection.addRequestProperty(field, value);
                }
            } else {
                connection.addRequestProperty(field, value);
            }
        }
    }

    if (!hasAcceptHeader) {
    	//  设置请求首部
        connection.addRequestProperty("Accept", "*/*");
    }
	// 对于 POST 或 PUT 请求,向 OutputStream 写入数据
    if (request.body() != null) {
        if (this.disableRequestBuffering) {
            if (contentLength != null) {
                connection.setFixedLengthStreamingMode(contentLength);
            } else {
                connection.setChunkedStreamingMode(8196);
            }
        }

        connection.setDoOutput(true);
        OutputStream out = connection.getOutputStream();
        if (gzipEncodedRequest) {
            out = new GZIPOutputStream((OutputStream)out);
        } else if (deflateEncodedRequest) {
            out = new DeflaterOutputStream((OutputStream)out);
        }

        try {
            ((OutputStream)out).write(request.body());
        } finally {
            try {
                ((OutputStream)out).close();
            } catch (IOException var19) {
            }

        }
    }

    return connection;
}

2.1.2 请求结果封装:

convertResponse(connection, request) 去获取请求的响应信息;

 Response convertResponse(HttpURLConnection connection, Request request) throws IOException {
 	// 响应代码
    int status = connection.getResponseCode();
    //  响应消息
    String reason = connection.getResponseMessage();
    if (status < 0) {
        throw new IOException(String.format("Invalid status(%s) executing %s %s", status, connection.getRequestMethod(), connection.getURL()));
    } else {
        Map<String, Collection<String>> headers = new TreeMap(String.CASE_INSENSITIVE_ORDER);
        Iterator var6 = connection.getHeaderFields().entrySet().iterator();

        while(var6.hasNext()) {
            Map.Entry<String, List<String>> field = (Map.Entry)var6.next();
            if (field.getKey() != null) {
                headers.put(field.getKey(), field.getValue());
            }
        }

        Integer length = connection.getContentLength();
        if (length == -1) {
            length = null;
        }
		// 读取响应数据
        Object stream;
        if (status >= 400) {
            stream = connection.getErrorStream();
        } else if (this.isGzip((Collection)headers.get("Content-Encoding"))) {
            stream = new GZIPInputStream(connection.getInputStream());
        } else if (this.isDeflate((Collection)headers.get("Content-Encoding"))) {
            stream = new InflaterInputStream(connection.getInputStream());
        } else {
            stream = connection.getInputStream();
        }
		// 返回相应的 Response对象
        return Response.builder().status(status).reason(reason).headers(headers).request(request).body((InputStream)stream, length).build();
    }
}

2.2 使用okHttp:

启用okHttp :

feign:
  okhttp:
    enabled: true

2.2 .1 发起请求

public Response execute(feign.Request input, feign.Request.Options options) throws IOException {
	// 超时时间配置项
  okhttp3.OkHttpClient requestScoped = this.getClient(options);
  	// 构建http 请求
    Request request = toOkHttpRequest(input);
    // 获取请求结果并封装 Response  对象返回
    okhttp3.Response response = requestScoped.newCall(request).execute();
    return toFeignResponse(response, input).toBuilder().request(input).build();
}

this.getClient(options) 配置项:

private okhttp3.OkHttpClient getClient(feign.Request.Options options) {
   okhttp3.OkHttpClient requestScoped;
   	// 如果OkHttpClient 配置的请求连接,读取超时时间以及重定向策略和 我们对客户端配置的相同则直接使用OkHttpClient 
   	// 否则使用客户端的配置
    if (this.delegate.connectTimeoutMillis() == options.connectTimeoutMillis() && this.delegate.readTimeoutMillis() == options.readTimeoutMillis() && this.delegate.followRedirects() == options.isFollowRedirects()) {
        requestScoped = this.delegate;
    } else {
        requestScoped = this.delegate.newBuilder().connectTimeout((long)options.connectTimeoutMillis(), TimeUnit.MILLISECONDS).readTimeout((long)options.readTimeoutMillis(), TimeUnit.MILLISECONDS).followRedirects(options.isFollowRedirects()).build();
    }

    return requestScoped;
}

在这里插入图片描述

如果okhttp 和客户端都进行了配置,则以客户端配置项为准;
在这里插入图片描述

toOkHttpRequest(input) 构建request 请求:

static Request toOkHttpRequest(feign.Request input) {
  Request.Builder requestBuilder = new Request.Builder();
  	// 构建 url
    requestBuilder.url(input.url());
    MediaType mediaType = null;
    boolean hasAcceptHeader = false;
    Iterator var4 = input.headers().keySet().iterator();

    while(var4.hasNext()) {
        String field = (String)var4.next();
        if (field.equalsIgnoreCase("Accept")) {
            hasAcceptHeader = true;
        }

        Iterator var6 = ((Collection)input.headers().get(field)).iterator();

        while(var6.hasNext()) {
            String value = (String)var6.next();
            requestBuilder.addHeader(field, value);
            if (field.equalsIgnoreCase("Content-Type")) {
                mediaType = MediaType.parse(value);
                if (input.charset() != null) {
                    mediaType.charset(input.charset());
                }
            }
        }
    }

    if (!hasAcceptHeader) {
        requestBuilder.addHeader("Accept", "*/*");
    }
	// 放入请求参数
    byte[] inputBody = input.body();
    boolean isMethodWithBody = HttpMethod.POST == input.httpMethod() || HttpMethod.PUT == input.httpMethod() || HttpMethod.PATCH == input.httpMethod();
    if (isMethodWithBody) {
        requestBuilder.removeHeader("Content-Type");
        if (inputBody == null) {
            inputBody = new byte[0];
        }
    }
	
    RequestBody body = inputBody != null ? RequestBody.create(mediaType, inputBody) : null;
    // 放入请求方式
    requestBuilder.method(input.httpMethod().name(), body);
    return requestBuilder.build();
}

2.2 .2 请求结果封装:


okhttp3.Response response = requestScoped.newCall(request).execute();
 return toFeignResponse(response, input).toBuilder().request(input).build();

toFeignResponse 对请求结果封装

private static Response toFeignResponse(okhttp3.Response response, feign.Request request) throws IOException {
    return Response.builder().protocolVersion((feign.Request.ProtocolVersion)Util.enumForName(feign.Request.ProtocolVersion.class, response.protocol())).status(response.code()).reason(response.message()).request(request).headers(toMap(response.headers())).body(toBody(response.body())).build();
}

三、feign http 返回值的解析:

feign 请求返回的结果 默认使用 SpringDecoder 进行解析,SpringDecoder 基于 Spring 的 HttpMessageConverter 抽象进行实现,它集成了 Spring 的消息转换功能,允许你将HTTP的响应体转换成Java对象,对于SpringDecoder 的解释可见 四、 扩展 章节 。

Object executeAndDecode(RequestTemplate template, Request.Options options) throws Throwable {
    Request request = this.targetRequest(template);
     if (this.logLevel != Level.NONE) {
         this.logger.logRequest(this.metadata.configKey(), this.logLevel, request);
     }

     long start = System.nanoTime();

     Response response;
     try {
         response = this.client.execute(request, options);
         response = response.toBuilder().request(request).requestTemplate(template).build();
     } catch (IOException var13) {
         if (this.logLevel != Level.NONE) {
             this.logger.logIOException(this.metadata.configKey(), this.logLevel, var13, this.elapsedTime(start));
         }

         throw FeignException.errorExecuting(request, var13);
     }

     long elapsedTime = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start);
     if (this.decoder != null) {
     		// 如果设置了使用自己的 解码器 
         return this.responseInterceptor.aroundDecode(new InvocationContext(this.decoder, this.metadata.returnType(), response));
     } else {
     	// 使用springmvc SpringDecoder 进行返回内容解析
         CompletableFuture<Object> resultFuture = new CompletableFuture();
         this.asyncResponseHandler.handleResponse(resultFuture, this.metadata.configKey(), response, this.metadata.returnType(), elapsedTime);

         try {
             if (!resultFuture.isDone()) {
                 throw new IllegalStateException("Response handling not done");
             } else {
             	// 返回SpringDecoder 解析后的内容
                 return resultFuture.join();
             }
         } catch (CompletionException var12) {
             Throwable cause = var12.getCause();
             if (cause != null) {
                 throw cause;
             } else {
                 throw var12;
             }
         }
     }
 }

handleResponse 方法对返回结果进行解析:

void handleResponse(CompletableFuture<Object> resultFuture, String configKey, Response response, Type returnType, long elapsedTime) {
        boolean shouldClose = true;

        try {
        	// 如果feign 日志级别 不是NONE 则进行日志输出
            if (this.logLevel != Level.NONE) {
                response = this.logger.logAndRebufferResponse(configKey, this.logLevel, response, elapsedTime);
            }

			// 返回内容解析
            if (Response.class == returnType) {
                if (response.body() == null) {
                    resultFuture.complete(response);
                } else if (response.body().length() != null && (long)response.body().length() <= 8192L) {
                    byte[] bodyData = Util.toByteArray(response.body().asInputStream());
                    resultFuture.complete(response.toBuilder().body(bodyData).build());
                } else {
                    shouldClose = false;
                    resultFuture.complete(response);
                }
            } else {
                Object result;
                if (response.status() >= 200 && response.status() < 300) {
                    if (this.isVoidType(returnType)) {
                        resultFuture.complete((Object)null);
                    } else {
                    	// 使用 SpringDecoder  进行解析
                        result = this.decode(response, returnType);
                        shouldClose = this.closeAfterDecode;
                        // 放入解析的结果
                        resultFuture.complete(result);
                    }
                } else if (this.dismiss404 && response.status() == 404 && !this.isVoidType(returnType)) {
                    result = this.decode(response, returnType);
                    shouldClose = this.closeAfterDecode;
                    resultFuture.complete(result);
                } else {
                    resultFuture.completeExceptionally(this.errorDecoder.decode(configKey, response));
                }
            }
        } catch (IOException var13) {
            if (this.logLevel != Level.NONE) {
                this.logger.logIOException(configKey, this.logLevel, var13, elapsedTime);
            }

            resultFuture.completeExceptionally(FeignException.errorReading(response.request(), response, var13));
        } catch (Exception var14) {
            resultFuture.completeExceptionally(var14);
        } finally {
            if (shouldClose) {
                Util.ensureClosed(response.body());
            }

        }

    }

    Object decode(Response response, Type type) throws IOException {
        return this.responseInterceptor.aroundDecode(new InvocationContext(this.decoder, type, response));
    }

四、 扩展:

4.1 SpringDecoder:

在 Spring Cloud OpenFeign 中,SpringDecoder 是一个特定的解码器,用来处理从对Feign客户端发出的请求返回的响应。SpringDecoder 基于 Spring 的 HttpMessageConverter 抽象进行实现,它集成了 Spring 的消息转换功能,允许你将HTTP的响应体转换成Java对象。

Feign是一个声明式的Web服务客户端,使得编写Web服务客户端变得更加简单。Feign中内置了对响应的解码(即反序列化)的支持。默认情况下,它使用一个名为Decoder的组件来对返回的响应体进行解码处理。

SpringDecoder实现了Feign中的Decoder接口。在与Spring Cloud集成时,Feign可利用Spring MVC框架的强大功能,包括使用HttpMessageConverters来对响应体进行解码。这意味着你可以使用如Jackson JSON或Gson这样的库来自动将返回的JSON映射到你的对象中。

当使用Spring Cloud OpenFeign时,SpringDecoder通常会自动配置并使用。你可以自定义HttpMessageConverter列表,以便在Feign客户端中处理特定的响应类型。

例如,假设你有一个OpenFeign接口来与REST API通信,并且你想要映射返回的JSON到你的Java对象中。Spring Cloud会使用SpringDecoder来自动处理解码工作:

@FeignClient(name = "myclient", url = "http://my.api.com")
public interface MyClient {
    @GetMapping("/some-endpoint")
    MyResource getMyResource();
}

在上面的例子中,当你调用getMyResource方法时,SpringDecoder会使用合适的HttpMessageConverter来将/some-endpoint端点返回的JSON转换成MyResource对象。如果你有自定义的需求,比如需要使用特殊的序列化配置或提供其他格式的转换支持,可以自定义Spring的HttpMessageConverter配置,并且它将反映在Feign客户端的处理中。

4.2 okHttp 比默认的HttpURLConnection好处:

OpenFeign是一个声明式的HTTP客户端框架,用于简化调用远程服务。底层的HTTP客户端可以由用户选择,常见的有Java原生的HttpURLConnection(默认)和OkHttpClient。每个客户端都有自己的优缺点。使用OkHttpClient而非HttpURLConnection作为Feign的客户端通信工具可能有以下几个优点:

1.) 性能提升

  • OkHttpClient支持连接池,减少握手时间,对于频繁的请求可以提高性能。
  • 具有更好的响应缓存机制,可以减少不必要的网络请求。
  • 支持HTTP/2,可以通过一个TCP连接发送多个请求,减少延时。
  1. 更丰富的特性

    • OkHttpClient支持各种拦截器,便于添加诸如重试、日志记录、头部处理等额外的功能。
    • 提供同步和异步API,可以根据需求进行选择。
    • 更易用的超时配置,例如可以针对读取超时、连接超时和写入超时分别设置。
  2. 更好的错误处理

    • OkHttpClient提供更详细的错误信息,可能还包括网络情况的统计等。
    • 支持自动重试机制,而且可以自定义重试策略。
  3. 现代API设计

    • OkHttpClient使用Fluent API,链式调用,易于理解和使用。
    • 支持响应流处理,可以更加有效地处理大型响应体。

5.) API一致性和可维护性

  • OkHttp 维护活跃,并且遵守语义化版本控制,确保API的一致性和向后兼容性。
  1. 更好的社区支持

    • OkHttp 拥有活跃的社区支持和定期的更新,可以确保利用最新的技术和修复最近的问题或安全漏洞。
  2. 内置功能

    • 支持GZIP压缩来减少数据的传输量,从而提升性能。
    • 内置WebSocket支持,对于需要与服务器建立持久连接的应用十分有用。

总结

SpringCloud-OpenFeign 通过 SynchronousMethodHandler 类中的invoke 方法,对http 请求进行组装并进行发送,默认使用HttpURLConnection 当通过feign.okhttp.enabled : true 可以使用okhttp 客户端发送请求,最终使用springmvc 的HttpMessageConverter 对请求结果进行解析并返回;

  • 20
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值