API调用利器-feign
http
调用的痛点
在平常开发中我们经常需要调用第三方API,当数量不多的时候写起来无所谓,但是如果项目中调用的API数量太多的情况下,简直就是噩梦。对于这部分代码。做的事情基本上都是构建请求,然后发送请求,请求响应成功之后再将结果转化成对象。而这里我们用来发起http
请求的客户端有很多选择,例如JDK自带的HttpURLConnection
,Apache的HttpClient
还有后起之秀OkHttp
。对于这些不同的客户端他们之间的代码并不兼容,如果换了客户端意味着你的代码也得重新写。
Retrofit
如果你之前做过安卓开发大概率应该知道Retrofit
,它与OkHttp
都是square
公司开发的项目。Retrofit
通过使用Interface+注解
的方式可以很轻松的完成一次http请求。例如下面示例所示:
public interface GitHubService {
@GET("users/{user}/repos")
Call<List<Repo>> listRepos(@Path("user") String user);
}
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://api.github.com/")
.build();
GitHubService service = retrofit.create(GitHubService.class);
当然实际发起http请求的并不是Retrofit
而是OkHttp
,这也意味者它并没有办法兼容其他HC客户端。当然如果你对底层的HC客户端并没有特殊要求,你完全可以使用Retrofit+OkHttp
这个组合。
具体项目地址可以自行参考:https://github.com/square/retrofit
Open Feign
从前面我们了解到,如果我们需要一种像Retrofit
一样简单同时又能支持多种不同HC客户端的产品,那应该选啥呢?当然如果你使用过SpringCloud
的话,你肯定使用过Feign
,即使你没使用过那也听说过。那到底什么是Feign呢?Github
中的介绍为:
Feign is a Java to HTTP client binder inspired by Retrofit, JAXRS-2.0, and WebSocket. Feign’s first goal was reducing the complexity of binding Denominator uniformly to HTTP APIs regardless of ReSTfulness.
从介绍中也可以看出,它就是用来降低http请求的复杂性的。

上图是github中关于Feign相关特性的介绍,通过上图可以看出Feign囊括了http请求的方方面面,例如HC客户端我们可以选的就有Apache HTTP
、OKHttp
、Google HTTP
等等,例如还支持各种解码和编码相关内容。
常用场景示例
关于如何使用,github中已经给出了相关示例,下面我们就常见情况给出相关示例。
GET请求
通过用户ID获取用户详情
@GetMapping("detail")
public BaseRep<User> getUserDetail(@RequestParam("id") Integer userId) {
return BaseRep.ok(new User(userId, "小明"));
}
使用Feign我们编写的代码如下所示:
//定义接口
public interface UserApiService {
@RequestLine("GET /user/detail?id={id}")
BaseRep<User> queryUserById(@Param("id") Integer id);
}
//测试示例
@Slf4j
class UserApiServiceTest {
@Test
void queryUserById() {
UserApiService userApiService = Feign.builder()
.decoder(new GsonDecoder()) //配置解码器
.target(UserApiService.class,"http://127.0.0.1:8080");
BaseRep<User> rep = userApiService.queryUserById(1);
log.info(rep.toString());
}
}
默认情况下接口的返回值只支持String
、byte[]
、void
,我们这里可以直接返回BaseRep<User>
是因为我们配置了解码器GsonDecoder
,当然我们同样还可以不使用Gson
使用Jackson
这都是可行的,我们只需要引入相关依赖即可。我这里使用的是Gson
,所以除了需要引入feign-core
核心包之外还需要引入feign-gson
。
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-core</artifactId>
<version>11.8</version>
</dependency>
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-gson</artifactId>
<version>11.8</version>
</dependency>
Expander
在上面的示例中我们使用GET请求中的参数通过@Param
解析到模板中没有问题,但是如果我们的请求参数是日期时,那它会解析成什么样式提交到服务器呢?
接口定义如下:
@GetMapping("expander")
public void expander(String date){
log.info(date);
}
Feign方法定义如下:
@RequestLine("GET /user/expander?date={date}")
void expanderTest(@Param(value = "date")Date date);
请求示例如下:
@Test
void testExpander(){
UserApiService userApiService = Feign.builder()
.logger(new Slf4jLogger())
.logLevel(Logger.Level.FULL)
.decoder(new GsonDecoder())
.target(UserApiService.class,"http://127.0.0.1:8080");
userApiService.expanderTest(new Date());
}
发起请求,最后服务器上收到的日期为Tue Mar 29 15:55:10 CST 2022
,但是实际上服务器上希望收到yyyy-MM-dd
格式的字符串,这是后该怎么办呢?打开@Param
注解源码可以发现,它内部有一个接口Expander
可以用来实现自定义格式。
public @interface Param {
String value() default "";
Class<? extends Expander> expander() default ToStringExpander.class;
boolean encoded() default false;
interface Expander {
String expand(Object value);
}
final class ToStringExpander implements Expander {
@Override
public String expand(Object value) {
return value.toString();
}
}
}
从源码可以看出,默认情况下指定的为ToStringExpander
,它是实现是直接调用对象上的toString()
方法。我们自定义一个DateToStringExpander
,使用它来完成日期的格式化。
public class DateToStringExpander implements Param.Expander {
@Override
public String expand(Object value) {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
return sdf.format(value);
}
}
在Feign的接口方法上我们稍作修改:
@RequestLine("GET /user/expander?date={date}")
void expanderTest(@Param(value = "date",expander = DateToStringExpander.class)Date date);
打印日志
通常在调试过程中我们需要打印出http请求和响应的日志,这样有利于我们在开发过程中排除错误便于调试。对于这种要求,我们只需要在创建调用实例时增加配置日志即可。不过在这之前我们需要先添加日志相关依赖,依赖如下:
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-slf4j</artifactId>
<version>11.8</version>
</dependency>
然后在配置的客户端中添加日志相关配置如下:
void queryUserById() {
UserApiService userApiService = Feign.builder()
.logger(new Slf4jLogger()) //添加日志
.logLevel(Logger.Level.FULL) //设置日志打印等级
.decoder(new GsonDecoder())
.target(UserApiService.class,"http://127.0.0.1:8080");
BaseRep<User> rep = userApiService.queryUserById(1);
log.info(rep.toString());
}
@QueryMap 拯救参数太多
前面介绍的参数只有一个,如果参数太多我们一个个定义会让方法的参数列表过长,这时候我们可以将参数封装成Map
,然后通过@QueryMap
来实现自动拼装参数。例如Controller
接口如下所示:
@GetMapping("query_by_id_name")
public BaseRep<User> queryByIdAndName(@RequestParam("id") Integer id, @RequestParam("name") String name) {
return BaseRep.ok(new User(id, name));
}
Feign接口方法定义如下:
@RequestLine("GET /user/query_by_id_name")
BaseRep<User> queryUserByIdAndNameWithMap(@QueryMap Map<String,Object> map);
请求示例:
void queryUserByIdAndNameWithMap(){
UserApiService userApiService = Feign.builder()
.logger(new Slf4jLogger())
.logLevel(Logger.Level.FULL)
.decoder(new GsonDecoder())
.target(UserApiService.class,"http://127.0.0.1:8080");
Map<String,String> map = new HashMap<>();
map.put("id","1");
map.put("name","小黑");
BaseRep<User> rep = userApiService.queryUserByIdAndNameWithMap(map);
log.info(rep.toString());
}
我们只需要使用@QueryMap
注解即可让Feign自动的将Bean解析成请求参数。同样@QueryMap
还支持POJO对象,例如我们将上面的请求体封装成对象UserQueryByIdAndNameReq
,定义如下:
@Data
@AllArgsConstructor
@NoArgsConstructor
public class UserQueryByIdAndNameReq {
private Integer id;
private String name;
}
Feign接口方法定义如下:
@RequestLine("GET /user/query_by_id_name")
BaseRep<User> queryUserByIdAndName(@QueryMap UserQueryByIdAndNameReq req);
测试示例代码如下:
@Test
void queryUserByIdAndName(){
UserApiService userApiService = Feign.builder()
.logger(new Slf4jLogger())
.logLevel(Logger.Level.FULL)
.decoder(new GsonDecoder())
.target(UserApiService.class,"http://127.0.0.1:8080");
UserQueryByIdAndNameReq req = new UserQueryByIdAndNameReq(1, "小黑");
BaseRep<User> rep = userApiService.queryUserByIdAndName(req);
log.info(rep.toString());
}
之所以QueryMap
可以将POJO映射成请求参数,是因为Feign.Builder
Z对象在构建时默认会设置FieldQueryMapEncoder
,它通过反射的方式去将POJO映射成请求参数。
POST请求
对于POST请求,同样Feign
能很好的完成。例如Controller中创建User定义如下:
@PostMapping("create_json")
public BaseRep<User> createUserByJson(@RequestBody User user) {
return BaseRep.ok(user);
}
Feign接口方法定义如下:
@RequestLine("POST /user/create_json")
@Headers({"Content-Type: application/json"})
BaseRep<User> createUserByJson(User user);
测试示例代码如下:
void createUserByJson(){
UserApiService userApiService = Feign.builder()
.logger(new Slf4jLogger())
.logLevel(Logger.Level.FULL)
.encoder(new FormEncoder(new GsonEncoder()))
.decoder(new GsonDecoder())
.target(UserApiService.class, "http://127.0.0.1:8080");
User user = new User(1, "小华");
BaseRep<User> rep = userApiService.createUserByJson(user);
log.info(rep.toString());
}
这里面构建的客户端实例与之前不同的是增加了Encoder
,它的作用是将POJO对象转化成JSON字符串。另外我们还需要设置请求头为Content-Type: application/json
。在实际开发中我们还会使用application/x-www-form-urlencoded
方式提交表单,对于这种方式同样Feign
也能解决。在这里我们我需要先增引入一个FormEncoder
,它的作用是将对象转化成表单形式的参数。
<dependency>
<groupId>io.github.openfeign.form</groupId>
<artifactId>feign-form</artifactId>
<version>3.8.0</version>
</dependency>
Controller接口定义如下:
@PostMapping("create_form")
public BaseRep<User> createByFrom(@RequestParam("userId") Integer id, @RequestParam("userName") String name) {
return BaseRep.ok(new User(id, name));
}
Feign接口定义如下:
@RequestLine("POST /user/create_form")
@Headers({"Content-Type: application/x-www-form-urlencoded"})
BaseRep<User> createUserByForm(User user);
测试示例如下:
void createUserByForm(){
UserApiService userApiService = Feign.builder()
.logger(new Slf4jLogger())
.logLevel(Logger.Level.FULL)
.encoder(new FormEncoder(new GsonEncoder()))
.decoder(new GsonDecoder())
.target(UserApiService.class, "http://127.0.0.1:8080");
User user = new User(1, "大明");
BaseRep<User> rep = userApiService.createUserByForm(user);
log.info(rep.toString());
}
自定义HC客户端
默认情况下,Feign
使用的客户端为JDK自带的HttpURLConnection
,但是在性能上可能并不能满足我们的要求。下面我们使用OkHttp
替换默认的HttpURLConnection
。我们首先添加feign-okhttp
依赖,该依赖是用来整合Feign
和OkHttp
的。当然如果你对自己技术有自信,也可以自己来实现相关代码,只需要自己实现Client
接口即可。
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-okhttp</artifactId>
<version>11.8</version>
</dependency>
修改之前的实例代码如下:
void createUserByFormWithOkHttp(){
UserApiService userApiService = Feign.builder()
.client(new OkHttpClient())
.logger(new Slf4jLogger())
.logLevel(Logger.Level.FULL)
.encoder(new FormEncoder(new GsonEncoder()))
.decoder(new GsonDecoder())
.target(UserApiService.class, "http://127.0.0.1:8080");
User user = new User(1, "大明");
BaseRep<User> rep = userApiService.createUserByForm(user);
log.info(rep.toString());
}
需要注意这里面的OkHttpClient``是Feign提供的,全类名为
feign.okhttp.OkHttpClient。只需要简单配置,我们就将原本的
HttpURLConnection替换成了
OkHttp“`。
总结
总体来说Feign
的功能很强大,它大大简化了我们调用http请求的各种繁杂操作,同时它还支持多种底层HC客户端、不同的解码编码方式、监控指标等特性。这也正是它在SpringCloud
中我们会使用它来实现服务的调用。虽然在大家可能在SpringCloud
中使用的较多,但实际上它也可以用在我们平常的开发项目中,毕竟提高效率的事谁不想呢。
资源
本文实例代码链接:https://gitee.com/zengchao_workspace/feign-demo
Feign Github地址:https://github.com/OpenFeign/feign
原文始发于微信公众号(一只菜鸟程序员):API调用利器-feign
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/72909.html