前言

Feign是一个流行的Java HTTP客户端,用于简化基于HTTP的API调用。在本文中,我们将介绍如何使用Feign与OkHttp或HttpClient集成来优化应用程序的性能。我们将研究各种因素,例如连接池,超时和性能指标,并提供具体的代码示例,以便您可以在实践中实现这些最佳实践。

Feign

Feign是一种基于Java的声明式HTTP客户端,可以使编写HTTP客户端变得更加容易和简单。使用Feign,可以通过创建一个接口,然后通过该接口来发送HTTP请求,而无需手动处理HTTP连接、请求和响应。Feign将根据接口的定义,自动生成HTTP请求和响应的实现,并将其绑定到该接口上,使得发送HTTP请求变得非常容易和直观。

Feign使用了Java的反射机制和动态代理技术,来根据接口定义动态地生成一个HTTP客户端实现。可以在接口定义中添加@RequestMapping等注解来定义HTTP请求的细节,例如请求方法、请求URL、请求头、请求体等。然后,Feign将使用这些注解来自动生成一个HTTP请求,并将其发送到服务器。Feign还提供了丰富的配置选项,可以自定义HTTP客户端的行为,例如设置连接超时、读取超时、重试机制等。

Feign的优点包括:

  • 简单易用:通过声明式接口,使得编写HTTP客户端变得非常简单和直观。
  • 易于集成:Feign与Spring Cloud等现代化开发框架无缝集成,可以方便地在微服务架构中使用。
  • 可配置性:Feign提供了丰富的配置选项,可以自定义HTTP客户端的行为。
  • 可扩展性:Feign是一个可扩展的框架,可以通过自定义组件和拦截器来增强其功能和灵活性。

Feign已有默认客户端,为何还要使用第三方连接池?

Feign有个Client接口,定义一个针对url执行请求并返回响应的方法

image-20230223185531926

其默认实现客户端为URLConnection,是Java标准库中自带的HTTP客户端,但它的功能相对较弱,缺少一些高级功能,例如连接池、请求复用、拦截器等。并且通过下面可以看到每次都会调用openConnection()方法开启一个socket连接。

image-20230223185648561

如果并发量一大,对系统整体的性能有一定的影响,因此可以考虑使用Apache HttpClient或OkHttp。

需要注意的是,如果同时使用了feign-okhttpfeign-httpclient依赖,Feign会默认使用OkHttpClient

URLConnection

URLConnection是Java标准库中提供的一个用于发送HTTP请求和接收响应的类。它是一个基于Java IO流的HTTP客户端,提供了一种简单的方式来与Web服务器进行通信。

URLConnection可以用于发送HTTP GET、POST、PUT、DELETE等各种类型的请求,支持HTTP、HTTPS协议,并且支持基本身份验证和Cookie管理。

OKHttp

OkHttp是一个流行的Java HTTP客户端库,由Square公司开发和维护。它被设计为更加现代和高效的替代品,可以用于与Web服务器进行通信,发送HTTP请求和接收响应。OkHttp具有以下特点:

特性:

  • 支持HTTP/2协议:OkHttp可以使用HTTP/2协议进行通信,这意味着它可以更有效地利用网络带宽,减少网络延迟,并允许并发请求。
  • 简单易用:OkHttp的API设计简单易用,可以轻松地发送HTTP请求并处理响应。
  • 高效性能:OkHttp使用连接池和请求复用机制来提高性能,减少网络延迟和资源占用。
  • 支持拦截器:OkHttp支持自定义拦截器,可以方便地实现各种HTTP请求和响应的自定义处理逻辑。
  • 支持Gzip压缩:OkHttp支持Gzip压缩,可以减少网络传输数据的大小,提高网络传输效率。
  • 支持HTTPS:OkHttp支持HTTPS协议,并且可以验证SSL证书,保证通信的安全性。

Apache HttpClient

Apache HttpClient是一个流行的Java HTTP客户端库,由Apache Software Foundation开发和维护。它提供了一组强大的API,可以轻松地发送HTTP请求和处理响应。Apache HttpClient具有以下特点:

  • 支持多种协议:可以用于发送HTTP、HTTPS、FTP、SMTP等多种协议的请求。
  • 高效性能:采用连接池和请求复用机制来提高性能,减少网络延迟和资源占用。
  • 支持拦截器:支持自定义拦截器,可以方便地实现各种HTTP请求和响应的自定义处理逻辑。
  • 支持连接超时和读取超时设置:可以设置连接超时和读取超时时间,可以有效地避免网络异常和连接阻塞问题。
  • 支持重定向:可以自动处理HTTP重定向,可以减少开发人员的工作量。
  • 支持代理:支持使用代理服务器发送HTTP请求。

虽然Apache HttpClient提供了很多高级功能,但是在Java 11及以上版本中,HttpClient已经被添加到Java标准库中,因此也可以直接使用Java标准库中的HttpClient来发送HTTP请求。

Feign使用OkHttp

全局使用OkHttp

  1. 默认情况下,feign不包括okhttp客户端,需要使用下述依赖引入

    <dependency>
        <groupId>io.github.openfeign</groupId>
        <artifactId>feign-okhttp</artifactId>
    </dependency>
    
  2. 引入后,在配置文件中加入配置打开okhttp

    feign.okhttp.enabled=true
    
  3. 可以借用feign调整okhttp的部分参数

    # 最大连接数
    feign.httpclient.maxConnections=200
    # 每个路由的最大连接数
    feign.httpclient.maxConnectionsPerRoute=50
    # 连接存活时间
    feign.httpclient.timeToLive=900
    # 连接超时时间
    feign.httpclient.connectionTimeout=2000
    

指定Feign接口使用OkHttp

  1. 将OkHttp客户端添加到项目中

    <dependency>
        <groupId>com.squareup.okhttp3</groupId>
        <artifactId>okhttp</artifactId>
        <version>3.14.9</version>
    </dependency>
    
  2. 创建一个实现了Feign的Client接口的OkHttp客户端

    import feign.Client;
    import okhttp3.OkHttpClient;
    import okhttp3.Request;
    import okhttp3.Response;
    
    public class OkHttpFeignClient implements Client {
        private final OkHttpClient client;
    
        public OkHttpFeignClient() {
            this.client = new OkHttpClient();
        }
    
        @Override
        public Response execute(Request request, Request.Options options) throws IOException {
            okhttp3.Request.Builder builder = new okhttp3.Request.Builder()
                    .url(request.url())
                    .method(request.method(), request.body());
    
            request.headers().forEach((name, values) -> {
                values.forEach(value -> {
                    builder.addHeader(name, value);
                });
            });
    
            okhttp3.Request okhttpRequest = builder.build();
            okhttp3.Response okhttpResponse = client.newCall(okhttpRequest).execute();
    
            return Response.builder()
                    .status(okhttpResponse.code())
                    .reason(okhttpResponse.message())
                    .headers(okhttpResponse.headers().toMultimap())
                    .body(okhttpResponse.body().byteStream(), okhttpResponse.body().contentLength())
                    .request(request)
                    .build();
        }
    }
    
  3. 在Feign客户端接口中,使用@FeignClient注解将Client属性设置为OkHttpFeignClient

    import feign.Headers;
    import feign.Param;
    import feign.RequestLine;
    import org.springframework.cloud.openfeign.FeignClient;
    import org.springframework.web.bind.annotation.GetMapping;
    
    @FeignClient(name = "example", url = "http://example.com", configuration = MyFeignConfiguration.class, client = OkHttpFeignClient.class)
    public interface MyFeignClient {
        @GetMapping("/api/users/{userId}")
        @Headers("Authorization: {token}")
        User getUser(@Param("userId") long userId, @Param("token") String token);
    }
    

Feign使用HttpClient

全局使用HttpClient

  1. 添加HttpClient依赖

    <dependency>
        <groupId>org.apache.httpcomponents</groupId>
        <artifactId>httpclient</artifactId>
        <version>4.5.13</version>
    </dependency>
    
  2. 修改配置文件

    feign.httpclient.enabled=true
    

指定接口使用HttpClient

  1. 添加HttpClient依赖

    <dependency>
        <groupId>org.apache.httpcomponents</groupId>
        <artifactId>httpclient</artifactId>
        <version>4.5.13</version>
    </dependency>
    
  2. 创建配置类

    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.apache.http.impl.client.HttpClientBuilder;
    import feign.httpclient.ApacheHttpClient;
    
    @Configuration
    public class ExampleClientConfiguration {
        @Bean
        public feign.Client feignClient() {
            return new ApacheHttpClient(HttpClientBuilder.create().build());
        }
    }
    
  3. 在Feign客户端接口中,使用@FeignClient注解将Client属性设置为ExampleClientConfiguration

    @FeignClient(name = "example", url = "http://example.com",
                 configuration = ExampleClientConfiguration.class)
    public interface ExampleClient {
        @RequestMapping(method = RequestMethod.GET, value = "/api/data")
        String getData();
    }