一、前言
前面已经介绍完了feign的整个原生框架的代码, 本节将给大家带来原生feign框架的使用细节
二、案例
约定
1.先定义一个数据载体
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Person {
private String name;
private Integer age;
private Integer gender;
private LocalDate birthday;
}
2.引入编解码器和okhttp
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-okhttp</artifactId>
<version>13.3</version>
</dependency>
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-jackson</artifactId>
<version>13.3</version>
</dependency>
1.@Param注解
// controller
@PostMapping("/paramTest/path")
public Person paramTest(@RequestParam("name") String name, @RequestBody Person person,
@RequestHeader Map<String, Object> headers) {
System.out.println("uncleqiao name:" + name);
System.out.println("uncleqiao 收到body:" + person);
person.setName("小杜同学");
person.setGender(0);
return person;
}
// feign接口
@RequestLine("POST /paramTest/{path}?name={name}")
@Headers({"Content-Type:application/json", "Authorization:{auth}"})
Person paramTest(@Param("path") String path, @Param String name, @Param("auth") String authorization, @Param Integer age, @Param LocalDate birthday);
// 测试类
@Test
void paramTest() {
com.fasterxml.jackson.databind.Module javaTimeModule = new JavaTimeModule();
List<Module> modules = List.of(javaTimeModule);
FeignDemoInterface feignDemoInterface = Feign
.builder()
.encoder(new JacksonEncoder(modules))
.decoder(new JacksonDecoder(modules))
.target(FeignDemoInterface.class, "http://localhost:8080");
Person person = feignDemoInterface.paramTest("path", "uncleqiao", "abc123", 18, LocalDate.now());
System.out.println(person);
}
// 服务端打印
uncleqiao name:uncleqiao
uncleqiao 收到body:Person(name=null, age=18, gender=null, birthday=2024-12-04)
请求头来了:{authorization=abc123, content-type=application/json, accept=*/*, user-agent=Java/17.0.7, host=localhost:8080, connection=keep-alive, content-length=48}
// 客户端打印
Person(name=小杜同学, age=18, gender=0, birthday=2024-12-04)
小结
- 测试了@Param参数替换占位符的三个场景,
/paramTest/{path}
, 请求行上- url参数上
name={name}
, 会被@RequestParam
注解接收 - 请求头上
Authorization:{auth}
, 会被@RequestHeader
注解接收到
@Param Integer age, @Param LocalDate birthday
这两个参数由于不会替换占位符, 会被JacksonEncoder
编码器写入到body中(请求体), 可以被@RequestBody
注解接收到
2.使用Expander
定制param
// controller
@PostMapping("paramExpanderTest")
public void paramExpanderTest(@RequestBody Person person) {
System.out.println("person:" + person);
}
// 自定义一个Expander
public class ExpanderTest implements Param.Expander {
@Override
public String expand(Object value) {
System.out.println("原值为:" + value);
return "小乔同学";
}
}
// feign接口
@RequestLine("POST /paramExpanderTest")
@Headers("Content-Type:application/json")
Person paramExpanderTest(@Param(expander = ExpanderTest.class) String name);
// 测试
@Test
void paramExpanderTest() {
com.fasterxml.jackson.databind.Module javaTimeModule = new JavaTimeModule();
List<Module> modules = List.of(javaTimeModule);
FeignDemoInterface feignDemoInterface = Feign
.builder()
.encoder(new JacksonEncoder(modules))
.decoder(new JacksonDecoder(modules))
.target(FeignDemoInterface.class, "http://localhost:8080");
feignDemoInterface.paramExpanderTest("小杜同学");
}
// 结果
// controller
person:Person(name=小乔同学, age=null, gender=null, birthday=null)
// ExpanderTest
原值为:小杜同学
注意: 要是即设置了请求拦截器也设置了Expander来修改某个值, 那么拦截器的优先级会更高, 因为是先解析参数再执行的拦截器
3.@Headers与@HeaderMap
headers注解可以添加到接口、方法上, HeaderMap注解可以用来修饰参数, 都是用来添加请求头参数的
// controller
@GetMapping("/headerTest")
public void header(@RequestHeader HttpHeaders headers) {
System.out.println("headerMap:" + headers);
}
// feign接口
@Headers({"Content-Type:application/json", "customHeader:class"})
public interface FeignDemoInterface {
@RequestLine("GET /headerTest")
@Headers({"accept:application/json", "customHeader:method"})
void headerTest(@HeaderMap Map<String, Object> headerMap);
}
// 测试
@Test
void headerTest() {
com.fasterxml.jackson.databind.Module javaTimeModule = new JavaTimeModule();
List<Module> modules = List.of(javaTimeModule);
FeignDemoInterface feignDemoInterface = Feign
.builder()
.encoder(new JacksonEncoder(modules))
.decoder(new JacksonDecoder(modules))
.target(FeignDemoInterface.class, "http://localhost:8080");
Map<String, Object> heardMap = Map.of("Authorization", "123mtr", "customHeader", "param");
feignDemoInterface.headerTest(heardMap);
}
// 服务端打印
headerMap:[accept:"application/json", authorization:"123mtr", content-type:"application/json", customheader:"class, method", "param", user-agent:"Java/17.0.7", host:"localhost:8080", connection:"keep-alive"]
小结
- 这里测试了
@Headers
放在接口, 方法上, 并且@HeaderMap
放在参数上的场景 - 细心的朋友肯定发现了我这里用的
@RequestHeader HttpHeaders
接收请求头而不是用@RequestHeader Map<String, Object> headers
的形式接收; 我们看到**customheader:“class, method”, “param”**是返回了两个值, 而不是一个或者三个 - 当使用
HttpHeaders
时, 也可以使用headers.getValuesAsList("customHeader")
获取请求头, 返回的将是平铺的list - 原因是feign在添加请求头时, class和method的请求头合并在一起, 而参数中的作为它们下级元素组装在一起; 使用
@HeaderMap
时, 只能获取最简单的key,value格式, 嵌套的处理不了, 所以这里推荐大家使用@RequestHeader HttpHeaders headers
方法获取请求头
注意: 默认情况下, @Param参数是用来替换占位符的, 如果某个@Param参数不能用来替换占位符, 它们会被组装成map, 但是默认的编码器只能对string和byte[]进行编码, 所以这种情况将会报错, 当设置了可以解析map的编码器, 例如jackson、fastjson等, 就会将此时的map编码后设置成请求体body的参数进行传递
4.URI参数
使用uri参数可以设置请求的相对路径
// controller
@GetMapping("/uriTest/relativePath")
public void uriTest(@RequestParam("name") String name, @RequestParam("age") Integer age, @RequestParam("birthday") String birthday) {
System.out.println("name:" + name);
System.out.println("age:" + age);
System.out.println("birthday:" + birthday);
}
// feign接口
@RequestLine("GET /relativePath?name=uncleqiao")
void uriTest(URI uri);
// 测试
@Test
void uriTest() {
FeignDemoInterface feignDemoInterface = Feign
.builder()
.target(FeignDemoInterface.class, "http://localhost:8080");
feignDemoInterface.uriTest(URI.create("http://localhost:8080/uriTest?age=18&birthday=" + LocalDate.now()));
}
// 服务端打印
name:uncleqiao
age:18
birthday:2024-12-04
参数解析顺序如下
-
RequestLine 取了请求行路径 ->
uriTemplate
-
uri -> 取值: target , fragment, 追加参数
-
feign.target -> 取值: target , fragment, 追加参数
-
最终请求地址: target + uri + 参数 + fragment
注意: 如果设置了URI参数, 那么feign.target设置的路径将会失效
5.@QueryMap
1.使用map做参数
// controller借用uriTest中的
// feign接口
@RequestLine("GET /uriTest/relativePath")
void queryMapTest(@QueryMap Map<String, Object> param);
// 测试
@Test
void queryMapTest() {
FeignDemoInterface feignDemoInterface = Feign
.builder()
.target(FeignDemoInterface.class, "http://localhost:8080");
Map<String, Object> heardMap = Map.of("name", "uncleqiao", "age", 18, "birthday", "2024-08-08");
feignDemoInterface.queryMapTest(heardMap);
}
// 结果
// 服务端打印
name:uncleqiao
age:18
birthday:2024-08-08
说明: @QueryMap
注解标识的如果是map参数, 那么key只能是字符串格式
2.使用javaBean做参数
// feign接口
@RequestLine("GET /uriTest/relativePath")
void queryMapTest2(@QueryMap Person person);
// 测试
@Test
void queryMapTest2() {
FeignDemoInterface feignDemoInterface = Feign
.builder()
.target(FeignDemoInterface.class, "http://localhost:8080");
Person person = new Person("uncleqiao", 18, 1, LocalDate.now());
feignDemoInterface.queryMapTest2(person);
}
说明:
@QueryMap
注解标识的如果是对象参数, 那么对象中所有满足javaBean规则的get方法都会生成一个参数添加到请求上- 默认使用的是
FieldQueryMapEncoder
解析javaBean对象
6.上传文件
// controlelr
@PostMapping("/upload")
public void uploadTest(@RequestBody byte[] file, @RequestParam("filename") String filename) {
System.out.println("filename:" + filename);
System.out.println("file:" + file);
// 目标文件路径
// String filePath = "output.txt";
// 将 byte[] 数据写入文件
// try (FileOutputStream fos = new FileOutputStream(filePath)) {
// fos.write(file);
// System.out.println("File saved successfully at: " + filePath);
// } catch (IOException e) {
// e.printStackTrace();
// }
}
// feign接口
@RequestLine("POST /upload?filename={filename}")
void uploadTest(byte[] file, @Param String filename);
// 测试
@Test
void uploadFileTest() throws Exception {
FeignDemoInterface feignDemoInterface = Feign
.builder()
.target(FeignDemoInterface.class, "http://localhost:8080");
FileInputStream fileInputStream = new FileInputStream("/Users/uncleqiao/Documents/upload_file.txt");
byte[] bodyData = Util.toByteArray(fileInputStream);
feignDemoInterface.uploadTest(bodyData, "upload_file.txt");
fileInputStream.close();
}
// 结果
// 服务端打印
filename:upload_file.txt
file:[B@3eb6e6ef
7.异步请求
// controlelr
@GetMapping("/asyncTest")
public Person asyncTest(@RequestParam("name") String name, @RequestParam("age") Integer age) {
System.out.println("name:" + name);
System.out.println("age:" + age);
Person person = new Person();
person.setName("小杜同学");
person.setAge(16);
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
}
return person;
}
// feign接口
@RequestLine("GET /asyncTest")
CompletableFuture<Person> asyncTest(@QueryMap Person person);
// 测试
@Test
void asyncTest() throws Exception {
FeignDemoInterface feignDemoInterface = AsyncFeign
.builder()
.decoder(new JacksonDecoder())
.target(FeignDemoInterface.class, "http://localhost:8080");
Person person = new Person("小乔同学", 18, 1, LocalDate.now());
CompletableFuture<Person> personCompletableFuture = feignDemoInterface.asyncTest(person);
System.out.println("当前时间:" + LocalDateTime.now());
Person person1 = personCompletableFuture.get();
System.out.println("当前时间:" + LocalDateTime.now());
System.out.println(person1);
}
// 结果
// 服务端打印
name:小乔同学
age:18
// 客户端打印
当前时间:2024-12-04T22:27:07.219589
当前时间:2024-12-04T22:27:12.272539
Person(name=小杜同学, age=16, gender=null, birthday=null)
到此, 所有的有关feign的原生功能全部介绍完毕.