②SpringCloud 實戰:引入Feign組件,發起服務間調用
- 2020 年 11 月 26 日
- 筆記
- SpringCloud, 實戰
這是SpringCloud實戰系列中第二篇文章,了解前面第一篇文章更有助於更好理解本文內容:
①SpringCloud 實戰:引入Eureka組件,完善服務治理
簡介
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來發起請求
interface GitHub {
@RequestLine("GET /repos/{owner}/{repo}/contributors")
List<Contributor> 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, "//api.github.com");
// 調用介面,接收返回參數
List<Contributor> 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 方法。
支援的註解
-
@RequestLine
作用與方法上;定義請求,支援用大括弧{expression}包裝對應@Param注釋參數。
使用示例:
@RequestLine("GET /repos/{owner}/{repo}/contributors") List<Contributor> contributors(@Param("owner") String owner, @Param("repo") String repo);
-
@Param
作用於參數上;定義模板變數參數映射,程式碼示例同上。 -
@Headers
作用於類上或者方法上;定義請求頭Header,程式碼示例:@Headers("Accept: application/json") interface BaseApi<V> { @Headers("Content-Type: {contentType}") @RequestLine("PUT /api/{key}") void put(@Param("key") String key, V value,@Param("contentType") String type); }
-
@QueryMap
作用於參數上;定義name-value對的映射(POJO),以展開為查詢字元串,程式碼示例:public interface Api { @RequestLine("GET /find") V find(@QueryMap Map<String, Object> queryMap); @RequestLine("GET /findObj") V findObj(@QueryMap CustomPojo customPojo); }
-
@HeaderMap
作用於參數上;映射成HeaderMap,程式碼示例:public interface Api { @RequestLine("POST /") void post(@HeaderMap Map<String, Object> headerMap); }
-
@Body
作用於參數上;定義一個模版,定義一個模版,解析對應的表達式,程式碼實例:@RequestLine("POST /") @Headers("Content-Type: application/xml") @Body("<login \"user_name\"=\"{user_name}\" \"password\"=\"{password}\"/>") 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](//jinglingwang.cn)
-
具體的步驟和上一篇文章創建Eureka-Client 一模一樣,有變動的配置:
server.port = 8082 spring.application.name=eureka-provider eureka.instance.appname=eureka-provider
-
編寫提供服務介面
@Controller public class HelloController{ @ResponseBody @RequestMapping(method = RequestMethod.GET, path = "hello") public String hello(){ return "hello, my name is eureka provider!"; } }
-
啟動服務,觀察provider成功註冊到註冊中心
我們把之前的Eureka-Client 作為消費者,使用OpenFeign來調用剛剛創建的provider項目。
現在開始改造Eureka-Client 項目:
-
引入 spring-cloud-openfeign 組件
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency>
-
在啟動類上添加註解
@EnableFeignClients
,啟用Feign的客戶端功能 -
定義Feign介面,@FeignClient 註解指定了服務名稱
@FeignClient(value = "eureka-provider") public interface ProviderFeign{ /** * 調用 eureka-provider 的 hello 介面 * @return */ @RequestMapping("/hello") String hello(); }
-
定義sayHello介面,通過feign調用provider的介面
@RestController public class SayHelloController{ @Autowired private ProviderFeign providerFeign; @GetMapping("sayHello") public String sayHello(){ return providerFeign.hello(); } }
-
重啟Eureka-Client 項目,訪問//localhost:8081/sayHello。頁面顯示
hello, my name is eureka provider!
表示我們使用OpenFeign發起服務間調用成功。
至此一個簡單使用OpenFeign發起服務間的調用示例就完成了,下面的教程是進階版,了解一下還是非常有必要的。
使用OpenFeign的其他小技巧
Get 請求以對象作為參數提交
當服務提供者定義了一個Get請求的介面,參數是一個對象,比如這樣的:
@RequestMapping(method = RequestMethod.GET, path = "query")
public String query(UserDTO user){
當服務調用方Feign使用@QueryMap來進行介面調用
@RequestMapping("/query")
String query(@QueryMap UserDTO userDTO);
這時候會發生服務提供方接收到的請求變為Post的現象,服務提供者接收到的請求報錯信:
Resolved [org.springframework.web.HttpRequestMethodNotSupportedException: Request method 'POST' not supported]
這種問題怎麼解決呢?
-
把@QueryMap 註解換成
@SpringQueryMap
註解就可以,這是最簡單快速的解決辦法。 -
在@RequestMapping註解中加入consumes的屬性
在依賴中加入feign-httpclient包,之後在@RequestMapping註解中配置consumes屬性@GetMapping(value = "/query",consumes = MediaType.APPLICATION_JSON_UTF8_VALUE)
把Feign 默認的 Client 替換成OKHttp
Feign 中默認使用 JDK 原生的 URLConnection 發送 HTTP 請求,我們可以把它換成httpclient或者OkHttp,添加如下配置即可:
# 啟用okhttp
feign.okhttp.enabled=true
feign.httpclient.enabled=false
如果你不是用的spring-cloud-dependencies,或者裡面沒有okhttp的包,自己引入即可:
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-okhttp</artifactId>
</dependency>
配置Feign日誌輸出
FeignClient 有一個屬性configuration,我們可以通過這個屬性來自定義每個FeignClient的日誌輸出
-
新建一個配置類ProviderFeignConfiguration:
import feign.Logger; ... @Configuration public class ProviderFeignConfiguration{ @Bean public Logger.Level loggerLevel(){ return Logger.Level.BASIC; } }
Feign日誌記錄的級別一共有4種:NONE、BASIC、HEADERS、FULL;
-
為@FeignClient指定配置
@FeignClient(value = "eureka-provider",configuration = ProviderFeignConfiguration.class)
-
為FeignClient包所在位置單獨配置日誌隔離級別
logging.level.cn.jinglingwang.eurelaclient.demo.feign=DEBUG
這一步你也可以不這樣做,可以通過自定義繼承 feign.Logger 重寫log方法即可輸出日誌。
-
重啟項目,訪問介面//localhost:8081/sayHello,查看日誌輸出變化:
DEBUG 20000 --- [nio-8081-exec-4] c.j.e.demo.feign.ProviderFeign : [ProviderFeign#hello] ---> GET //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);
使用方法:
-
在剛剛上面的ProviderFeignConfiguration類裡面添加以下程式碼即可:
@Bean public BasicAuthRequestInterceptor basicAuth(){ return new BasicAuthRequestInterceptor("username","jinglingwang.cn"); }
-
改造下provider的介面程式碼,輸出Header看是否能輸出這個欄位
@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!"; }
-
重啟兩個項目,訪問//localhost:8081/sayHello,查看provider控制台成功輸出以下內容:
username:jinglingwang.cn
配置超時時間
-
在剛剛上面的ProviderFeignConfiguration類裡面添加以下程式碼:
@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 來進行配置,比如:
# 單獨配置Feing:eureka-provider的連接超時時間 1ms
feign.client.config.eureka-provider.read-timeout=1
重啟之後刷新介面//localhost:8081/sayHello,出現超時的日誌:
具體可以配置的配置項見下圖:
注意:配置文件配置的優先順序是大於上面Java程式碼配置的優先順序的,從上面的測試結果也可以看出,因為我們同時使用了兩種配置方式(重啟時Java 的配置並沒有注釋),從下圖所示的源碼也可以看出:
總結
- Feign 支援原生的API和聲明式註解兩種方式發起請求
- 啟用Feign客戶端需要使用
@EnableFeignClients
註解來開啟 - FeignClient 支援配置文件和Java配置來控制,配置文件的優先順序更高