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