API调用利器-feign

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请求的复杂性的。

API调用利器-feign
feign

上图是github中关于Feign相关特性的介绍,通过上图可以看出Feign囊括了http请求的方方面面,例如HC客户端我们可以选的就有Apache HTTPOKHttpGoogle 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());
    }
}

默认情况下接口的返回值只支持Stringbyte[]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.BuilderZ对象在构建时默认会设置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依赖,该依赖是用来整合FeignOkHttp的。当然如果你对自己技术有自信,也可以自己来实现相关代码,只需要自己实现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

(0)
小半的头像小半

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!