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 }

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM