zoukankan      html  css  js  c++  java
  • Java 开源项目 OpenFeign —— feign 的基本使用

    1. 前言

    最近公司的项目里使用到了 Feign 开源项目,这里作学习笔记

    2. Feign 架构(来自官方)

    feign 由大部分组成,由于刚开始接触 feign ,我们自然比较关注的 clientsencoders/decoders

    3.  代码测试

    3.1 官方教程

    接触一个项目最直接的方式就是从官方 Demo 开始,刚开始接触 feign 的童鞋可能会找不到官方教程的 GsonDecoder 源,它在 feign-gson 模块中,让我们引入 Maven 依赖

     1     <properties>
     2         <feign-version>9.5.0</feign-version>
     3     </properties>
     4 
     5     <dependencies>
     6         <dependency>
     7             <groupId>io.github.openfeign</groupId>
     8             <artifactId>feign-core</artifactId>
     9             <version>${feign-version}</version>
    10         </dependency>
    11 
    12         <dependency>
    13             <groupId>io.github.openfeign</groupId>
    14             <artifactId>feign-gson</artifactId>
    15             <version>${feign-version}</version>
    16         </dependency>
    17     </dependencies>

    接着是官方的Demo:

     1 public class SimpleGit {
     2     interface GitHub {
     3 
     4         @RequestLine("GET /repos/{owner}/{repo}/contributors")
     5         List<Contributor> contributors(@Param("owner") String owner, @Param("repo") String repo);
     6 
     7         @RequestLine("POST /repos/{owner}/{repo}/issues")
     8         void createIssue(Issue issue, @Param("owner") String owner, @Param("repo") String repo);
     9 
    10     }
    11 
    12     public static class Contributor {
    13         String login;
    14         int contributions;
    15     }
    16 
    17     public static class Issue {
    18         String title;
    19         String body;
    20         List<String> assignees;
    21         int milestone;
    22         List<String> labels;
    23     }
    24 
    25     public static void main(String[] args) {
    26         GitHub github = Feign.builder()
    27                 .decoder(new GsonDecoder())
    28                 .target(GitHub.class, "https://api.github.com");
    29 
    30         // Fetch and print a list of the contributors to this library.
    31         List<Contributor> contributors = github.contributors("OpenFeign", "feign");
    32         for (Contributor contributor : contributors) {
    33             System.out.println(contributor.login + " (" + contributor.contributions + ")");
    34         }
    35     }
    36 }

    从 Demo 可以看到,我们需要一个 decoder ,GsonDecoder#decode 方法,可以看到这是一个转换 Json 串到实体的方法,同时 还捕捉了 IOExeption , IOExeption 包装了 IOException(包括 HTTP 请求中的错误)。

    运行它,可以看到打印的结果:

    adriancole (358)
    velo (86)
    kdavisk6 (82)
    ...

    3.2 使用 Jackson 改写官方 Demo

    你可能会想使用 Jackson 作为 encoder/decoder 方法 ,官方也提供了 feign-json 模块,我们可以引入 Maven 依赖,或者手动编写一个,然后手动替换相应的语句。不过我们这里缺了一些内容,我们需要补上一些 Jackson 注解。不然会遇到 FeignException 错误(原因在于请求的返回不止两个字段) :

    1     @Data
    2     @JsonIgnoreProperties(ignoreUnknown = true)
    3     public static class Contributor {
    4         String login;
    5         int contributions;
    6     }

    4. 注解

    在官方的 Demo 中,我们看到了一个注解 RequestLine,除此,feign 还提供了其他注解完成一个 HTTP 请求中所需要的各种信息

    4.1 RequestLine

    用于填充 请求类别,请求路径http 版本( HTTP /1.1 )(METHOD)

    4.3 Body

    用于填充请求体(METHOD)

    4.3 Headers 

    用于填充请求头(METHOD, TYPE)

    4.3 HeaderMap

    用于填充请求头(PARAMETER)

    4.2 Param

    用于填充 Body/Headers/RequestLine 上的占位符(PARAMETER, FIELD, METHOD)

    4.3 QueryMap

    用于填充请求参数 (PARAMETER)

    5. 两个完整的注解例子

    5.1 使用 Jackson

    让我们结合 SpringMVC 写一个完整的例子,这样可以让我们更深的了解它的作用,我们需要定义一个词典,一个控制器,一个供 Feign 调用的接口,还有一个测试类:

    5.1.1 词典类

     1 /**
     2  * @author pancc
     3  * @version 1.0
     4  */
     5 @Data
     6 @Accessors(chain = true)
     7 public class Dict {
     8 
     9     private int size;
    10     private List<String> words;
    11 
    12     public int getSize() {
    13         return words == null ? 0 : words.size();
    14     }
    15 
    16     public static Dict Instance;
    17 
    18     static {
    19         String[] names = {
    20                 "auto", "autoCycle", "autoMan",
    21                 "bicycle", "bike",
    22                 "cream", "clean", "cycle",
    23                 "day", "doom", "dying",
    24                 "envy", "em..", "eye"
    25         };
    26         Instance = new Dict().setWords(Lists.newArrayList(names));
    27     }
    28 }

    5.1.2 控制器

     1 /**
     2  * @author pancc
     3  * @version 1.0
     4  */
     5 @RestController
     6 @RequestMapping("dic")
     7 public class FeignController {
     8     private static final Dict dict = Dict.Instance;
     9 
    10 
    11     @GetMapping("details")
    12     public Dict details() {
    13         return dict;
    14     }
    15 
    16 
    17     @GetMapping("startsWith/{query}")
    18     public List<String> startsWith(@PathVariable("query") String query) {
    19         return dict.getWords().stream().filter(s -> s.startsWith(query)).collect(Collectors.toList());
    20     }
    21 
    22     @GetMapping("query")
    23     public List<String> startAndEnd(@RequestParam("startsWith") String start, @RequestParam("endsWith") String end) {
    24         return dict.getWords().stream().filter(s -> s.startsWith(start) && s.endsWith(end))
    25                 .collect(Collectors.toList());
    26     }
    27 
    28     @PostMapping(value = "add", consumes = MediaType.APPLICATION_JSON_VALUE)
    29     public List<String> add(@RequestBody Map<String, String> map) {
    30         List<String> strings = Lists.newArrayList(dict.getWords());
    31         map.forEach((key, value) -> strings.add(value));
    32         return strings;
    33     }
    34 
    35     @PutMapping(value = "replace", consumes = MediaType.APPLICATION_JSON_VALUE)
    36     public List<String> replace(@RequestBody Map<String, String> map) {
    37         return dict.getWords().stream().map(s -> {
    38             if (map.containsKey(s)) {
    39                 return map.get(s);
    40             }
    41             return s;
    42         }).collect(Collectors.toList());
    43     }
    44 
    45     @PutMapping(value = "updateFirst")
    46     public List<String> updateFirst(@RequestParam("target") String str) {
    47         return dict.getWords().stream().map(s -> {
    48             if (dict.getWords().get(0).equals(s)) {
    49                 return str;
    50             }
    51             return s;
    52         }).collect(Collectors.toList());
    53     }
    54 
    55     @GetMapping(value = "headers")
    56     public Map<String, Object> headers(HttpServletRequest request) {
    57         return Collections.list(request.getHeaderNames())
    58                 .stream().collect(Collectors.toMap(s -> s, request::getHeader));
    59     }
    60 
    61     @DeleteMapping("deleteFirst")
    62     public List<String> deleteFirst() {
    63         return dict.getWords().stream().filter(s -> !dict.getWords().get(0).equals(s)).collect(Collectors.toList());
    64     }
    65 
    66 }

    5.1.3 Feign 接口

     1 /**
     2  * @author pancc
     3  * @version 1.0
     4  */
     5 @Headers("Accept: application/json")
     6 public interface DictFeign {
     7     /**
     8      * @see FeignController#details
     9      */
    10     @RequestLine("GET /dic/details")
    11     Dict details();
    12 
    13 
    14     /**
    15      * @see FeignController#startsWith
    16      */
    17     @RequestLine("GET /dic/startsWith/{query}")
    18     List<String> startsWith(@Param("query") String query);
    19 
    20 
    21     /**
    22      * @see FeignController#updateFirst
    23      */
    24     @RequestLine("PUT /dic/updateFirst?target={target}")
    25     @Headers(HttpHeaders.APPLICATION_JSON)
    26     List<String> updateFirst(@Param("target") String target);
    27 
    28     /**
    29      * @see FeignController#headers
    30      */
    31     @RequestLine("GET /dic/headers")
    32     @Headers(HttpHeaders.APPLICATION_JSON)
    33     Map<String, Object> headers(@HeaderMap Map<String, Object> headers);
    34 
    35     /**
    36      * @see FeignController#startAndEnd
    37      */
    38     @RequestLine("GET /dic/query")
    39     List<String> startAndEnd(@QueryMap Map<String, String> map);
    40 
    41     /**
    42      * @see FeignController#replace
    43      */
    44     @RequestLine("PUT /dic/replace")
    45     @Headers(HttpHeaders.APPLICATION_JSON)
    46     List<String> replace(Map<String, String> map);
    47 
    48 
    49     /**
    50      * @see FeignController#add
    51      */
    52     @RequestLine("POST /dic/add")
    53     @Headers(HttpHeaders.APPLICATION_JSON)
    54     @Body("%7B"var1" : "{v1}","var2": "{v2}" %7D")
    55     List<String> add(@Param("v1") String var1, @Param("v2") String var2);
    56 
    57     /**
    58      * @see FeignController#deleteFirst
    59      */
    60     @RequestLine("DELETE /dic/deleteFirst")
    61     List<String> deleteFirst();
    62 
    63 }

    这里需要注意使用 %7B 代替 { %7D 代替 }

    5.1.4 测试类

     1 /**
     2  * @author pancc
     3  * @version 1.0
     4  */
     5 @SpringBootTest(
     6         value = {"server.port=8081", "logging.level.web=debug"}, webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT)
     7 class DictFeignTest {
     8 
     9     static DictFeign dictFeign;
    10     private static Dict dict = Dict.Instance;
    11 
    12     @BeforeAll
    13     public static void beforeAll() {
    14         dictFeign = Feign.builder().decoder(new JacksonDecoder()).encoder(new JacksonEncoder())
    15                 .target(DictFeign.class, "http://127.0.0.1:8081");
    16     }
    17 
    18     @Test
    19     void details() {
    20         Assertions.assertEquals(dict, dictFeign.details());
    21     }
    22 
    23     @Test
    24     void startsWith() {
    25         Assertions.assertEquals(3, dictFeign.startsWith("a").size());
    26     }
    27 
    28 
    29     @Test
    30     void startAndEnd() {
    31         Map<String, String> map = Maps.newHashMap();
    32         map.put("startsWith", "e");
    33         map.put("endsWith", "e");
    34         Assertions.assertEquals(1, dictFeign.startAndEnd(map).size());
    35     }
    36 
    37     @Test
    38     void replace() {
    39         Assertions.assertNotEquals(dictFeign.replace(Collections.singletonMap("bike", "bikes")).indexOf("bikes"), -1);
    40     }
    41 
    42     @Test
    43     void updateFirst() {
    44         Assertions.assertEquals("game", dictFeign.updateFirst("game").get(0));
    45     }
    46 
    47     @Test
    48     void deleteFirst() {
    49         Assertions.assertEquals(13, dictFeign.deleteFirst().size());
    50     }
    51 
    52 
    53     @Test
    54     void headers() {
    55         Map<String, Object> headers = Maps.newHashMap();
    56         headers.put("age", 15);
    57         headers.put("length", 21);
    58         Assertions.assertTrue(dictFeign.headers(headers).containsKey("age"));
    59         Assertions.assertTrue(dictFeign.headers(headers).containsKey("length"));
    60 
    61     }
    62 
    63     @Test
    64     void add() {
    65         String var1 = "go~";
    66         String var2 = "back";
    67         List<String> adds = dictFeign.add(var1, var2);
    68         Assertions.assertTrue(adds.contains(var1));
    69         Assertions.assertTrue(adds.contains(var2));
    70 
    71     }
    72 }

    5.2 当遇到表单

    在上边的例子中,我们沿用了 JacksonDecoder/JacksonEncoder,它们用于序列化/反序列化 POJO 类,要使用表单时,就需要实现 FormEncoder,同样我们加入 Maven 依赖:

    1    <dependency>
    2             <groupId>io.github.openfeign.form</groupId>
    3             <artifactId>feign-form</artifactId>
    4             <version>3.8.0</version>
    5         </dependency>

    接下来,模拟一个用户登录的流程,我们将尝试种登录方式,表单,Json ,与 JWT 

    5.2.1 用户登录实体

     1 /**
     2  * @author pancc
     3  * @version 1.0
     4  */
     5 @Data
     6 @Accessors(chain = true)
     7 public class UserInfo {
     8 
     9     private String username;
    10     private String password;
    11 
    12 
    13     boolean isValid() {
    14         return "root".equals(this.getPassword()) && "admin".equals(this.getUsername());
    15     }
    16 
    17     static boolean isValid(String token) {
    18         if (Strings.nullToEmpty(token).trim().startsWith("Bearer ")) {
    19             return "user-token".equals(token.split(" ")[1]);
    20         }
    21         return false;
    22 
    23     }
    24 
    25 }

    5.2.2 响应实体

     1 /**
     2  * @author pancc
     3  * @version 1.0
     4  */
     5 @Data
     6 @Accessors(chain = true)
     7 @SuppressWarnings("unused")
     8 public class Response {
     9     private int status = 200;
    10     private String msg;
    11     private Object data;
    12 
    13     public String getMsg() {
    14         return status == 200 ? "Success" : "Failed";
    15     }
    16 }

    5.2.3 控制器

     1 /**
     2  * @author pancc
     3  * @version 1.0
     4  */
     5 @RestController
     6 @RequestMapping("/")
     7 public class LoginController {
     8 
     9 
    10     @PostMapping(value = "login", consumes = MediaType.APPLICATION_JSON_VALUE)
    11     public Response loginWithJson(@RequestBody UserInfo userInfo) {
    12         if (userInfo.isValid()) {
    13             return new Response().setData("Well Come " + userInfo.getUsername());
    14         }
    15         return new Response().setStatus(404);
    16     }
    17 
    18     @PostMapping(value = "login", consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
    19     public Response loginWithForm(UserInfo userInfo) {
    20         if (userInfo.isValid()) {
    21             return new Response().setData("Well Come " + userInfo.getUsername());
    22         }
    23         return new Response().setStatus(404);
    24     }
    25 
    26     @PostMapping(value = "login")
    27     public Response loginWithToken(@Nullable @RequestHeader(HttpHeaders.AUTHORIZATION) String token) {
    28         if (UserInfo.isValid(token)) {
    29             return new Response().setData("Well Come");
    30         }
    31         return new Response().setStatus(404);
    32     }
    33 
    34 }

    5.2.4 Feign 接口

     1 /**
     2  * @author pancc
     3  * @version 1.0
     4  */
     5 public interface LoginFeign {
     6     /**
     7      * @see LoginController#loginWithJson
     8      */
     9     @RequestLine("POST /login")
    10     @Headers("Content-Type: application/json")
    11     Response loginWithJson(UserInfo userInfo);
    12 
    13     /**
    14      * @see LoginController#loginWithForm
    15      */
    16     @RequestLine("POST /login")
    17     @Headers("Content-Type: application/x-www-form-urlencoded")
    18     Response loginWithForm(UserInfo userInfo);
    19 
    20 
    21     /**
    22      * @see LoginController#loginWithToken
    23      */
    24     @RequestLine("POST /login")
    25     @Headers("Authorization: Bearer {token}")
    26     Response loginWithToken(@Param("token") String token);
    27 
    28 
    29 }

    5.2.5 测试类

     1 @SpringBootTest(
     2         value = {"server.port=8082", "logging.level.web=debug"}, webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT)
     3 class LoginFeignTest {
     4 
     5     @Test
     6     void loginWithJson() {
     7         LoginFeign feign = Feign.builder()
     8                 .encoder(new JacksonEncoder())
     9                 .decoder(new JacksonDecoder())
    10                 .target(LoginFeign.class, "http://127.0.0.1:8082");
    11         Response res = feign.loginWithJson(new UserInfo().setPassword("root").setUsername("admin"));
    12         Assertions.assertEquals(200,res.getStatus());
    13     }
    14 
    15     @Test
    16     void loginWithForm() {
    17         LoginFeign feign = Feign.builder()
    18                 .encoder(new FormEncoder())
    19                 .decoder(new JacksonDecoder())
    20                 .target(LoginFeign.class, "http://127.0.0.1:8082");
    21         Response res = feign.loginWithForm(new UserInfo().setPassword("root").setUsername("admin"));
    22         Assertions.assertEquals(200,res.getStatus());
    23     }
    24 
    25     @Test
    26     void loginWithToken() {
    27         LoginFeign feign = Feign.builder()
    28                 .encoder(new JacksonEncoder())
    29                 .decoder(new JacksonDecoder())
    30                 .target(LoginFeign.class, "http://127.0.0.1:8082");
    31         Response res = feign.loginWithToken("user-token");
    32         Assertions.assertEquals(200,res.getStatus());
    33     }
    34 }

    7. 代理

    7.1 网络连接的难题

    在上边测试官方 Demo 的过程中,我们很大可能会遇到网络问题,这时候就需要使用代理,我们可以使用 Ok-Http 来设置代理,它的依赖:

    1      <dependency>
    2             <groupId>io.github.openfeign</groupId>
    3             <artifactId>feign-okhttp</artifactId>
    4             <version>${feign-version}</version>
    5         </dependency>

    接着需要改写一下官方 demo ,设置代理:

     1 /**
     2  * @author pancc
     3  * @version 1.0
     4  */
     5 public class SimpleGit {
     6     interface GitHub {
     7 
     8         @RequestLine("GET /repos/{owner}/{repo}/contributors")
     9         List<Contributor> contributors(@Param("owner") String owner, @Param("repo") String repo);
    10 
    11     }
    12 
    13     @Data
    14     @JsonIgnoreProperties(ignoreUnknown = true)
    15     public static class Contributor {
    16         String login;
    17         int contributions;
    18     }
    19 
    20     public static void main(String[] args) {
    21         Proxy proxy = new Proxy(Proxy.Type.SOCKS, new InetSocketAddress("127.0.0.1", 10808));
    22         OkHttpClient client = new OkHttpClient.Builder().proxy(proxy).build();
    23         GitHub github = Feign.builder()
    24                 .decoder(new JacksonDecoder())
    25                 .client(new feign.okhttp.OkHttpClient(client))
    26                 .target(GitHub.class, "https://api.github.com");
    27         // Fetch and print a list of the contributors to this library.
    28         List<Contributor> contributors = github.contributors("OpenFeign", "feign");
    29         for (Contributor contributor : contributors) {
    30             System.out.println(contributor.login + " (" + contributor.contributions + ")");
    31         }
    32     }
    33 }
  • 相关阅读:
    ARMR模型简单实践作业(3)-季节性波动与差分
    ARMR模型简单实践作业(2)log()
    mac 下 basemap 安装,以及踩的一些小坑
    安装vsftpd(一)--匿名用户作业
    移动硬盘出现参数错误,无法显示
    MapperReduce序列化作业(二)——排序
    ARMR模型简单实践作业(1)-平稳性检验
    wget简单使用(2)
    基本排序算法之堆排序
    HDFS学习之客户端I/O流操作
  • 原文地址:https://www.cnblogs.com/siweipancc/p/12635294.html
Copyright © 2011-2022 走看看