计算机系统应用教程网站

网站首页 > 技术文章 正文

Feign : 优雅的调用 API

btikc 2024-09-10 12:03:35 技术文章 18 ℃ 0 评论


前言

在项目开发中,系统间会存在接口调用,在微服务架构中可以使用 Dubbo 来实现服务间的 RPC 调用,也可以使用 Spring Could 完成 API 调用,这些框架屏蔽了 API 调用中的像服务建连、通信协议、序列化等等细节,让开发者可以很轻松发起一个接口调用,但是当需要调用一个外部系统时,由于通信协议、技术框架、网络隔离、安全性等因素,一般会采用原生 HTTP 协议进行接口调用

URLConnection

在 Java 中,JDK 提供了 HTTP 的工具类 URLConnection,通过 URLConnection 可以发起 HTTP 请求,如:

HttpURLConnection connection = null;
InputStream inputStream = null;
InputStreamReader inputStreamReader = null;
BufferedReader bufferedReader = null;
try {
    connection = (HttpURLConnection)(new URL("http://www.api.com/get")).openConnection();
    connection.connect();
    // 读取接口返回内容
    inputStream = connection.getInputStream();
    inputStreamReader = new InputStreamReader(inputStream);
    bufferedReader = new BufferedReader(inputStreamReader);
    String line = bufferedReader.readLine();
    StringBuffer lines = new StringBuffer();
    while(line != null) {
        line = bufferedReader.readLine();
        lines.append(line);
    }
   
    // 反序列化成 json
    JSONObject jsonObject = JSON.parseObject(EntityUtils.toString(response.getEntity()));
    // TODO
    // jsonObject.get('xx')
    // ……
    
} catch(Exception e) {
    e.printStackTrace();
} finally {
    connection.disconnect();
    if(inputStream != null) inputStream.close();
    if(inputStreamReader != null) inputStreamReader.close();
    if(bufferedReader != null) bufferedReader.close();
}

在不借助任何框架的前提下使用 JDK 自带的类库来发起 HTTP 调用

HttpClient

在实际项目中一般不会使用 JDK 自带的 URLConnection,原因是性能不高,没有连接池,代码也很臃肿,所以一般会使用 HttpClient 来代替 URLConnection,这也是大家比较常见的使用方式,如:

SchemeRegistry schreg = new SchemeRegistry();
schreg.register(new Scheme("http", 80, PlainSocketFactory.getSocketFactory()));
PoolingClientConnectionManager conMgr = new PoolingClientConnectionManager(schreg);
conMgr.setMaxTotal(200);// 连接池最大数
conMgr.setDefaultMaxPerRoute(conMgr.getMaxTotal());

// 发起 API 调用
DefaultHttpClient defaultHttpClient = new DefaultHttpClient(conMgr);
HttpGet get = new HttpGet("http://www.api.com/get");
CloseableHttpResponse response = defaultHttpClient.execute(get);

// 反序列化成 json
JSONObject jsonObject = JSON.parseObject(EntityUtils.toString(response.getEntity()));
// TODO
// jsonObject.get('xx')
// ……

EntityUtils.consume(response.getEntity());

一般来说 HttpClient 可以满足日常的开发了,如果是使用 json 作为序列化协议的话,则需要在调用接口和获取接口返回值时分别进行序列化和反序列化操作,这样看似没有问题,但假如当系统中需要调用100个不同的 HTTP 接口,每个接口都需要处理序列化、反序列化以及上面的 HttpClient 相关的代码,代码可读性就非常差,系统的可维护性就低,当然你可以封装 HttpClient 的相关方法,对序列化、反序列化进行泛化封装,提高可读性,此外也有另一种更好的处理方法,使用 Feign 框架,像调用本地方法一样调用 API。

Feign

引入依赖

<dependency>
  <groupId>io.github.openfeign</groupId>
  <artifactId>feign-core</artifactId>
  <version>10.10.1</version>
</dependency>

使用 Feign 调用接口跟调用 RPC 一样,都是先定义 API 的接口

interface API {
    @RequestLine("GET /get")
    @Headers("Content-Type: application/json")
    Response get(Request request);
}

