1. 程式人生 > >②SpringCloud 實戰:引入Feign元件,發起服務間呼叫

②SpringCloud 實戰:引入Feign元件,發起服務間呼叫

這是SpringCloud實戰系列中第二篇文章,瞭解前面第一篇文章更有助於更好理解本文內容: [①SpringCloud 實戰:引入Eureka元件,完善服務治理](https://www.cnblogs.com/admol/p/14030066.html) ## 簡介 Feign 是一個宣告式的 REST 客戶端,它的目的就是讓 REST 呼叫更加簡單。 Feign 提供了 HTTP 請求的模板,通過編寫簡單的介面和插入註解,就可以定義好 HTTP 請求的引數、格式、地址等資訊。 而且 Feign 會完全代理 HTTP 請求,我們只需要像呼叫方法一樣呼叫它就可以完成服務請求及相關處理。Spring Cloud 對 Feign 進行了封裝,使其支援 SpringMVC 標準註解和 ttpMessageConverters。Feign 可以與 Eureka 和 Ribbon 組合使用以支援負載均衡,與 Hystrix 組合使用,支援熔斷回退。 如果你沒有使用 Spring Cloud,那麼可以直接用原生的 Feign 來呼叫 API,如果你使用了 Spring Cloud,可以直接用 Spring Cloud OpenFeign 來呼叫 API。 ## 使用原生API 這裡以官方給出的Github示例為例,展示怎麼使用原生的API來發起請求 ```java interface GitHub { @RequestLine("GET /repos/{owner}/{repo}/contributors") List contributors(@Param("owner") String owner, @Param("repo") String repo); @RequestLine("POST /repos/{owner}/{repo}/issues") void createIssue(Issue issue, @Param("owner") String owner, @Param("repo") String repo); } public static class Contributor { String login; int contributions; } public class MyApp { public static void main(String... args) { GitHub github = Feign.builder() .decoder(new GsonDecoder()) .target(GitHub.class, "https://api.github.com"); // 呼叫介面,接收返回引數 List contributors = github.contributors("OpenFeign", "feign"); for (Contributor contributor : contributors) { // 列印輸出結果 System.out.println(contributor.login + " (" + contributor.contributions + ")"); } } } ``` 上面的程式碼是一個 GET 請求的示列,定義了一個 GitHub 的介面,介面中定義了一個查詢的方法和建立Issue的方法。 在方法上使用了`@RequestLine` 註解,定義了請求方法型別和請求的 URI,URI 中有對應的引數佔位符,返回值有集合,集合中是對應的結構物件。 最後通過 Feign 的 builder 模式構建了 GitHub 介面物件後,就可以直接通過 GiuHub 介面物件呼叫裡面的 contributors 方法。 ### **支援的註解** 1. @RequestLine 作用與方法上;定義請求,支援用大括號{expression}包裝對應@Param註釋引數。 使用示例: ```java @RequestLine("GET /repos/{owner}/{repo}/contributors") List contributors(@Param("owner") String owner, @Param("repo") String repo); ``` 2. @Param 作用於引數上;定義模板變數引數對映,程式碼示例同上。 3. @Headers 作用於類上或者方法上;定義請求頭Header,程式碼示例: ```java @Headers("Accept: application/json") interface BaseApi { @Headers("Content-Type: {contentType}") @RequestLine("PUT /api/{key}") void put(@Param("key") String key, V value,@Param("contentType") String type); } ``` 4. @QueryMap 作用於引數上;定義name-value對的對映(POJO),以展開為查詢字串,程式碼示例: ```java public interface Api { @RequestLine("GET /find") V find(@QueryMap Map queryMap); @RequestLine("GET /findObj") V findObj(@QueryMap CustomPojo customPojo); } ``` 5. @HeaderMap 作用於引數上;對映成HeaderMap,程式碼示例: ```java public interface Api { @RequestLine("POST /") void post(@HeaderMap Map headerMap); } ``` 6. @Body 作用於引數上;定義一個模版,定義一個模版,解析對應的表示式,程式碼例項: ```java @RequestLine("POST /") @Headers("Content-Type: application/xml") @Body("
") void xml(@Param("user_name") String user, @Param("password") String password); @RequestLine("POST /") @Headers("Content-Type: application/json") // json curly braces must be escaped! @Body("%7B\"user_name\": \"{user_name}\", \"password\": \"{password}\"%7D") void json(@Param("user_name") String user, @Param("password") String password); ``` ## 使用OpenFeign 原生的Feign API 使用已經很方便了,但是還有更簡單的,驚不驚喜意不意外?Spring Cloud 推出了spring-cloud-openfeign,使用OpenFeign比使用原生的API還要簡單。 **先建立一個提供服務的專案:[eureka-provider](http://jinglingwang.cn)** 1. 具體的步驟和上一篇文章建立Eureka-Client 一模一樣,有變動的配置: ```xml server.port = 8082 spring.application.name=eureka-provider eureka.instance.appname=eureka-provider ``` 2. 編寫提供服務介面 ```java @Controller public class HelloController{ @ResponseBody @RequestMapping(method = RequestMethod.GET, path = "hello") public String hello(){ return "hello, my name is eureka provider!"; } } ``` 3. 啟動服務,觀察provider成功註冊到註冊中心 ![](https://img2020.cnblogs.com/blog/709068/202011/709068-20201126102527437-318334061.png) 我們把之前的Eureka-Client 作為消費者,使用OpenFeign來呼叫剛剛建立的provider專案。 **現在開始改造[Eureka-Client](http://jinglingwang.cn) 專案:** 1. 引入 spring-cloud-openfeign 元件 ```xml org.springframework.cloud
spring-cloud-starter-openfeign
``` 2. 在啟動類上添加註解`@EnableFeignClients`,啟用Feign的客戶端功能 3. 定義Feign介面,@FeignClient 註解指定了服務名稱 ```java @FeignClient(value = "eureka-provider") public interface ProviderFeign{ /** * 呼叫 eureka-provider 的 hello 介面 * @return */ @RequestMapping("/hello") String hello(); } ``` 4. 定義sayHello介面,通過feign呼叫provider的介面 ```java @RestController public class SayHelloController{ @Autowired private ProviderFeign providerFeign; @GetMapping("sayHello") public String sayHello(){ return providerFeign.hello(); } } ``` 5. 重啟Eureka-Client 專案,訪問[http://localhost:8081/sayHello](http://jinglingwang.cn)。頁面顯示`hello, my name is eureka provider!` 表示我們使用OpenFeign發起服務間呼叫成功。 至此一個簡單使用OpenFeign發起服務間的呼叫示例就完成了,下面的教程是進階版,瞭解一下還是非常有必要的。 ## 使用OpenFeign的其他小技巧 ### **Get 請求以物件作為引數提交** 當服務提供者定義了一個Get請求的介面,引數是一個物件,比如這樣的: ```java @RequestMapping(method = RequestMethod.GET, path = "query") public String query(UserDTO user){ ``` 當服務呼叫方Feign使用@QueryMap來進行介面呼叫 ```java @RequestMapping("/query") String query(@QueryMap UserDTO userDTO); ``` 這時候會發生服務提供方接收到的請求變為Post的現象,服務提供者接收到的請求報錯信: ```java Resolved [org.springframework.web.HttpRequestMethodNotSupportedException: Request method 'POST' not supported] ``` 這種問題怎麼解決呢? 1. 把@QueryMap 註解換成`@SpringQueryMap`註解就可以,這是最簡單快速的解決辦法。 2. 在@RequestMapping註解中加入consumes的屬性 在依賴中加入feign-httpclient包,之後在@RequestMapping註解中配置consumes屬性 ```java @GetMapping(value = "/query",consumes = MediaType.APPLICATION_JSON_UTF8_VALUE) ``` ### **把Feign 預設的 Client 替換成OKHttp** Feign 中預設使用 JDK 原生的 URLConnection 傳送 HTTP 請求,我們可以把它換成httpclient或者OkHttp,新增如下配置即可: ```xml # 啟用okhttp feign.okhttp.enabled=true feign.httpclient.enabled=false ``` 如果你不是用的spring-cloud-dependencies,或者裡面沒有okhttp的包,自己引入即可: ```java io.github.openfeign
feign-okhttp
``` ### **配置Feign日誌輸出** FeignClient 有一個屬性configuration,我們可以通過這個屬性來自定義每個FeignClient的日誌輸出 1. 新建一個配置類ProviderFeignConfiguration: ```java import feign.Logger; ... @Configuration public class ProviderFeignConfiguration{ @Bean public Logger.Level loggerLevel(){ return Logger.Level.BASIC; } } ``` Feign日誌記錄的級別一共有4種:NONE、BASIC、HEADERS、FULL; 2. 為@FeignClient指定配置 ```java @FeignClient(value = "eureka-provider",configuration = ProviderFeignConfiguration.class) ``` 3. 為FeignClient包所在位置單獨配置日誌隔離級別 ```java logging.level.cn.jinglingwang.eurelaclient.demo.feign=DEBUG ``` 這一步你也可以不這樣做,可以通過自定義繼承 feign.Logger 重寫log方法即可輸出日誌。 4. 重啟專案,訪問介面[http://localhost:8081/sayHello](http://localhost:8081/sayHello),檢視日誌輸出變化: ```java DEBUG 20000 --- [nio-8081-exec-4] c.j.e.demo.feign.ProviderFeign : [ProviderFeign#hello] ---> GET http://eureka-provider/hello HTTP/1.1 DEBUG 20000 --- [nio-8081-exec-4] c.j.e.demo.feign.ProviderFeign : [ProviderFeign#hello] <--- HTTP/1.1 200 (4ms) ``` ### **配置Auth認證** Feign提供了一個預設的攔截器`BasicAuthRequestInterceptor`,他主要的功能是為發起的Http請求新增一個請求頭:`template.header("Authorization", headerValue);` 使用方法: 1. 在剛剛上面的ProviderFeignConfiguration類裡面新增以下程式碼即可: ```java @Bean public BasicAuthRequestInterceptor basicAuth(){ return new BasicAuthRequestInterceptor("username","jinglingwang.cn"); } ``` 2. 改造下provider的介面程式碼,輸出Header看是否能輸出這個欄位 ```java @ResponseBody @RequestMapping(method = RequestMethod.GET, path = "hello") public String hello(HttpServletRequest request) throws UnsupportedEncodingException{ String header = request.getHeader("Authorization"); if(header != null && header.length() > 6){ String authorization = new String(Base64.decode(header.substring(6).getBytes("UTF-8")),"UTF-8"); System.out.println(authorization); } return "hello, my name is eureka provider!"; } ``` 3. 重啟兩個專案,訪問[http://localhost:8081/sayHello](http://jinglingwang.cn),檢視provider控制檯成功輸出以下內容: ```java username:jinglingwang.cn ``` ### **配置超時時間** 1. 在剛剛上面的ProviderFeignConfiguration類裡面新增以下程式碼: ```java @Bean public Request.Options options(){ return new Request.Options(5,TimeUnit.SECONDS,5,TimeUnit.SECONDS,true); } ``` 上面引數分別的意思是:連線超時5秒,讀取超時5秒,true:遵循3xx重定向 ### 通過配置檔案配置Feign 上面的配置基本上都是通過Java程式碼的方式來進行的,其實也可以通過配置檔案來配置Feign,通過feign.client.config.{feignName}.xxx 來進行配置,比如: ```java # 單獨配置Feing:eureka-provider的連線超時時間 1ms feign.client.config.eureka-provider.read-timeout=1 ``` 重啟之後重新整理介面[http://localhost:8081/sayHello](http://jinglingwang.cn),出現超時的日誌: ![](https://img2020.cnblogs.com/blog/709068/202011/709068-20201126102554294-1081177866.png) 具體可以配置的配置項見下圖: ![](https://img2020.cnblogs.com/blog/709068/202011/709068-20201126102603167-908456739.png) 注意:配置檔案配置的優先順序是大於上面Java程式碼配置的優先順序的,從上面的測試結果也可以看出,因為我們同時使用了兩種配置方式(重啟時Java 的配置並沒有註釋),從下圖所示的原始碼也可以看出: ![](https://img2020.cnblogs.com/blog/709068/202011/709068-20201126102617276-299245600.png) ## [總結](https://jinglingwang.cn/) 1. Feign 支援原生的API和宣告式註解兩種方式發起請求 2. 啟用Feign客戶端需要使用`@EnableFeignClients`註解來開啟 3. FeignClient 支援配置檔案和Java配置來控制,配置檔案的優先