原来OpenFeign功能这么强大,你知道吗?

开发 前端
本文介绍了OpenFeign的基本使用方法,包括如何引入依赖、如何定义接口、如何构建客户端、如何自定义拦截器、重试器等。

OpenFeign是Spring微服务全家桶中的重要组件。前身是Netflix Feign,在2013年首次发布。2016年,Netflix发布了Feign的最后一个版本(8.18.0),并将其捐赠给开源社区,随后Feign更名为OpenFeign,于同年发布了OpenFeign的首个版本(9.0.0)。在2017年,Spring Cloud团队将对Feign的依赖升级为OpenFeign。

图片图片

OpenFeign和Netflix Feign

为了避免歧义,文中提到的Feign或OpenFeign,都是指 OpenFeign。

OpenFeign是Netflix团队开发的一个声明式、模板化的 Web 服务客户端,目标是开发一种简单、优雅的 HTTP 服务客户端。在设计时,借鉴了各种优秀类库,比如Retrofit、 JAXRS-2.0、WebSocket等。

通过OpenFeign,我们可以像调用方法一样实现HTTP API访问。

本文将介绍如何使用原生的 OpenFeign,原生的使用方式,不是集成在Spring Cloud中的使用方式。

来,一起来。

先来个简单的例子

引入依赖

OpenFeign很贴心的提供了BOM,我们可以直接使用控制组件版本。

