Netflix Feign @RequestLine 注解

@RequestLine 从字面理解就是“请求行”,请求行是 HTTP 请求消息的起始行,它包含了重要的请求相关信息,用于向服务器表明客户端的请求意图和相关细节。

一个典型的 HTTP 请求行格式如下:请求方法 请求URL HTTP协议版本

106fe6db88ff6bd4a80f054c27a0325b_1730080348600-621585fe-72bb-4a62-9cb6-c1c4084d94be.png

@RequestLine 是 Netflix Feign 中的一个注解,用于定义 HTTP 请求的细节。它允许你在接口方法上指定请求的类型(如 GET、POST、PUT 等)和请求路径,使得 Feign 客户端能够根据这些信息构建和发送 HTTP 请求到目标服务。

应用示例

假设我们有一个简单的 http://localhost:8090/simple/hello 服务,下面定义一个简单的 Feign 客户端来调用该接口,代码如下:

package com.hxstrive.demo_netflix_feign.feign;

import feign.Contract;
import feign.Feign;
import feign.RequestLine;
import feign.codec.Decoder;
import feign.codec.Encoder;

/**
 * Feign客户端
 * @author HuangXin
 */
public interface SimpleFeign {

    // 定义获取用户信息的方法
    @RequestLine("GET /simple/hello")
    public String get();

    // 创建Feign客户端实例的静态方法
    static SimpleFeign create() {
        return Feign.builder()
                .encoder(new Encoder.Default())
                .decoder(new Decoder.Default())
                .contract(new Contract.Default())
                .target(SimpleFeign.class, "http://localhost:8090/");
    }

}

上面示例,使用 create() 静态方法创建 SimpleFeign 客户端,该客户端绑定到 http://localhost:8090/ 基础地址上,并且配置了默认编码/解码器。然后,使用@RequestLine("GET /simple/hello") 注解修饰方法 get 表示发送一个 GET 请求到http://localhost:8090/的/simple/hello端点,期望获取一个字符串作为返回结果。

最后,在 Controller 中使用 Feign 客户端,代码如下:

@RestController
public class FeignController {

    @GetMapping("/demo1")
    public String demo1() {
        // 调用方法,发起 HTTP 请求
        return SimpleFeign.create().get();
    }

}

在上述例子中,我们根本不需要接触关于 HTTP 请求相关的库,利用 Feign 发起 HTTP 请求就像调用本地方法一样简单。

源码分析

我们先看看 @RequestLine 注解的源码:

package feign;

import java.lang.annotation.Retention;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

@java.lang.annotation.Target(METHOD)
@Retention(RUNTIME)
public @interface RequestLine {

  String value();

  boolean decodeSlash() default true;

  CollectionFormat collectionFormat() default CollectionFormat.EXPLODED;
}

从上面源码可知,@RequestLine 注解存在三个属性,分别是:

value

value 属性是 @RequestLine 注解中最关键的部分。它用于指定 HTTP 请求的方法和路径。例如,@RequestLine("GET /simple/hello"),这里的 value 值定义了一个 GET 请求,请求的路径是 /simple/hello。这个路径可以是绝对路径(包含完整的域名和端口等信息),但更多情况下是相对于服务端(在构建 Feign 客户端中指定的,上述示例的 target(SimpleFeign.class, "http://localhost:8090/") 方法)的某个根路径的相对路径。

Feign 使用这个属性来构建实际的 HTTP 请求。当调用被@RequestLine注解标记的方法时,Feign 会根据这个属性的值来确定要发送的请求的类型(如GET、POST等)和目标 URL。它是实现服务间 HTTP 通信的基础,使得客户端代码能够以简洁的方式定义对远程服务的请求。

decodeSlash

decodeSlash 属性是一个布尔值,默认值为 true。它主要用于控制是否对请求路径中的斜杠(/)进行解码。在 URL 中,斜杠是路径分隔符,但有时可能会被编码(例如,%2F是斜杠的编码形式)。当decodeSlash=true时,Feign 会自动将编码后的斜杠进行解码,以确保请求路径的正确性。

假设在一个文件下载的服务中,文件路径可能包含斜杠。如果文件路径在传递过程中被编码,而decodeSlash 设置为 true,Feign 会在构建请求时将其正确解码,使得请求能够准确地指向服务器上的目标文件。例如,对于请求路径 /files/%2Fsubfolder%2Ffile.txt,如果 decodeSlash=true,Feign 会将其解码为 /files/subfolder/file.txt 后再发送请求。

示例:

a、定义 Feign 客户端:

@RequestLine(value = "GET {path}", decodeSlash = true)
public String get3(@Param("path") String encodePath);

b、调用 Feign 客户端:

@GetMapping("/")
public String index() {
    String val = "";
    // 注意:%2F 解码后为 /
    // %2Fsimple%2Fhello 解码后为 /simple/hello
    val = SimpleFeign.create().get3("%2Fsimple%2Fhello");
    return val;
}

注意:不能直接这样写 @RequestLine(value = "GET %2Fsimple%2Fhello", decodeSlash = true),这不会自动解码 %2F 的,原因:

c0ccae9de5591af3bbbd3fd97d0f622c_1730085063647-88f3f93e-208d-4386-be7a-da58dc16e45f.png

0f3a5d76caea1aee1f32828c28e78c7e_1730085227694-e8a65d50-d369-4e6e-b982-d742b21ca645.png

什么是 Expression?简单的说就是这种格式的“{path}”表达式,打断点查看,如下图:

602f9d262cb44995d3f4d5d48ced0dfb_1730085671986-e8c8359c-0004-4494-9569-f5a0503425a1.png

源码见 feign.template.Template.java

collectionFormat

collectionFormat 属性用于指定集合类型参数在请求中的格式。它的默认值是CollectionFormat.EXPLODED。这个属性主要涉及到当请求包含数组或集合类型的参数时,如何将这些参数组织到请求中。例如,对于一个包含多个标签的查询参数,如 tags=["tag1","tag2","tag3"],collectionFormat 属性决定了这些标签在请求 URL 中的格式。

不同格式的说明(以查询参数为例):

  • CollectionFormat.EXPLODED格式(默认):在这种格式下,集合中的每个元素会作为单独的参数出现。例如,对于 GET /api/articles?tags=tag1&tags=tag2&tags=tag3 这样的请求,查询参数tags的集合值(["tag1","tag2","tag3"])是以每个元素单独作为一个参数的形式出现的。

  • CollectionFormat.CSV(Comma - Separated Values)格式:集合中的元素会以逗号分隔的形式作为一个参数的值。例如,GET /api/articles?tags=tag1,tag2,tag3。

  • CollectionFormat.PIPES 格式:元素以管道符(|)分隔,如 GET /api/articles?tags=tag1|tag2|tag3。

  • CollectionFormat.SSV(Space - Separated Values)格式:元素以空格分隔,如 GET /api/articles?tags=tag1 tag2 tag3。

示例:

a、服务定义,接收一个数组参数:

@GetMapping("/hello2")
public String hello2(@RequestParam("args") String[] args) {
    return "Hello World, args=" + String.join("、", args);
}

b、定义 Feign 客户端:

@RequestLine(value = "GET /simple/hello2?args={args}", collectionFormat = CollectionFormat.CSV)
public String get2(@Param("args") List<String> args);

c、 调用 Feign 客户端:

@GetMapping("/")
public String index() {
    String val = "";
    val = SimpleFeign.create().get2(Arrays.asList("one", "two", "three"));
    return val;
}

输出日志如下:

s.n.www.protocol.http.HttpURLConnection  : sun.net.www.MessageHeader@79b793345 pairs: {GET 
/simple/hello2?args=one%2Ctwo%2Cthree
 HTTP/1.1: null}{Accept: */*}{User-Agent: Java/17.0.10}{Host: localhost:8090}{Connection: keep-alive}

/simple/hello2?args=one%2Ctwo%2Cthree 进行 URL 地址解码操作,如下图:

8a9bea33cb544fb568f6dfac31ded95a_1730083791533-77034e76-0f76-47c6-a825-f09180804f43.png

说说我的看法
全部评论(
没有评论
关于
本网站属于个人的非赢利性网站,转载的文章遵循原作者的版权声明,如果原文没有版权声明,请来信告知:hxstrive@outlook.com
公众号