OpenFeign 声明式HTTP客户端

OpenFeign是Netflix开源的一款声明式HTTP客户端,它能简化HTTP接口调用的代码逻辑,在微服务的服务间通信方面应用十分广泛。此外,SpringCloud对OpenFeign进行了集成,如果使用SpringCloud建议直接使用spring-cloud-starter-openfeign这个项目,但如果不希望引入SpringCloud也可以直接使用OpenFeign库。

这里我们简单介绍OpenFeign库(非SpringCloud环境)的使用。

引入Maven依赖

OpenFeign这个库在早期的groupIdcom.netflix.feign,2016年改为了io.github.openfeign,同时项目也改名为OpenFeign,前者也早就停止更新了,我们使用新的包即可。

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

feign-core是OpenFeign的核心功能,feign-jackson是和JSON序列化相关的库,这里其实也可以换成Gson等其它的库,但因为SpringWeb默认的JSON序列化器就是Jackson(工程中也存在Jackson的依赖),所以我们直接选择feign-jackson即可。

除此之外,OpenFeign默认基于HTTPURLConnection客户端进行HTTP请求,如果使用这个默认客户端就不需要再额外引入其他依赖了,但如果想使用OkHttpClient等其它HTTP客户端,OpenFeign也是支持的,但也要引入对应客户端库的依赖。

使用OpenFeign发起HTTP请求

这里简单起见,我们就直接在SpringBoot环境中使用OpenFeign。如果非Spring工程也是类似的,这里就不多介绍了。

例子中,假设我们要调用一个GET的HTTP接口:GET http://127.0.0.1:8081/api/v1/order/queryOrderById?orderId={orderId}

package com.gacfox.web.client;

import com.gacfox.orderapi.OrderDto;
import feign.Param;
import feign.RequestLine;

public interface OrderServiceClient {
    @RequestLine("GET /api/v1/order/queryOrderById?orderId={orderId}")
    OrderDto queryOrderById(@Param("orderId") String orderId);
}

上面代码我们声明了一个接口,其中使用了几个OpenFeign提供的注解:

  • @RequestLine:声明具体的调用地址,其中可以包含占位符
  • @Param:具体的参数,可以填充@RequestLine中声明的占位符

注意:对于GET请求我们在参数中一定要使用@Param将参数拼到URL的形式,OpenFeign默认使用JDK的HTTPURLConnection,它不支持GET请求带Body(当然这确实也不合理)。OpenFeign接口参数如果不指定任何注解默认是放到HTTP请求的Body中的,HTTPURLConnection此时又会将GET请求转为POST,调用就可能会报错405 Method Not Allowed

package com.gacfox.web.config;

import com.gacfox.web.client.OrderServiceClient;
import feign.Feign;
import feign.jackson.JacksonDecoder;
import feign.jackson.JacksonEncoder;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class ClientConfig {
    @Value("${demoboot.order.url}")
    private String orderUrl;

    @Bean
    public OrderServiceClient getOrderServiceClient() {
        return Feign.builder()
                .encoder(new JacksonEncoder())
                .decoder(new JacksonDecoder())
                .target(OrderServiceClient.class, orderUrl);
    }
}

上面代码我们具体创建了OrderServiceClient对象,我们虽然没有具体编写该接口的实现类,但OpenFeign通过代理帮我们生成了,在具体实现的对象中会有发起HTTP请求的相关逻辑。创建时我们还传了几个参数,比如Jackson的序列化和反序列化器,以及接口类对象,和具体的调用地址(协议、IP和端口)。在实际开发中,我们其实也可以使用注解和反射的形式封装这部分逻辑,实现接口代理的自动注册,这里就不多介绍了。

注:这里${demoboot.order.url}实际值为http://127.0.0.1:8081

package com.gacfox.web.comtroller;

import com.gacfox.orderapi.OrderDto;
import com.gacfox.web.client.OrderServiceClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;

@Controller
@RequestMapping(value = "/")
public class IndexPageController {

    @Autowired
    private OrderServiceClient orderServiceClient;

    @GetMapping(value = "/orderPage")
    public String orderPage(@RequestParam String orderId, Model model) {
        OrderDto orderDto = orderServiceClient.queryOrderById(orderId);
        model.addAttribute("order", orderDto);
        return "order";
    }
}

最终,我们在一个Controller中注入了OrderServiceClient对象,并在具体的代码中对其进行了调用。

发起POST请求也是类似的,下面例子我们写了一个POST请求的接口方法:

@Headers("Content-Type: application/json")
@RequestLine("POST /api/v1/order/addOrder")
void addOrder(OrderDto orderDto);

这里我们的orderDto参数没有带任何注解,表示该参数使用指定的默认序列化器写入请求Body。不过这里我们还用到了@Headers注解,这是因为我们发起的是一个POST请求,其Body为JSON字符串,如果不加Content-Type: application/json,上游的服务是拒绝识别解析的。

HTTP接口注解

之前我们已经使用过了几个OpenFeign的注解,这些注解的具体用法可以参考以下表格。

注解 目标 说明
@RequestLine 方法 指定接口的URL,可以带有{}格式的参数占位符
@Param 参数 指定参数填充@RequestLine@Headers@Body中的占位符
@Headers 类,方法 指定请求头,可以带有{}格式的参数占位符
@QueryMap 参数 可以指定Map类型或POJO的一组封装的参数。用于填充@RequestLine中的占位符
@HeaderMap 参数 可以指定Map类型或POJO的一组封装的参数。用于填充@Headers中的占位符
@Body 方法 请求Body模板,支持{}占位符,用于Body为非默认序列化器格式,需要手动指定的情况

请求拦截器

OpenFeign支持指定请求的拦截器,我们可以在拦截器中指定一些通用的处理逻辑。

package com.gacfox.web.client;

import feign.RequestInterceptor;
import feign.RequestTemplate;

public class ApiRequestInterceptor implements RequestInterceptor {
    @Override
    public void apply(RequestTemplate requestTemplate) {
        requestTemplate.header("Content-Type", "application/json");
    }
}

上面代码我们创建了一个ApiRequestInterceptor类,其中逻辑非常简单,给所有请求增加一个Header。

return Feign.builder()
        .encoder(new JacksonEncoder())
        .decoder(new JacksonDecoder())
        .requestInterceptor(new ApiRequestInterceptor())
        .target(OrderServiceClient.class, orderUrl);

创建客户端代理对象时,我们直接用requestInterceptor()方法指定拦截器对象即可。

相关配置

下面例子我们对客户端请求进行了一些配置。

return Feign.builder()
        .encoder(new JacksonEncoder())
        .decoder(new JacksonDecoder())
        .options(new Request.Options(1, TimeUnit.SECONDS, 5, TimeUnit.SECONDS, true))
        .target(OrderServiceClient.class, orderUrl);

Request.Options()方法中,前两个数字参数指定请求的connectTimeoutreadTimeout,其默认值分别为10秒和60秒,最后一个参数指定是否followRedirects

作者:Gacfox
版权声明:本网站为非盈利性质,文章如非特殊说明均为原创,版权遵循知识共享协议CC BY-NC-ND 4.0进行授权,转载必须署名,禁止用于商业目的或演绎修改后转载。
Copyright © 2017-2024 Gacfox All Rights Reserved.
Build with NextJS | Sitemap