原來(lái)OpenFeign功能這么強(qiáng)大,你知道嗎?
OpenFeign是Spring微服務(wù)全家桶中的重要組件。前身是Netflix Feign,在2013年首次發(fā)布。2016年,Netflix發(fā)布了Feign的最后一個(gè)版本(8.18.0),并將其捐贈(zèng)給開(kāi)源社區(qū),隨后Feign更名為OpenFeign,于同年發(fā)布了OpenFeign的首個(gè)版本(9.0.0)。在2017年,Spring Cloud團(tuán)隊(duì)將對(duì)Feign的依賴升級(jí)為OpenFeign。
圖片
OpenFeign和Netflix Feign
為了避免歧義,文中提到的Feign或OpenFeign,都是指 OpenFeign。
OpenFeign是Netflix團(tuán)隊(duì)開(kāi)發(fā)的一個(gè)聲明式、模板化的 Web 服務(wù)客戶端,目標(biāo)是開(kāi)發(fā)一種簡(jiǎn)單、優(yōu)雅的 HTTP 服務(wù)客戶端。在設(shè)計(jì)時(shí),借鑒了各種優(yōu)秀類庫(kù),比如Retrofit、 JAXRS-2.0、WebSocket等。
通過(guò)OpenFeign,我們可以像調(diào)用方法一樣實(shí)現(xiàn)HTTP API訪問(wèn)。
本文將介紹如何使用原生的 OpenFeign,原生的使用方式,不是集成在Spring Cloud中的使用方式。
來(lái),一起來(lái)。
先來(lái)個(gè)簡(jiǎn)單的例子
引入依賴
OpenFeign很貼心的提供了BOM,我們可以直接使用控制組件版本。
<project>
……
<properties>
<openfeign.version>13.4</openfeign.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-bom</artifactId>
<version>${openfeign.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
</project>
我這里使用的是13.4版本(學(xué)習(xí)的時(shí)候就得學(xué)新的,新的bug少)。
然后引入core模塊:
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-core</artifactId>
</dependency>
有了前面的BOM,后面的模塊就不用指定版本了。
定義接口
private interface Client {
@RequestLine("GET /anything/{anything}")
@Headers({"Content-Type: application/json"})
String anything(@Param("anything") String anything);
}
為了調(diào)用方便,我們借助https://httpbin.org提供的HTTP API接口anything用來(lái)驗(yàn)證,這個(gè)接口會(huì)返回傳入的參數(shù)。這樣也方便我們檢查調(diào)用是否正常。
創(chuàng)建客戶端
final Client client = Feign.builder()
.logLevel(Level.FULL)
.target(Client.class, "https://httpbin.org");
final String anything = client.anything("testCore");
Assertions.assertNotNull(anything);
Assertions.assertTrue(anything.contains("testCore"));
System.out.println(anything);
是不是非常簡(jiǎn)單,通過(guò)建造器模式簡(jiǎn)單配置下參數(shù),定義接口的域名,然后就像調(diào)用本地方法一樣調(diào)用接口,然后就拿到返回值了。
OpenFeign提供了很多的擴(kuò)展口,比如日志、解析器、攔截器、編碼器、錯(cuò)誤處理器等,可以通過(guò)builder方法進(jìn)行配置。
自定義編解碼器
OpenFeign提供了12種編解碼器,默認(rèn)使用的是字符串編解碼器,如果需要自定義編解碼器,可以通過(guò)builder方法進(jìn)行配置。
比如,我們想要使用Jackson實(shí)現(xiàn):
我們先引入feign-jackson模塊:
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-jackson</artifactId>
</dependency>
然后在建造器參數(shù)指定JacksonDecoder和JacksonEncoder:
final Client client = Feign.builder()
.logLevel(Level.FULL)
.decoder(new JacksonDecoder())
.encoder(new JacksonEncoder())
.target(Client.class, "https://httpbin.org");
final Map<String, Object> requestBody = Map.of("k1", "value1", "k2", "value2");
final Map<String, Object> anythingResult = client.anythingJson("testJson", requestBody);
如果想要換成Gson,引入feign-gson模塊,在建造器參數(shù)替換為GsonDecoder和GsonEncoder就行。
還有JAXB、Moshi、Fashjson、SAX等一種編解碼器可以使用。
自定義客戶端
OpenFeign默認(rèn)的客戶端是Java提供的HttpURLConnection,如果需要自定義客戶端,可以通過(guò)builder方法進(jìn)行配置。
比如,我們想要使用OkHttp,可以先引入feign-okhttp模塊:
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-okhttp</artifactId>
</dependency>
然后通過(guò)client方法替換:
final Client client = Feign.builder()
.logLevel(Level.FULL)
.decoder(new JacksonDecoder())
.encoder(new JacksonEncoder())
.client(new OkHttpClient())
.target(Client.class, "https://httpbin.org");
final Map<String, Object> anythingResult = client.anythingJsonBodyTemplate("testJson", "value1", "value2");
此時(shí)使用的就是OkHttpClient了。
OpenFeign還支持Apache HTTP、Apache HC5、Google HTTP、Java11 HTTP2、Ribbon。
自定義攔截器
攔截器可以對(duì)請(qǐng)求和響應(yīng)進(jìn)行攔截處理,比如打印日志、添加請(qǐng)求頭、添加簽名等,可以使用requestInterceptor自定義攔截器。
首先,定義我們自己的攔截器,比如我們?cè)谡?qǐng)求頭中添加一個(gè)自定義的header:
public class MyRequestInterceptor implements RequestInterceptor {
@Override
public void apply(RequestTemplate template) {
template.header("my-header", "my-value");
}
}
然后指定攔截器:
final Client client = Feign.builder()
.logLevel(Level.FULL)
.decoder(new JacksonDecoder())
.encoder(new JacksonEncoder())
.requestInterceptor(new MyRequestInterceptor())
.target(Client.class, "https://httpbin.org");
final Map<String, Object> anythingResult = client.anythingJsonBodyTemplate("testJson", "value1", "value2");
System.out.println(anythingResult);
Assertions.assertNotNull(anythingResult);
Assertions.assertTrue(anythingResult.get("url") instanceof String);
Assertions.assertTrue(((String) anythingResult.get("url")).endsWith("testJson"));
Assertions.assertTrue(anythingResult.containsKey("json"));
Assertions.assertTrue(anythingResult.get("json") instanceof Map<?, ?>);
Assertions.assertTrue(anythingResult.containsKey("headers"));
boolean hasMyHeader = false;
if (anythingResult.get("headers") instanceof Map headers) {
for (Object key : headers.keySet()) {
if (key.toString().equalsIgnoreCase("my-header")) {
hasMyHeader = true;
final Object value = headers.get(key);
Assertions.assertTrue(value instanceof String);
Assertions.assertEquals("my-value", value);
}
}
}
Assertions.assertTrue(hasMyHeader);
訪問(wèn)anything接口時(shí)會(huì)把請(qǐng)求頭的信息返回回來(lái),說(shuō)明攔截器執(zhí)行成功了。
自定義重試器
OpenFeign默認(rèn)的重試器是feign.Retryer.Default,共重試5次,每次間隔步長(zhǎng)為1.5的(重試次數(shù)-1)次冪,間隔最大1秒。
如果想要自定義重試邏輯,我們可以自己實(shí)現(xiàn)。
public class MyRetryer implements Retryer {
int attempt = 0;
@Override
public void continueOrPropagate(RetryableException e) {
if (attempt++ >= 3) {
throw e;
}
System.out.println("重試第:" + attempt + "次");
try {
TimeUnit.MILLISECONDS.sleep(100);
} catch (InterruptedException ex) {
Thread.currentThread().interrupt();
throw new RuntimeException(ex);
}
}
@Override
public Retryer clone() {
return new MyRetryer();
}
}
然后通過(guò)retryer方法指定。
final Client client = Feign.builder()
.logLevel(Level.FULL)
.decoder(new JacksonDecoder())
.encoder(new JacksonEncoder())
.retryer(new MyRetryer())
// 默認(rèn)是 feign.Retryer.Default
// 可以指定不重試 feign.Retryer.NEVER_RETRY
.target(Client.class, "https://httpbin.abc");
Assertions.assertThrowsExactly(RetryableException.class, () -> client.codes("500"));
需要強(qiáng)調(diào)一下,只有訪問(wèn)HTTP時(shí)出現(xiàn)了IO異常才會(huì)重試,如果接口正常返回了,只不過(guò)不是200之類的正常響應(yīng),不會(huì)進(jìn)重試邏輯。示例中把域名寫錯(cuò)了,屬于IO異常,會(huì)重試3次。
如果不想重試,可以指定為feign.Retryer.NEVER_RETRY。
使用Spring的注解
OpenFeign接口定義使用的是URL模板,具體協(xié)議可以參https://www.rfc-editor.org/rfc/rfc6570.html。
大部分人對(duì)這個(gè)協(xié)議有些陌生,但是對(duì)Spring的注解比較屬性,所以O(shè)penFeign也貼心的提供了Spring契約適配。
首先,引入spring模塊依賴:
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-spring</artifactId>
</dependency>
這種方式引入的是Spring 6.x,如果想要使用Spring 4.x,可以引入feign-spring4模塊。
然后使用Spring MVC注解定義接口:
@PostMapping(value = "/anything/{anything}",
produces = "application/json", consumes = "application/json")
Map<String, Object> anythingJsonSpring(@PathVariable("anything") String anything,
@RequestParam("p1") String p1,
@RequestParam("p2") String p2,
@RequestBody Map<String, Object> requestBody);
構(gòu)建客戶端的時(shí)候,需要使用contract指定是Spring的契約:
final SpringClient client = Feign.builder()
.logLevel(Level.FULL)
.decoder(new JacksonDecoder())
.encoder(new JacksonEncoder())
.contract(new SpringContract())
.target(SpringClient.class, "https://httpbin.org");
final Map<String, Object> requestBody = Map.of("k1", "value1", "k2", "value2");
final Map<String, Object> anythingResult = client.anythingJsonSpring("testJson",
"param1", "param2", requestBody);
這樣就可以正常運(yùn)行了。
文末總結(jié)
本文介紹了OpenFeign的基本使用方法,包括如何引入依賴、如何定義接口、如何構(gòu)建客戶端、如何自定義攔截器、重試器等。
OpenFeign的入門篇結(jié)束,后續(xù)我們將介紹OpenFeign的更多功能,比如錯(cuò)誤處理器、熔斷器、監(jiān)控等。
圖片