<project>
    ……
    <properties>
        <openfeign.version>13.4</openfeign.version>
    </properties>
    
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>io.github.openfeign</groupId>
                <artifactId>feign-bom</artifactId>
                <version>${openfeign.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>
</project>

我这里使用的是13.4版本(学习的时候就得学新的,新的bug少)。

然后引入core模块:

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

有了前面的BOM,后面的模块就不用指定版本了。

定义接口

private interface Client {
    @RequestLine("GET /anything/{anything}")
    @Headers({"Content-Type: application/json"})
    String anything(@Param("anything") String anything);
}

为了调用方便,我们借助https://httpbin.org提供的HTTP API接口anything用来验证,这个接口会返回传入的参数。这样也方便我们检查调用是否正常。

创建客户端

final Client client = Feign.builder()
        .logLevel(Level.FULL)
        .target(Client.class, "https://httpbin.org");
final String anything = client.anything("testCore");
Assertions.assertNotNull(anything);
Assertions.assertTrue(anything.contains("testCore"));
System.out.println(anything);

是不是非常简单,通过建造器模式简单配置下参数,定义接口的域名,然后就像调用本地方法一样调用接口,然后就拿到返回值了。

OpenFeign提供了很多的扩展口,比如日志、解析器、拦截器、编码器、错误处理器等,可以通过builder方法进行配置。

自定义编解码器

OpenFeign提供了12种编解码器,默认使用的是字符串编解码器,如果需要自定义编解码器,可以通过builder方法进行配置。

比如,我们想要使用Jackson实现:

我们先引入feign-jackson模块:

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

然后在建造器参数指定JacksonDecoder和JacksonEncoder:

final Client client = Feign.builder()
        .logLevel(Level.FULL)
        .decoder(new JacksonDecoder())
        .encoder(new JacksonEncoder())
        .target(Client.class, "https://httpbin.org");
final Map<String, Object> requestBody = Map.of("k1", "value1", "k2", "value2");
final Map<String, Object> anythingResult = client.anythingJson("testJson", requestBody);

如果想要换成Gson,引入feign-gson模块,在建造器参数替换为GsonDecoder和GsonEncoder就行。

还有JAXB、Moshi、Fashjson、SAX等一种编解码器可以使用。

自定义客户端

OpenFeign默认的客户端是Java提供的HttpURLConnection,如果需要自定义客户端,可以通过builder方法进行配置。

比如,我们想要使用OkHttp,可以先引入feign-okhttp模块:

<dependency>
    <groupId>io.github.openfeign</groupId>
    <artifactId>feign-okhttp</artifactId>
</dependency>

然后通过client方法替换:

final Client client = Feign.builder()
        .logLevel(Level.FULL)
        .decoder(new JacksonDecoder())
        .encoder(new JacksonEncoder())
        .client(new OkHttpClient())
        .target(Client.class, "https://httpbin.org");
final Map<String, Object> anythingResult = client.anythingJsonBodyTemplate("testJson", "value1", "value2");

此时使用的就是OkHttpClient了。

OpenFeign还支持Apache HTTP、Apache HC5、Google HTTP、Java11 HTTP2、Ribbon。

自定义拦截器

拦截器可以对请求和响应进行拦截处理,比如打印日志、添加请求头、添加签名等,可以使用requestInterceptor自定义拦截器。

首先,定义我们自己的拦截器,比如我们在请求头中添加一个自定义的header:

public class MyRequestInterceptor implements RequestInterceptor {
    @Override
    public void apply(RequestTemplate template) {
        template.header("my-header", "my-value");
    }
}

然后指定拦截器:

final Client client = Feign.builder()
        .logLevel(Level.FULL)
        .decoder(new JacksonDecoder())
        .encoder(new JacksonEncoder())
        .requestInterceptor(new MyRequestInterceptor())
        .target(Client.class, "https://httpbin.org");
final Map<String, Object> anythingResult = client.anythingJsonBodyTemplate("testJson", "value1", "value2");
System.out.println(anythingResult);

Assertions.assertNotNull(anythingResult);
Assertions.assertTrue(anythingResult.get("url") instanceof String);
Assertions.assertTrue(((String) anythingResult.get("url")).endsWith("testJson"));
Assertions.assertTrue(anythingResult.containsKey("json"));
Assertions.assertTrue(anythingResult.get("json") instanceof Map<?, ?>);

Assertions.assertTrue(anythingResult.containsKey("headers"));
boolean hasMyHeader = false;
if (anythingResult.get("headers") instanceof Map headers) {
    for (Object key : headers.keySet()) {
        if (key.toString().equalsIgnoreCase("my-header")) {
            hasMyHeader = true;
            final Object value = headers.get(key);
            Assertions.assertTrue(value instanceof String);
            Assertions.assertEquals("my-value", value);
        }
    }
}
Assertions.assertTrue(hasMyHeader);

访问anything接口时会把请求头的信息返回回来,说明拦截器执行成功了。

自定义重试器

OpenFeign默认的重试器是feign.Retryer.Default,共重试5次,每次间隔步长为1.5的(重试次数-1)次幂,间隔最大1秒。

如果想要自定义重试逻辑,我们可以自己实现。

public class MyRetryer implements Retryer {
    int attempt = 0;

    @Override
    public void continueOrPropagate(RetryableException e) {
        if (attempt++ >= 3) {
            throw e;
        }
        System.out.println("重试第:" + attempt + "次");
        try {
            TimeUnit.MILLISECONDS.sleep(100);
        } catch (InterruptedException ex) {
             Thread.currentThread().interrupt();
            throw new RuntimeException(ex);
        }
    }

    @Override
    public Retryer clone() {
        return new MyRetryer();
    }
}

然后通过retryer方法指定。

final Client client = Feign.builder()
        .logLevel(Level.FULL)
        .decoder(new JacksonDecoder())
        .encoder(new JacksonEncoder())
        .retryer(new MyRetryer())
        // 默认是 feign.Retryer.Default
        // 可以指定不重试 feign.Retryer.NEVER_RETRY
        .target(Client.class, "https://httpbin.abc");
Assertions.assertThrowsExactly(RetryableException.class, () -> client.codes("500"));

需要强调一下,只有访问HTTP时出现了IO异常才会重试,如果接口正常返回了,只不过不是200之类的正常响应,不会进重试逻辑。示例中把域名写错了,属于IO异常,会重试3次。

如果不想重试,可以指定为feign.Retryer.NEVER_RETRY。

使用Spring的注解

OpenFeign接口定义使用的是URL模板,具体协议可以参https://www.rfc-editor.org/rfc/rfc6570.html。

大部分人对这个协议有些陌生,但是对Spring的注解比较属性,所以OpenFeign也贴心的提供了Spring契约适配。

首先,引入spring模块依赖:

<dependency>
    <groupId>io.github.openfeign</groupId>
    <artifactId>feign-spring</artifactId>
</dependency>

这种方式引入的是Spring 6.x,如果想要使用Spring 4.x,可以引入feign-spring4模块。

然后使用Spring MVC注解定义接口:

@PostMapping(value = "/anything/{anything}",
        produces = "application/json", consumes = "application/json")
Map<String, Object> anythingJsonSpring(@PathVariable("anything") String anything,
        @RequestParam("p1") String p1,
        @RequestParam("p2") String p2,
        @RequestBody Map<String, Object> requestBody);

构建客户端的时候,需要使用contract指定是Spring的契约:

final SpringClient client = Feign.builder()
        .logLevel(Level.FULL)
        .decoder(new JacksonDecoder())
        .encoder(new JacksonEncoder())
        .contract(new SpringContract())
        .target(SpringClient.class, "https://httpbin.org");
final Map<String, Object> requestBody = Map.of("k1", "value1", "k2", "value2");
final Map<String, Object> anythingResult = client.anythingJsonSpring("testJson",
        "param1", "param2", requestBody);

这样就可以正常运行了。

文末总结

本文介绍了OpenFeign的基本使用方法,包括如何引入依赖、如何定义接口、如何构建客户端、如何自定义拦截器、重试器等。

OpenFeign的入门篇结束,后续我们将介绍OpenFeign的更多功能,比如错误处理器、熔断器、监控等。

图片图片

责任编辑:武晓燕 来源: 看山的小屋
相关推荐

2022-06-06 08:31:05

Base64编码Base58

2019-12-30 09:51:35

Word设计模式软件

2023-11-02 10:22:29

gRPC后端通信

2023-08-30 07:39:16

PawSQL数据库

2020-12-24 18:44:34

RSA加密算法

2022-09-07 09:01:14

JS操作符运算符

2024-03-26 10:10:45

JavaScript操作符操作表达式

2021-01-04 14:16:01

小程序地图腾讯

2023-12-12 08:41:01

2024-04-30 09:02:48

2023-04-26 10:21:04

2023-12-20 08:23:53

NIO组件非阻塞

2024-04-07 00:00:00

ESlint命令变量

2024-05-28 09:12:10

2021-07-29 06:55:03

Spring@AutowriedbyType注入

2018-04-24 15:40:39

无线路由器无线网络上网

2019-06-14 15:36:13

Windows 10安全PC

2021-02-02 08:21:28

网络面试通信

2020-10-28 11:20:55

vue项目技

2022-03-10 08:25:27

JavaScrip变量作用域
点赞
收藏

51CTO技术栈公众号