摘要:轉(zhuǎn)載請(qǐng)注明出處翻譯使用文檔使用諸如和之類的工具來(lái)實(shí)現(xiàn)或服務(wù)的客戶端此外允許你在庫(kù)如之上編寫自己的代碼通過(guò)自定義解碼器和錯(cuò)誤處理可以用最小的開銷和最少的代碼將你的代碼關(guān)聯(lián)到任何基于文本的接口是通過(guò)將注解轉(zhuǎn)換成模板請(qǐng)求來(lái)實(shí)現(xiàn)它的功能的可以將請(qǐng)求
轉(zhuǎn)載請(qǐng)注明出處: 翻譯: Spring Cloud Feign使用文檔
Why Feign and not X?Feign使用諸如Jersey和CXF之類的工具來(lái)實(shí)現(xiàn)ReST或SOAP服務(wù)的java客戶端, 此外, Feign允許你在http庫(kù)(如: Apache HC)之上編寫自己的代碼. 通過(guò)自定義解碼器(decoders)和錯(cuò)誤處理(error handing), Feign可以用最小的開銷和最少的代碼將你的代碼關(guān)聯(lián)到任何基于文本的http接口(http APIS),
How does Feign work?Feign是通過(guò)將注解(annotations)轉(zhuǎn)換成模板請(qǐng)求來(lái)實(shí)現(xiàn)它的功能的, Feign可以將請(qǐng)求參數(shù)直接應(yīng)用到這些模板上. 盡管Feign只支持基于文本的接口, 但同樣的它能顯著地簡(jiǎn)化系統(tǒng)的方方面面, 如請(qǐng)求重放等, 此外, Feign也可以使你的單元測(cè)試更加簡(jiǎn)單.
Java Version CampatibilityFeign 10.x及以上的版本是基于Java 8構(gòu)建的, 且應(yīng)該同樣支持Java 9、10、11, 如果你需要在JDK 6的版本上使用的話, 請(qǐng)使用Feign 9.x版本.
Basics下面的代碼是適配Retrofit示例的用法:
interface GitHub { @RequestLine("GET /repos/{owner}/{repo}/contributors") ListInterface Annotationscontributors(@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"); // Fetch and print a list of the contributors to this library. List contributors = github.contributors("OpenFeign", "feign"); for (Contributor contributor : contributors) { System.out.println(contributor.login + " (" + contributor.contributions + ")"); } } }
Feign的注解定義了接口與底層http客戶端功能之間的約定, 默認(rèn)情況下各個(gè)注解的約定含義如下:
Annotation | Interface Target | Usage |
---|---|---|
@RequestLine | 接口 | 定義請(qǐng)求的HttpMethod和UriTemplate. 模板中可以使用大括號(hào)包圍的表達(dá)式({expression}), 表達(dá)式的值由@Param對(duì)應(yīng)參數(shù)的注解值提供. |
@Param | 參數(shù) | 定義模板變量, 變量的值應(yīng)該由名字相對(duì)應(yīng)的表達(dá)式提供. |
@Headers | 方法、Type | 定義HeaderTemplate; 使用@Param注解的值解析對(duì)應(yīng)的表達(dá)式. 當(dāng)該注解應(yīng)用在Type上時(shí), 該模板會(huì)被應(yīng)用到每一個(gè)請(qǐng)求上. 當(dāng)該注解應(yīng)用在方法上時(shí), 該模板僅會(huì)被應(yīng)用到對(duì)應(yīng)的方法上. |
@QueryMap | 參數(shù) | 將鍵值對(duì)類型的Map、POJO展開成地址上的請(qǐng)求參數(shù)(query string) |
@HeaderMap | 參數(shù) | 將鍵值對(duì)類型的Map展開成請(qǐng)求頭Http Headers. |
@Body | 方法 | 定義與UriTemplate和HeaderTemplate類似的模板(Template), 該模板可以使用@Param的注解值解析對(duì)應(yīng)的表達(dá)式 |
Feign支持由URI Template - RFC 6570定義的簡(jiǎn)單字符串(Level 1)表達(dá)式, 表達(dá)式的值從相關(guān)方法上對(duì)應(yīng)@Param注解提供, 示例如下:
public interface GitHub { @RequestLine("GET /repos/{owner}/{repo}/contributors") ListgetContributors(@Param("owner") String owner, @Param("repo") String repository); 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"); /* The owner and repository parameters will be used to expand the owner and repo expressions * defined in the RequestLine. * * the resulting uri will be https://api.github.com/repos/OpenFeign/feign/contributors */ github.contributors("OpenFeign", "feign"); } }
表達(dá)式必須使用大括號(hào)({})包裹著, 并且支持使用冒號(hào)(:)分隔的正則表達(dá)式來(lái)限定表達(dá)式的值. 如限定上述例子的owner參數(shù)的值必須是字母: {owner:[a-zA-Z]*}.
Request Parameter ExpansionRequestLine和QueryMap遵循 URI Template - RFC 6570規(guī)范對(duì)一級(jí)模板(Level 1 templates)的規(guī)定:
未被解析的值將會(huì)被忽略.
所有未編碼或者通過(guò)@Param注解標(biāo)記為已編碼(encoded)的字符和變量值都使用pct編碼(pct-encoded).
可以從Advanced Usage一節(jié)查看更多示例.
What about slashes? /Custom Expansion默認(rèn)情況下, @RequestLine和@QueryMap模板不會(huì)對(duì)正斜杠/進(jìn)行編碼, 如果需要默認(rèn)對(duì)其進(jìn)行編碼的話, 可以將@RequestLine的decodeSlash屬性值設(shè)置為false.
What about plus? +
根據(jù)URI規(guī)范, +可以使用在URI地址和請(qǐng)求參數(shù)(query segments)這兩個(gè)部分上, 然而在請(qǐng)求參數(shù)(query)上對(duì)該符號(hào)的處理卻有可能不一致, 在一些遺留的系統(tǒng)上, +會(huì)被解析成一個(gè)空白符(space). 對(duì)此, Feign采用現(xiàn)代系統(tǒng)對(duì)+的解釋, 不會(huì)將+認(rèn)為是一個(gè)空白符(space), 并將請(qǐng)求參數(shù)上的+編碼為%2B.
如果你希望將+當(dāng)成空白符(space), 那么請(qǐng)直接使用一個(gè)空格 或者直接將其編碼為%20.
@Param注解有一個(gè)可選的參數(shù)expander可以用來(lái)控制單個(gè)參數(shù)的展開行為(expansion), 該屬性的值必須指向一個(gè)實(shí)現(xiàn)了Expander接口的類:
public interface Expander { String expand(Object value); }
對(duì)該方法的返回值的處理與上述規(guī)則相同, 如果返回值是null或者是一個(gè)空字符串, 那么該值會(huì)被忽略. 如果返回值不是使用pct編碼(pct-encoded)的, 將會(huì)自動(dòng)轉(zhuǎn)換成pct編碼. 可以從 Custom @Param Expansion 一節(jié)查看更多示例.
Request Headers Expansion@Headers和HeaderMap模板對(duì) Request Parameter Expansion 一節(jié)闡述的規(guī)則做以下修改, 并遵循之:
未被解析的值將會(huì)被忽略, 但如果解析到一個(gè)空的值(empty header value), 那么對(duì)應(yīng)的請(qǐng)求頭會(huì)被移除.
不會(huì)對(duì)請(qǐng)求頭使用pct編碼(pct-encoding).
可以從Headers一節(jié)查看示例.
Reuqest Body Expansion關(guān)于@Param參數(shù)和參數(shù)名需要注意的點(diǎn)
無(wú)論是在@RequestLine、@QueryMap、@BodyTemplate還是@Headers上的表達(dá)式, 只要表達(dá)式內(nèi)的變量名字相同, 那么它們的值也必然相同. 如下面的例子, contentType的值會(huì)同時(shí)被解析到請(qǐng)求頭(header)和路徑(path)上:
public interface ContentService { @RequestLine("GET /api/documents/{contentType}") @Headers("Accept: {contentType}") String getDocumentByType(@Param("contentType") String type); }當(dāng)你在設(shè)計(jì)你的接口的一定要牢記這一點(diǎn).
Body模板對(duì) Request Parameter Expansion 一節(jié)闡述的規(guī)則做以下修改, 并遵循之:
未被解析的值將會(huì)被忽略.
展開的值在被解析到請(qǐng)求體之前不會(huì)經(jīng)過(guò)Encoder處理.
必須指定Content-Type請(qǐng)求頭, 可以從 Body Templates一節(jié)查看示例.
Customization你可以在很多地方對(duì)Feign進(jìn)行定制. 比如, 你可以使用Feign.builder()對(duì)自定義的組件構(gòu)建API接口:
interface Bank { @RequestLine("POST /account/{id}") Account getAccountInfo(@Param("id") String id); } public class BankService { public static void main(String[] args) { Bank bank = Feign.builder().decoder( new AccountDecoder()) .target(Bank.class, "https://api.examplebank.com"); } }Multiple Interfaces
Feign客戶以對(duì)使用Target
例如, 下面的代碼可以實(shí)現(xiàn)為從身份服務(wù)中獲取當(dāng)前url和授權(quán)令牌(auth token), 然后設(shè)置到每個(gè)請(qǐng)求上:
public class CloudService { public static void main(String[] args) { CloudDNS cloudDNS = Feign.builder() .target(new CloudIdentityTargetExamples(user, apiKey)); } class CloudIdentityTarget extends Target { /* implementation of a Target */ } }
Feign包含了GitHub和Wikipedia的客戶端示例代碼, 在實(shí)踐中也可以參考這些項(xiàng)目, 尤其是example daemon.
IntegrationsFeign在設(shè)計(jì)上就希望能夠和其他開源項(xiàng)目很好的整合到一起, 我們也很樂(lè)于將你喜歡的模塊添加進(jìn)來(lái).
GsonGson包含了和JSON接口相關(guān)的編碼(GsonEncoder)、解碼器(GsonDecoder), 將它將它用到Feign.Builder的方式如下:
public class Example { public static void main(String[] args) { GsonCodec codec = new GsonCodec(); GitHub github = Feign.builder() .encoder(new GsonEncoder()) .decoder(new GsonDecoder()) .target(GitHub.class, "https://api.github.com"); } }Jackson
Jackson包含了和JSON接口相關(guān)的編碼(JacksonEncoder)、解碼器(JacksonDecoder), 將它將它用到Feign.Builder的方式如下:
public class Example { public static void main(String[] args) { GitHub github = Feign.builder() .encoder(new JacksonEncoder()) .decoder(new JacksonDecoder()) .target(GitHub.class, "https://api.github.com"); } }Sax
SaxDecoder提供了可以與普通JVM和Android環(huán)境兼容的方式解析XML文本, 下面的例子展示了如何使用:
public class Example { public static void main(String[] args) { Api api = Feign.builder() .decoder(SAXDecoder.builder() .registerContentHandler(UserIdHandler.class) .build()) .target(Api.class, "https://apihost"); } }JAXB
JAXB包含了和XML接口相關(guān)的編碼器(JAXBEncoder)、解碼器(JAXBEncoder), 將它將它用到Feign.Builder的方式如下:
public class Example { public static void main(String[] args) { Api api = Feign.builder() .encoder(new JAXBEncoder()) .decoder(new JAXBDecoder()) .target(Api.class, "https://apihost"); } }JAX-RS
JAXRSContract使用JAX-RS規(guī)范提供的標(biāo)準(zhǔn)覆蓋了對(duì)注解的處理, 目前實(shí)現(xiàn)的是1.1版的規(guī)范, 示例如下:
interface GitHub { @GET @Path("/repos/{owner}/{repo}/contributors") ListOkHttpcontributors(@PathParam("owner") String owner, @PathParam("repo") String repo); } public class Example { public static void main(String[] args) { GitHub github = Feign.builder() .contract(new JAXRSContract()) .target(GitHub.class, "https://api.github.com"); } }
OkHttpClient直接將Feign的http請(qǐng)求直接交由OkHttp處理, 后者實(shí)現(xiàn)了SPDY協(xié)議和提供了更好的網(wǎng)絡(luò)控制能力.
將OkHttp整合到Feign中需要你把OkHttp模塊放到classpath下, 然后做如下配置:
public class Example { public static void main(String[] args) { GitHub github = Feign.builder() .client(new OkHttpClient()) .target(GitHub.class, "https://api.github.com"); } }Ribbon
RibbonClient會(huì)覆蓋Feign客戶端的URL解析, 以實(shí)現(xiàn)由Ribbon提供的智能路由和彈性能力.
將Ribbon與Feign整合需要你將url中的主機(jī)名(host)部分替換成Ribbon客戶端名. 例如Ribbon客戶端明為myAppProd:
public class Example { public static void main(String[] args) { MyService api = Feign.builder() .client(RibbonClient.create()) .target(MyService.class, "https://myAppProd"); } }Java 11 Http2
Http2Client直接將Feign的http請(qǐng)求交給Java11 New HTTP/2 Client處理, 后者實(shí)現(xiàn)了HTTP/2協(xié)議.
要將New HTTP/2 Client與Feign整合使用, 你需要使用Java SDK 11, 并做如下配置:
GitHub github = Feign.builder() .client(new Http2Client()) .target(GitHub.class, "https://api.github.com");Hystrix
HystrixFeign實(shí)現(xiàn)了由Hystrix提供的斷路器功能.
要將Hystrix與Feign整合, 你需要將Hystrix模塊放到classpath下, 并使用HystrixFeign:
public class Example { public static void main(String[] args) { MyService api = HystrixFeign.builder().target(MyService.class, "https://myAppProd"); } }SOAP
SOAP包含了XML接口相關(guān)的編碼器(SOAPEncoder)、解碼器(SOAPDecoder).
該模塊通過(guò)JAXB和SOAPMessage實(shí)現(xiàn)了對(duì)SOAP Body的編碼和解碼的支持, 通過(guò)將SOAPFault包裝秤javax.xml.ws.soap.SOAPFaultException實(shí)現(xiàn)了對(duì)SOAPFault解碼的功能, 因此, 對(duì)于SOAPFault的處理, 你只需要捕獲SOAPFaultException.
使用示例如下:
public class Example { public static void main(String[] args) { Api api = Feign.builder() .encoder(new SOAPEncoder(jaxbFactory)) .decoder(new SOAPDecoder(jaxbFactory)) .errorDecoder(new SOAPErrorDecoder()) .target(MyApi.class, "http://api"); } }
如果SOAP Faults的響應(yīng)使用了表示錯(cuò)誤的狀態(tài)碼(4xx, 5xx, …)的話, 那么你還需要添加一個(gè)SOAPErrorDecoder.
SLF4JSLF4JModule實(shí)現(xiàn)了將Feign的日志重定向到SLF4J, 這允許你很容易的就能使用你想用的日志后端(Logback、Log4J等).
要將SLF4J與Feign整合, 你需要將SLF4J模塊和對(duì)應(yīng)的日志后端模塊放到classpath下, 并做如下配置:
public class Example { public static void main(String[] args) { GitHub github = Feign.builder() .logger(new Slf4jLogger()) .target(GitHub.class, "https://api.github.com"); } }Decoders
Feign.builder()允許你手動(dòng)指定額外的配置, 如配置如何對(duì)響應(yīng)進(jìn)行解析.
如果你接口定義的方法的返回值是除了Response、String、byte[]或void之外的類型, 那么你必須配置一個(gè)非默認(rèn)的Decoder.
下面的代碼展示了如何配置使用feign-gson對(duì)JSON解碼:
public class Example { public static void main(String[] args) { GitHub github = Feign.builder() .decoder(new GsonDecoder()) .target(GitHub.class, "https://api.github.com"); } }
如果你想在對(duì)響應(yīng)進(jìn)行解碼之前先對(duì)其做處理的話, 你可以使用mapAndDecode方法, 下面的代碼展示了對(duì)一個(gè)jsonp響應(yīng)的處理, 在將響應(yīng)交給JSON解碼器之前, 需要先對(duì)jsonp做處理:
public class Example { public static void main(String[] args) { JsonpApi jsonpApi = Feign.builder() .mapAndDecode((response, type) -> jsopUnwrap(response, type), new GsonDecoder()) .target(JsonpApi.class, "https://some-jsonp-api.com"); } }Encoders
將一個(gè)請(qǐng)求體發(fā)送到服務(wù)器的最簡(jiǎn)單的辦法是定義一個(gè)POST請(qǐng)求方法, 該方法的參數(shù)類型是String或byte[], 且參數(shù)上不帶任何注解, 并且你可能還需要設(shè)置Content-Type請(qǐng)求頭(如果沒(méi)有的話):
interface LoginClient { @RequestLine("POST /") @Headers("Content-Type: application/json") void login(String content); } public class Example { public static void main(String[] args) { client.login("{"user_name": "denominator", "password": "secret"}"); } }
而通過(guò)配置Encoder, 你可以發(fā)送一個(gè)類型安全的請(qǐng)求體, 下面的例子展示了使用feign-gson擴(kuò)展來(lái)實(shí)現(xiàn)編碼:
static class Credentials { final String user_name; final String password; Credentials(String user_name, String password) { this.user_name = user_name; this.password = password; } } interface LoginClient { @RequestLine("POST /") void login(Credentials creds); } public class Example { public static void main(String[] args) { LoginClient client = Feign.builder() .encoder(new GsonEncoder()) .target(LoginClient.class, "https://foo.com"); client.login(new Credentials("denominator", "secret")); } }@Body templates
使用@Body注解的模板會(huì)使用@Param注解的值來(lái)展開模板內(nèi)部的表達(dá)式, 對(duì)于POST請(qǐng)求你可能還需要設(shè)置Content-Type請(qǐng)求頭(如果沒(méi)有的話):
interface LoginClient { @RequestLine("POST /") @Headers("Content-Type: application/xml") @Body("Headers") 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); } public class Example { public static void main(String[] args) { client.xml("denominator", "secret"); // client.json("denominator", "secret"); // {"user_name": "denominator", "password": "secret"} } }
Feign支持在api上為每個(gè)請(qǐng)求設(shè)置請(qǐng)求頭, 也支持為每個(gè)客戶端的請(qǐng)求設(shè)置請(qǐng)求頭, 你可以根據(jù)實(shí)際場(chǎng)景進(jìn)行選擇.
Set headers using apis對(duì)于那些明確需要設(shè)置某些請(qǐng)求頭的接口的情況, 適用于將請(qǐng)求頭的定義作為接口的一部分.
靜態(tài)配置的請(qǐng)求頭可以通過(guò)在接口上使用@Headers注解設(shè)置:
@Headers("Accept: application/json") interface BaseApi{ @Headers("Content-Type: application/json") @RequestLine("PUT /api/{key}") void put(@Param("key") String key, V value); }
也可以在方法上的@Headers使用變量展開動(dòng)態(tài)指定請(qǐng)求頭的內(nèi)容:
public interface Api { @RequestLine("POST /") @Headers("X-Ping: {token}") void post(@Param("token") String token); }
有時(shí)候, 對(duì)于同一個(gè)接口或客戶端的請(qǐng)求頭, 其鍵和值可能會(huì)隨著不同的方法調(diào)用而發(fā)生變化, 且不可預(yù)知(例如: 自定義元數(shù)據(jù)請(qǐng)求頭字段"x-amz-meta-"或"x-goog-meta-"), 此時(shí)可以在接口上聲明一個(gè)Map參數(shù), 并使用@HeaderMap注解將Map的內(nèi)容設(shè)置為對(duì)應(yīng)請(qǐng)求的請(qǐng)求頭:
public interface Api { @RequestLine("POST /") void post(@HeaderMap MapheaderMap); }
上述的幾個(gè)方法都可以在接口上指定請(qǐng)求的請(qǐng)求頭, 且不需要在構(gòu)造時(shí)對(duì)Feign客戶端做任何的定制.
Setting headers per target當(dāng)同一個(gè)接口的請(qǐng)求需要針對(duì)不同的請(qǐng)求對(duì)象(endpoints)配置不同的請(qǐng)求頭, 或者需要對(duì)同一個(gè)接口的每個(gè)請(qǐng)求都定制其請(qǐng)求頭時(shí), 可以在Feign客戶端上使用RequestInterceptor或Target來(lái)設(shè)置請(qǐng)求頭.
使用RequestInterceptor設(shè)置請(qǐng)求頭的例子可以在Request Interceptor一節(jié)中查看示例.
使用Target設(shè)置請(qǐng)求頭的示例如下:
static class DynamicAuthTokenTargetimplements Target { public DynamicAuthTokenTarget(Class clazz, UrlAndTokenProvider provider, ThreadLocal requestIdProvider); @Override public Request apply(RequestTemplate input) { TokenIdAndPublicURL urlAndToken = provider.get(); if (input.url().indexOf("http") != 0) { input.insert(0, urlAndToken.publicURL); } input.header("X-Auth-Token", urlAndToken.tokenId); input.header("X-Request-ID", requestIdProvider.get()); return input.request(); } } public class Example { public static void main(String[] args) { Bank bank = Feign.builder() .target(new DynamicAuthTokenTarget(Bank.class, provider, requestIdProvider)); } }
上述方法的最終效果取決于你對(duì)RequestInterceptor或Target內(nèi)部的實(shí)現(xiàn), 可以通過(guò)這種方法對(duì)每個(gè)Feign客戶端的所有接口調(diào)用設(shè)置請(qǐng)求頭. 這在一些場(chǎng)景下是非常有用的, 如對(duì)每個(gè)Feign客戶端的所有請(qǐng)求設(shè)置認(rèn)證令牌authentication token. 這些方法是在接口調(diào)用者所在的線程中執(zhí)行的(譯者注: 需要注意線程安全), 因此請(qǐng)求頭的值可以是在調(diào)用時(shí)根據(jù)上下文動(dòng)態(tài)地設(shè)置. 例如, 可以根據(jù)不同的調(diào)用線程, 從ThreadLocal里讀取不同的數(shù)據(jù)設(shè)置請(qǐng)求頭.
Advanced usage Base Apis大多數(shù)情況下服務(wù)的接口都遵循相同的約定. Feign使用單繼承的方式來(lái)實(shí)現(xiàn), 比如下面的例子:
interface BaseAPI { @RequestLine("GET /health") String health(); @RequestLine("GET /all") Listall(); }
你可以通過(guò)繼承的方式來(lái)?yè)碛?b>BaseAPI的接口, 并實(shí)現(xiàn)其他特定的接口:
interface CustomAPI extends BaseAPI { @RequestLine("GET /custom") String custom(); }
很多時(shí)候, 接口對(duì)資源的表示也是一致的, 因此, 也可以在基類的接口中使用泛型參數(shù):
@Headers("Accept: application/json") interface BaseApiLogging{ @RequestLine("GET /api/{key}") V get(@Param("key") String key); @RequestLine("GET /api") List list(); @Headers("Content-Type: application/json") @RequestLine("PUT /api/{key}") void put(@Param("key") String key, V value); } interface FooApi extends BaseApi { } interface BarApi extends BaseApi { }
你可以通過(guò)為Feign客戶端設(shè)置Logger來(lái)記錄其http日志, 最簡(jiǎn)單的實(shí)現(xiàn)如下:
public class Example { public static void main(String[] args) { GitHub github = Feign.builder() .decoder(new GsonDecoder()) .logger(new Logger.JavaLogger().appendToFile("logs/http.log")) .logLevel(Logger.Level.FULL) .target(GitHub.class, "https://api.github.com"); } }Request Interceptors
如果你需要跨Feign客戶端對(duì)所有請(qǐng)求都做修改, 那么你可以配置RequestInterceptor來(lái)實(shí)現(xiàn). 例如, 如果你是請(qǐng)求的一個(gè)代理, 那么你可能會(huì)需要設(shè)置X-Forwarded-For請(qǐng)求頭:
static class ForwardedForInterceptor implements RequestInterceptor { @Override public void apply(RequestTemplate template) { template.header("X-Forwarded-For", "origin.host.com"); } } public class Example { public static void main(String[] args) { Bank bank = Feign.builder() .decoder(accountDecoder) .requestInterceptor(new ForwardedForInterceptor()) .target(Bank.class, "https://api.examplebank.com"); } }
另一個(gè)常見(jiàn)的使用攔截器的場(chǎng)景是授權(quán), 比如使用內(nèi)置的BasicAuthRequestInterceptor:
public class Example { public static void main(String[] args) { Bank bank = Feign.builder() .decoder(accountDecoder) .requestInterceptor(new BasicAuthRequestInterceptor(username, password)) .target(Bank.class, "https://api.examplebank.com"); } }Custom @Param Expansion
使用@Param注解的參數(shù)會(huì)用其toString()方法展開獲得參數(shù)值, 也可以通過(guò)制定一個(gè)自定義的Param.Expander來(lái)控制. 如對(duì)日期的格式化:
public interface Api { @RequestLine("GET /?since={date}") Result list(@Param(value = "date", expander = DateToMillis.class) Date date); }Dynamic Query Parameters
可以通過(guò)對(duì)Map類型的參數(shù)加上QueryMap注解, 將Map的內(nèi)容構(gòu)造成查詢參數(shù)(query parameters):
public interface Api { @RequestLine("GET /find") V find(@QueryMap MapqueryMap); }
同樣的, 也可以通過(guò)使用QueryMapEncoder實(shí)現(xiàn)用POJO對(duì)象生成查詢參數(shù)(query parameter):
public interface Api { @RequestLine("GET /find") V find(@QueryMap CustomPojo customPojo); }
當(dāng)用這種方式時(shí), 如果沒(méi)有指定一個(gè)自定義的QueryMapEncoder, 那么查詢參數(shù)的(query parameter)內(nèi)容將根據(jù)對(duì)象的成員變量生成, 參數(shù)名對(duì)應(yīng)變量名. 下面的例子中, 根據(jù)POJO對(duì)象生成的查詢參數(shù)(query parameter)的內(nèi)容是"/find?name={name}&number={number}", 生成的查詢參數(shù)的順序是不固定的, 按照慣例, 如果POJO對(duì)象的某個(gè)變量值為null, 那么該變量會(huì)被丟棄.
public class CustomPojo { private final String name; private final int number; public CustomPojo (String name, int number) { this.name = name; this.number = number; } }
設(shè)置自定義QueryMapEncoder的方式如下:
public class Example { public static void main(String[] args) { MyApi myApi = Feign.builder() .queryMapEncoder(new MyCustomQueryMapEncoder()) .target(MyApi.class, "https://api.hostname.com"); } }
當(dāng)用@QueryMao注解時(shí), 默認(rèn)的編碼器(encoder)會(huì)對(duì)對(duì)象的字段使用反射來(lái)將其展開成查詢參數(shù)(query string). 如果希望通過(guò)對(duì)象的getter和setter方法來(lái)展開查詢參數(shù)(query string), 請(qǐng)使用BeanQueryMapEncoder:
public class Example { public static void main(String[] args) { MyApi myApi = Feign.builder() .queryMapEncoder(new BeanQueryMapEncoder()) .target(MyApi.class, "https://api.hostname.com"); } }Error Handling
你可以通過(guò)在Feign實(shí)例構(gòu)造時(shí)注冊(cè)一個(gè)自定義的ErrorDecoder來(lái)實(shí)現(xiàn)對(duì)非正常響應(yīng)的控制:
public class Example { public static void main(String[] args) { MyApi myApi = Feign.builder() .errorDecoder(new MyErrorDecoder()) .target(MyApi.class, "https://api.hostname.com"); } }
所有HTTP狀態(tài)碼不為2xx的響應(yīng)都會(huì)觸發(fā)ErrorDecoder的decode方法, 在這個(gè)方法內(nèi)你可以對(duì)這些響應(yīng)針對(duì)性地拋出異常, 或做其他額外的處理. 如果希望對(duì)請(qǐng)求進(jìn)行重試, 那么可以拋出RetryableException, 該異常會(huì)觸發(fā)Retryer.
Retry默認(rèn)情況下, Feign會(huì)對(duì)產(chǎn)生IOException的請(qǐng)求自動(dòng)重試, 無(wú)論使用的是哪種HTTP方法, 都認(rèn)為IOExcdeption是由短暫的網(wǎng)絡(luò)問(wèn)題產(chǎn)生的. 對(duì)ErrorDecoder內(nèi)拋出的RetryableException也會(huì)進(jìn)行請(qǐng)求重試. 你也可以通在Feign實(shí)例構(gòu)造時(shí)設(shè)置自定義的Retryer來(lái)定制重試行為:
public class Example { public static void main(String[] args) { MyApi myApi = Feign.builder() .retryer(new MyRetryer()) .target(MyApi.class, "https://api.hostname.com"); } }
Retryer的實(shí)現(xiàn)需要決定一個(gè)請(qǐng)求是否應(yīng)該進(jìn)行重試, 可以通過(guò)continueOrPropagate(RetryableException e)方法的返回值(true或false)來(lái)實(shí)現(xiàn). 每個(gè)Feign客戶端執(zhí)行時(shí)都會(huì)構(gòu)造一個(gè)Retryer實(shí)例, 這樣的話你可以維護(hù)每個(gè)請(qǐng)求的重新狀態(tài).
如果最終重試也失敗了, 那么會(huì)拋出RetryException, 如果希望拋出導(dǎo)致重試失敗的異常, 可以在構(gòu)造Feign客戶端時(shí)指定exceptionPropagationPolicy()選項(xiàng).
Static and Default Methods使用Feign的接口可能是靜態(tài)的或默認(rèn)的方法(Java 8及以上支持), 這允許Feign客戶端包含一些不適用底層接口定義的邏輯. 例如, 使用靜態(tài)方法可以很輕易地指定通用客戶端構(gòu)造配置, 使用默認(rèn)方法可以用于組合查詢或定義默認(rèn)參數(shù):
interface GitHub { @RequestLine("GET /repos/{owner}/{repo}/contributors") Listcontributors(@Param("owner") String owner, @Param("repo") String repo); @RequestLine("GET /users/{username}/repos?sort={sort}") List repos(@Param("username") String owner, @Param("sort") String sort); default List repos(String owner) { return repos(owner, "full_name"); } /** * Lists all contributors for all repos owned by a user. */ default List contributors(String user) { MergingContributorList contributors = new MergingContributorList(); for(Repo repo : this.repos(owner)) { contributors.addAll(this.contributors(user, repo.getName())); } return contributors.mergeResult(); } static GitHub connect() { return Feign.builder() .decoder(new GsonDecoder()) .target(GitHub.class, "https://api.github.com"); } }
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/73455.html
摘要:繼承支持通過(guò)單繼承接口支持樣板,這允許將通用操作分組為方便的基本接口。,記錄基本信息以及請(qǐng)求和響應(yīng)。例如,類定義參數(shù)和以下客戶端使用注解使用類 聲明式REST客戶端:Feign Feign是一個(gè)聲明式的Web服務(wù)客戶端,它使編寫Web服務(wù)客戶端變得更容易,要使用Feign,請(qǐng)創(chuàng)建一個(gè)接口并對(duì)其進(jìn)行注解,它具有可插拔的注解支持,包括Feign注解和JAX-RS注解,F(xiàn)eign還支持可插拔...
摘要:不過(guò)大多數(shù)講解還停留在對(duì)功能使用的層面,其底層的很多原理,很多人可能并不知曉。每個(gè)線程池里的線程就僅僅用于請(qǐng)求那個(gè)服務(wù)。 歡迎關(guān)注微信公眾號(hào):石杉的架構(gòu)筆記(id:shishan100) 每日更新!精品技術(shù)文章準(zhǔn)時(shí)送上! 目錄 一、業(yè)務(wù)場(chǎng)景介紹 二、Spring Cloud核心組件:Eureka 三、Spring Cloud核心組件:Feign 四、Spring Cloud核心組件:R...
摘要:不過(guò)大多數(shù)講解還停留在對(duì)功能使用的層面,其底層的很多原理,很多人可能并不知曉。每個(gè)線程池里的線程就僅僅用于請(qǐng)求那個(gè)服務(wù)。 歡迎關(guān)注微信公眾號(hào):石杉的架構(gòu)筆記(id:shishan100) 每日更新!精品技術(shù)文章準(zhǔn)時(shí)送上! 目錄 一、業(yè)務(wù)場(chǎng)景介紹 二、Spring Cloud核心組件:Eureka 三、Spring Cloud核心組件:Feign 四、Spring Cloud核心組件:R...
摘要:下一篇介紹基于的服務(wù)注冊(cè)與調(diào)用。服務(wù)提供者工程配置這里服務(wù)提供者是使用之前進(jìn)階教程第三篇整合連接池以及監(jiān)控改造而來(lái),這里一樣的部分就不再重復(fù)說(shuō)明,下面將說(shuō)明新增的部分。 Spring Cloud簡(jiǎn)介 Spring Cloud是一個(gè)基于Spring Boot實(shí)現(xiàn)的云應(yīng)用開發(fā)工具,它為基于JVM的云應(yīng)用開發(fā)中涉及的配置管理、服務(wù)發(fā)現(xiàn)、斷路器、智能路由、微代理、控制總線、全局鎖、決策競(jìng)選、分...
閱讀 1216·2021-09-03 10:44
閱讀 617·2019-08-30 13:13
閱讀 2808·2019-08-30 13:11
閱讀 1976·2019-08-30 12:59
閱讀 1043·2019-08-29 15:32
閱讀 1607·2019-08-29 15:25
閱讀 1003·2019-08-29 12:24
閱讀 1290·2019-08-27 10:58