Java 开源项目 OpenFeign —— feign 结合 SpringBoot

1. 前言

Spring 对 Feign 做了封装,包括常用的 encoder/decoder ,让我们能用 Bean 的形式使用 Feign。我们将沿用之前的代码。

1.1 Maven 依赖

 1         <dependency>
 2             <groupId>org.springframework.cloud</groupId>
 3             <artifactId>spring-cloud-starter-openfeign</artifactId>
 4         </dependency>
 5         <dependency>
 6             <groupId>org.springframework.cloud</groupId>
 7             <artifactId>spring-cloud-starter</artifactId>
 8         </dependency>
 9 
10     <dependencyManagement>
11         <dependencies>
12             <dependency>
13                 <groupId>org.springframework.cloud</groupId>
14                 <artifactId>spring-cloud-dependencies</artifactId>
15                 <version>Hoxton.SR3</version>
16                 <type>pom</type>
17                 <scope>import</scope>
18             </dependency>
19         </dependencies>
20     </dependencyManagement>

1.2 激活 openfeign

在 Springboot 启动类加上注解  @EnableFeignClients

2. SpringMvc 注解

与原生的 Feign 不同,在 Springboot 中要使用到 SpringMvc 的注解,这存在一些限制(比如动态改变 URL ),需要我们找 workRound 

2. 编写代码

由于使用 SpringMVC 风格,编写的代码与 feign 原生的有不少出入,需要仔细对比。

2.1 改写官方教程

我们只需要创建与 GitHub 接口相似的代码,与响应的测试代码

2.1.1 创建 Client

 1 /**
 2  * @author pancc
 3  * @version 1.0
 4  */
 5 @FeignClient(name = "gitHubs", url = " https://api.github.com", qualifier = "gitHubsClient")
 6 public interface GitHubClient {
 7     @GetMapping(value = "/repos/{owner}/{repo}/contributors")
 8     @ResponseBody
 9     List<SimpleGit.Contributor> contributors(@PathVariable("owner") String owner, @PathVariable("repo") String repo);
10 
11     @Data
12     @JsonIgnoreProperties(ignoreUnknown = true)
13     class Contributor {
14         String login;
15         int contributions;
16     }
17 
18 }

2.1.2 创建测试代码

 1 @SpringBootTest
 2 class GitHubClientTest {
 3     @Autowired
 4     GitHubClient gitHubClient;
 5 
 6     @Test
 7     void contributors() {
 8         List<SimpleGit.Contributor> contributors = gitHubClient.contributors("OpenFeign", "feign");
 9         Assertions.assertNotNull(contributors);
10         Assertions.assertNotEquals(0, contributors.size());
11 
12     }
13 }

2.1.3 动态改变请求 url

从上边的代码可以看到,我们将 url 写死在了 GitHubClient 中。让我们新增一个方法,增加 URI 类型的参数

1     @GetMapping(value = "/repos/{owner}/{repo}/contributors")
2     @ResponseBody
3     List<SimpleGit.Contributor> contributorsWithURL(URI uri, @PathVariable("owner") String owner, @PathVariable("repo") String repo);

然后用任意字符串替换 FeignClient#url 并写一个测试:

1     @Test
2     void contributorsWithURL() {
3         List<SimpleGit.Contributor> contributors =    gitHubClient.contributorsWithURL(URI.create("https://api.github.com"), "OpenFeign", "feign");
4         Assertions.assertNotNull(contributors);
5         Assertions.assertNotEquals(0, contributors.size());
6     }

2.2 改写 DictFeign

2.2.1 编写 DictClient 接口

 1 /**
 2  * @author pancc
 3  * @version 1.0
 4  */
 5 @FeignClient(
 6         name = "dictClients",
 7         url = "127.0.0.1:8080",
 8         path = "dic",
 9         qualifier = "dictClients"
10 )
11 public interface DictClient {
12     /**
13      * @see FeignController#details
14      */
15     @GetMapping("details")
16     Dict details();
17 
18     /**
19      * @see FeignController#details
20      */
21     @GetMapping("details")
22     Dict detailsWithURI(URI uri);
23 
24 
25     /**
26      * @see FeignController#startsWith
27      */
28     @GetMapping("startsWith/{query}")
29     List<String> startsWith(@PathVariable("query") String query);
30 
31 
32     /**
33      * @see FeignController#updateFirst
34      */
35     @PutMapping("updateFirst?target={target}")
36     List<String> updateFirst(@PathVariable("target") String target);
37 
38     /**
39      * @see FeignController#headers
40      */
41     @GetMapping("headers")
42     Map<String, Object> headers(@RequestHeader Map<String, Object> headers);
43 
44     /**
45      * @see FeignController#startAndEnd
46      */
47     @GetMapping("query")
48     List<String> startAndEnd(@RequestParam Map<String, String> map);
49 
50     /**
51      * @see FeignController#replace
52      */
53     @PutMapping(value = "replace", consumes = MediaType.APPLICATION_JSON_VALUE)
54     List<String> replace(Map<String, String> map);
55 
56 
57     /**
58      * @see FeignController#add
59      */
60     @PostMapping(value = "add", consumes = MediaType.APPLICATION_JSON_VALUE)
61     List<String> add(Map<String, Object> map);
62 
63     /**
64      * @see FeignController#deleteFirst
65      */
66     @DeleteMapping("deleteFirst")
67     List<String> deleteFirst();
68 }

