@RequestLine 从字面理解就是“请求行”,请求行是 HTTP 请求消息的起始行,它包含了重要的请求相关信息,用于向服务器表明客户端的请求意图和相关细节。
一个典型的 HTTP 请求行格式如下:请求方法 请求URL HTTP协议版本
@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 属性是 @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 属性是一个布尔值,默认值为 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 的,原因:
什么是 Expression?简单的说就是这种格式的“{path}”表达式,打断点查看,如下图:
源码见 feign.template.Template.java。
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 地址解码操作,如下图: