Feign 基本使用

前言

​ 在开发 Spring Cloud 微服务的时候,我们知道,服务之间都是以 HTTP 接口的形式对外提供服务的,因此消费者在进行调用的时候,底层就是通过 HTTP Client 的这种方式进行访问。

​ 当然我们可以使用JDK原生的 URLConnection、Apache 的 HTTP Client、Netty 异步 Http Client,Spring 的 RestTemplate 去实现服务间的调用。

​ 但是最方便、最优雅的方式是通过 Spring Cloud Open Feign 进行服务间的调用 Spring Cloud 对 Feign 进行了增强,使 Feign 支持 Spring Mvc 的注解,并整合了 Ribbon 等,从而让 Feign 的使用更加方便。

Feign 概述

什么是 Feign

Feign 是一个声明式的 Web Service 客户端。它的出现使开发 Web Service 客户端变得很简单。使用 Feign 只需要创建一个接口加上对应的注解,比如:@FeignClient 注解。 Feign 有可插拔的注解,包括 Feign 注解和 AX-RS 注解。Feign 也支持编码器和解码器,Spring Cloud Open Feign 对 Feign 进行增强支持 Spring Mvc 注解,可以像 Spring Web 一样使用 HttpMessageConverters 等。

Feign 是一种声明式、模板化的 HTTP 客户端。在 Spring Cloud 中使用 Feign,可以做到使用 HTTP 请求访问远程服务,就像调用本地方法一样的,开发者完全感知不到这是在调用远程方法,更感知不到在访问 HTTP 请求。

入门案例

此处以调用 Github API 查询服务为例。

引入依赖

1
2
3
4
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

启动类加入如下注解:

1
2
3
4
5
6
import org.springframework.cloud.openfeign.EnableFeignClients;

@SpringBootApplication
/** 开启 Feign 扫描支持 */
@EnableFeignClients
@ComponentScan({"cn.psvmc.zapicall.controller", "cn.psvmc.zapicall.feign"})

假如我们要访问

https://api.github.com/search/repositories?q=java

Feign接口编写

1
2
3
4
5
6
7
8
9
10
11
12
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;

/**
* 使用 Feign 访问 Github 查询 API
*/
@FeignClient(name = "github-client", url = "https://api.github.com")
public interface GitHubFeign {
@GetMapping("/search/repositories")
String searchRepo(@RequestParam("q") String q);
}

Controller

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import cn.psvmc.zapicall.feign.GitHubFeign;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.*;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;

@RestController
@RequestMapping(
value = "/github"

)
public class GithubController {
@Resource
private GitHubFeign gitHubFeign;

@RequestMapping(
value = "/search",
method = RequestMethod.GET,
produces = MediaType.APPLICATION_JSON_VALUE
)
String searchRepo(@RequestParam("q") String q, HttpServletResponse response) {
response.setHeader("token", "123456");
return gitHubFeign.searchRepo(q);
}
}

注意

设置响应头Content-Type要使用produces = MediaType.APPLICATION_JSON_VALUE

MediaType.APPLICATION_JSON_UTF8_VALUE 已被标记为不推荐使用,并在 Spring Framework 的最新版本中被废弃。相应的替代值是 MediaType.APPLICATION_JSON_VALUEMediaType.APPLICATION_JSON_VALUE 是一个常量,它表示 JSON 格式的媒体类型,并且默认使用 UTF-8 字符编码

代码中设置response.setHeader("Content-Type", "application/json");无效,会被覆盖掉。

我们就可以通过以下地址访问

http://localhost:8080/github/search?q=java

Get请求

映射属性

1
2
3
4
5
6
@FeignClient(name = "feignApiClient", url = "http://localhost:8080/user")
public interface FeignApiInteferce {
//一个个传参
@GetMapping("/post6")
String mGet(@RequestParam("name") String name, @RequestParam("sex") String sex);
}

映射对象

1
2
3
4
5
6
@FeignClient(name = "feignApiClient", url = "http://localhost:8080/user")
public interface FeignApiInteferce {
//对象传参用 `@SpringQueryMap` 注解
@GetMapping(value = "/post6")
String mGet(@SpringQueryMap User user);
}

Post请求

application/json

参数通过 @RequestBody标识
请求头Content-Type@Headers注解中指定

1
2
3
4
5
6
@FeignClient(name = "feignApiClient", url = "http://localhost:8080/user")
public interface FeignApiInteferce {
@Headers({"Content-Type: application/json"})
@PostMapping("/post1")
String post1(@RequestBody User user);
}

对应的接口

1
2
3
4
@PostMapping("/post1")
public String post1(@RequestBody User user) throws JsonProcessingException {
return new JsonMapper().writeValueAsString(user);
}

application/x-www-form-urlencoded

1
2
3
4
5
6
@FeignClient(name = "feignApiClient", url = "http://localhost:8080/user")
public interface FeignApiInteferce {
@Headers({"Content-Type: application/x-www-form-urlencoded;charset=UTF-8"})
@PostMapping("/post5")
String post5(@RequestBody MultiValueMap<String, Object> user);
}

调用

1
2
3
4
5
6
//使用MultiValueMap的实现类LinkedMultiValueMap
//用add方法添加参数
LinkedMultiValueMap<String, Object> multiValueMap = new LinkedMultiValueMap<>();
multiValueMap.add("name","臭小子");
multiValueMap.add("sex", "男");
feignApiInteferce.post5(multiValueMap);

对应的接口

1
2
3
4
@PostMapping("/post5")
public String post5(User user) throws JsonProcessingException {
return new JsonMapper().writeValueAsString(user);
}

multipart/form-data

1
2
3
4
5
@FeignClient
public interface UploadClient {
@PostMapping(value = "/uploadFile", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
String uploadFile(@RequestPart(value = "file") MultipartFile file, @RequestParam String uploadPath);
}

设置header

有时候接口验签,header中需要追加一些参数;通过@RequestHeader注解实现
例如header中增加参数 authorization

1
2
3
4
5
6
7
@FeignClient(name = "feignApiClient", url = "http://localhost:8080/user")
public interface FeignApiInteferce {
@Headers({"Content-Type:application/json","Authorization:{authorization}"})
@PostMapping("/post2")
String post2(@RequestHeader(name = "authorization")String authorization,
@RequestBody User user);
}

对应的接口

1
2
3
4
5
6
//header中增加参数authorization
@PostMapping("/post2")
public String post2(@RequestBody User user, HttpServletRequest request) throws JsonProcessingException {
String authorization = request.getHeader("authorization");
return new JsonMapper().writeValueAsString(user)+", authorization=" + authorization;
}

URL后添加参数

除了放到body中的参数,还能直接在url后面追加参数,@RequestParam注解
例如增加参数title http://xxxxxxxx?title=xxxx

1
2
3
4
5
6
7
8
@FeignClient(name = "feignApiClient", url = "http://localhost:8080/user")
public interface FeignApiInteferce {
@Headers({"Content-Type:application/json","Authorization:{authorization}"})
@PostMapping("/post3")
String post3(@RequestHeader(name = "authorization") String authorization,
@RequestParam("title") String title,
@RequestBody User user);
}

URL中添加参数

1
2
3
4
5
6
7
8
9
@FeignClient(name = "feignApiClient", url = "http://localhost:8080/user")
public interface FeignApiInteferce {
@Headers({"Content-Type:application/json","Authorization:{authorization}"})
@PostMapping("/post3/{userId}?title={title}")
String post4(@RequestHeader(name = "authorization")String authorization,
@PathVariable("userId") String userId,
@PathVariable("title") String title,
@RequestBody User user);
}

禁用SSL认证

如果请求报错

unable to find valid certification path to requested target

这是请求了https的地址,但是没有设置证书,我们可以关闭证书验证。

我们添加依赖

1
2
3
4
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

这时候添加禁用SSL认证的配置

application.properties

1
feign.httpclient.disableSslValidation=true

但是这个时候,配置并不生效,还要添加

1
2
3
4
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-httpclient</artifactId>
</dependency>