2.2.2 编写对应的测试方法

 1 @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT)
 2 class DictClientTest {
 3     private final Dict dict = Dict.Instance;
 4     @Autowired
 5     private DictClient dictClient;
 6 
 7 
 8     @Test
 9     void details() {
10         Assertions.assertEquals(dict, dictClient.details());
11     }
12 
13     @Test
14     void startsWith() {
15         Assertions.assertEquals(3, dictClient.startsWith("a").size());
16     }
17 
18 
19     @Test
20     void startAndEnd() {
21         Map<String, String> map = Maps.newHashMap();
22         map.put("startsWith", "e");
23         map.put("endsWith", "e");
24         Assertions.assertEquals(1, dictClient.startAndEnd(map).size());
25     }
26 
27     @Test
28     void replace() {
29         Assertions.assertNotEquals(dictClient.replace(Collections.singletonMap("bike", "bikes")).indexOf("bikes"), -1);
30     }
31 
32     @Test
33     void updateFirst() {
34         Assertions.assertEquals("game", dictClient.updateFirst("game").get(0));
35     }
36 
37     @Test
38     void deleteFirst() {
39         Assertions.assertEquals(13, dictClient.deleteFirst().size());
40     }
41 
42 
43     @Test
44     void headers() {
45         Map<String, Object> headers = Maps.newHashMap();
46         headers.put("age", 15);
47         headers.put("length", 21);
48         Assertions.assertTrue(dictClient.headers(headers).containsKey("age"));
49         Assertions.assertTrue(dictClient.headers(headers).containsKey("length"));
50     }
51 
52     @Test
53     void add() {
54         String var1 = "go~";
55         String var2 = "back";
56         List<String> adds = dictClient.add(Maps.toMap(Arrays.asList(var1, var2), input -> input));
57         Assertions.assertTrue(adds.contains(var1));
58         Assertions.assertTrue(adds.contains(var2));
59 
60     }
61 }

2.3 改写登录流程

2.3.1 配置 FormEncoder

由于使用 Spring 默认使用 Jackson(Json) ,我们需要手动配置一个 FormEncoder,需要注意的是配置类不能写成 Component 以避免自动配置的 encoder 被替换;

 1 /**
 2  * @author pancc
 3  * @version 1.0
 4  */
 5 public class LoginClientConfig {
 6     @Bean
 7     public FormEncoder formEncoder() {
 8         return new FormEncoder(new JacksonEncoder());
 9     }
10 }

2.3.2 编写 LoginClient

 1 /**
 2  * @author pancc
 3  * @version 1.0
 4  */
 5 @FeignClient(name = "loginClient", url = "127.0.0.1:8080", configuration = LoginClientConfig.class)
 6 public interface LoginClient {
 7     /**
 8      * @see LoginController#loginWithJson
 9      */
10     @PostMapping(value = "login", consumes = MediaType.APPLICATION_JSON_VALUE)
11     Response loginWithJson(@RequestBody UserInfo userInfo);
12 
13     /**
14      * @see LoginController#loginWithForm
15      */
16     @PostMapping(value = "login", consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
17     Response loginWithForm(UserInfo userInfo);
18 
19 
20     /**
21      * @see LoginController#loginWithToken
22      */
23     @PostMapping("login")
24     Response loginWithToken(@RequestHeader("Authorization") String token);
25 
26 
27 }

2.3.3 编写测试

 1 @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT)
 2 class LoginClientTest {
 3 
 4     @Autowired
 5     private LoginClient loginClient;
 6 
 7     @Test
 8     void loginWithJson() {
 9         Response res = loginClient.loginWithJson(new UserInfo().setPassword("root").setUsername("admin"));
10         Assertions.assertEquals(200, res.getStatus());
11     }
12 
13     @Test
14     void loginWithForm() {
15         Response res = loginClient.loginWithForm(new UserInfo().setPassword("root").setUsername("admin"));
16         Assertions.assertEquals(200, res.getStatus());
17     }
18 
19     @Test
20     void loginWithToken() {
21         Response res = loginClient.loginWithToken("Bearer user-token");
22         Assertions.assertEquals(200, res.getStatus());
23     }
24 
25 }

3. 代理

代理的使用总是不可避免地,就像怕配置 encoder 一样,让我们为 GitHubClient 配置一个代理

3.1 代理配置类

参照 feign 的基本使用与 2.3.1,我们可以很快的写配置类,同样避免将配置类写成 Component

 1 /**
 2  * @author pancc
 3  * @version 1.0
 4  */
 5 public class GitHubClientConfig {
 6     @Bean
 7     public Client client() {
 8         okhttp3.OkHttpClient okHttpClient =
 9                 new okhttp3.OkHttpClient.Builder().proxy(new Proxy(Proxy.Type.SOCKS, new InetSocketAddress("127.0.0.1", 10808))).build();
10         return new OkHttpClient(okHttpClient);
11     }
12 }

3.2 修改 GithubClient 配置

1 @FeignClient(name = "gitHubs", url = "https://api.github.com", qualifier = "gitHubsClient", configuration = GitHubClientConfig.class)
2 public interface GitHubClient {
3 ....

4. 总结要点

4.1 依赖

     <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter</artifactId>
        </dependency>
 <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>Hoxton.SR3</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

4.2 编写方式

  1. 启动注解 @EnableFeignClients
  2. feign 接口使用 @FeignClient 注解
  3. feign 接口使用 SpringMVC 风格
  4. feign 的配置写在一个不被 Spring 实例化的配置类中,各种 feign 基本模块写成 Bean。启用时只添加配置类进相应的 feign 接口的注解字段中
  5. 使表单需要配置 FormEncoder
  6. feign 接口方法的第一个参数可以为 URI ,用于动态替换 FeignClient 中的 url 

 

 

原文地址:https://www.cnblogs.com/siweipancc/p/12637083.html