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