API 接口声明了一个 get 方法,接口的地址为 /get ,同时在 header 头中加入了 Content-Type,Request 作为参数是 get 接口的参数,Response 作为返回值是 get 接口的返回值,Feign 会自动将 Request 对象序列化成 /get 的参数,同时把接口返回值序列化成 Response 对象。

定义好接口后,就可以通过 Feign 来生成 "http://www.api.com" 接口的代理类

API api = Feign.builder()
                .decoder(new JacksonDecoder())
                .encoder(new JacksonEncoder())
                .client(new ApacheHttpClient(HttpClientBuilder.create().setMaxConnPerRoute(100).setMaxConnTotal(500).build()))
                .logger(new Slf4jLogger())
                .addCapability(new Metrics5Capability())
                .retryer(Retryer.NEVER_RETRY)
                .target(Oudianyun.class, "http://www.api.com");

通过 Feign.builder() 为每个域名生成一个 API 代理类,其中序列化协议使用了 json;HTTP 框架使用了 Apache HttpClient,在 Feign 内部最终还是通过 HttpClient 来发送 HTTP 请求,也可以只用 OkHttp;日志使用了 Slf4j

通过 Feign.builder 得到 API 的代理类后,就可以发起接口调用了

// 构建参数
Request request = new Request();
request.setId("abc");
// 调用 get 接口
Response response = api.get(request);

发现没有,使用 Feign 后根据不用关心 HTTP 请求中需要处理的序列化、请求头等硬编码,这些都是 Feign 帮我们实现了,使用时只需要根据不同的域名通过 Feign 来生成代理类,然后跟调用本地方法一样直接调用接口

上面完成简单的 Feign 的使用,在实际开发中这还是远不够的,上面的 API 对象是通过硬编码的方式来创建的,而实际项目中对象一般都是交给 Spring 来管理的,因此最佳的方式是把 Feign 和 Spring 整合

Spring Cloud Feign

Spring Cloud 整合了 Feign,并对其进行改造并将整合内容放到了 spring-cloud-openfeign 模块中,相比原生的 Feign 来说 Spring 容器管理了所有的对象,并对其进行了增强,加入了 fallback机制、负载均衡等特性。

引入依赖

<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-starter-openfeign</artifactId>
  <version>2.2.6.RELEASE</version>
</dependency>

跟 Feign 一样,Spring Cloud Feign 也是先定义 API 的接口

@FeignClient(value = "SpringCloudAPI", url ="http://www.api.com", fallback = SpringCloudAPIFallback.class)
interface SpringCloudAPI {
    
    @GetMapping("/get")
    Response get(Request request);
    
}

使用 @FeignClient 注解来声明 url 以及 fallback 降级处理类,当接口出现异常时会调用 SpringCloudAPIFallback 类中的降级方法,降级类如下:

@Component
class SpringCloudAPIFallback implements SpringCloudAPI{
    public Response get(Request request) {
        return "接口出现异常,使用 mock 数据";
    }
}

通过在 Spring Boot 中新增 @EnableFeignClients 开启 Spring Cloud Feign 功能

@SpringBootApplication(scanBasePackages = "feign")
@EnableFeignClients(basePackages = "feign")
public class SpringCloudFeignDemo {
    public static void main(String[] args) {
        new SpringApplication(SpringCloudFeignDemo.class).run(args);
    }
}

Spring Boot 中启用 Feign HttpClient 的参数配置如下:

feign.httpclient.enabled=true
feign.httpclient.connection-timeout=2000
feign.httpclient.connection-timer-repeat=3000
feign.httpclient.max-connections=500
feign.httpclient.max-connections-per-route=500
feign.httpclient.time-to-live=900
feign.httpclient.time-to-live-unit=SECONDS

Spring Could Feign 的 Fallback 是依赖 hystrix 来实现降级的,由于 hystrix 已经不维护了,因此推荐使用 Alibaba 的 Sentinel 来代替 hystrix,加入以下配置启用 Sentinel

引入依赖

<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
    <version>2.2.3.RELEASE</version>
</dependency>

开启配置

feign.sentinel.enabled=true

Tags:

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表