轉載請註明出處: 翻譯: Spring Cloud Feign使用文檔html
Feign
使用諸如Jersey
和CXF
之類的工具來實現ReST
或SOAP
服務的java客戶端, 此外, Feign
容許你在http庫(如: Apache HC
)之上編寫本身的代碼. 經過自定義解碼器(decoders
)和錯誤處理(error handing
), Feign
能夠用最小的開銷和最少的代碼將你的代碼關聯到任何基於文本的http接口(http APIS
),java
Feign
是經過將註解(annotations
)轉換成模板請求來實現它的功能的, Feign
能夠將請求參數直接應用到這些模板上. 儘管Feign
只支持基於文本的接口, 但一樣的它能顯著地簡化系統的方方面面, 如請求重放等, 此外, Feign
也可使你的單元測試更加簡單.git
Feign 10.x
及以上的版本是基於Java 8構建的, 且應該一樣支持Java 九、十、11, 若是你須要在JDK 6的版本上使用的話, 請使用Feign 9.x
版本.github
下面的代碼是適配Retrofit示例
的用法:正則表達式
interface GitHub { @RequestLine("GET /repos/{owner}/{repo}/contributors") List<Contributor> contributors(@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<Contributor> contributors = github.contributors("OpenFeign", "feign"); for (Contributor contributor : contributors) { System.out.println(contributor.login + " (" + contributor.contributions + ")"); } } }
Feign
的註解定義了接口與底層http客戶端功能之間的約定, 默認狀況下各個註解的約定含義以下:express
Annotation | Interface Target | Usage |
---|---|---|
@RequestLine |
接口 | 定義請求的HttpMethod 和UriTemplate . 模板中可使用大括號包圍的表達式({expression} ), 表達式的值由@Param 對應參數的註解值提供. |
@Param |
參數 | 定義模板變量, 變量的值應該由名字相對應的表達式提供. |
@Headers |
方法、Type |
定義HeaderTemplate ; 使用@Param 註解的值解析對應的表達式. 當該註解應用在Type 上時, 該模板會被應用到每個請求上. 當該註解應用在方法上時, 該模板僅會被應用到對應的方法上. |
@QueryMap |
參數 | 將鍵值對類型的Map、POJO展開成地址上的請求參數(query string ) |
@HeaderMap |
參數 | 將鍵值對類型的Map展開成請求頭Http Headers . |
@Body |
方法 | 定義與UriTemplate 和HeaderTemplate 相似的模板(Template ), 該模板可使用@Param 的註解值解析對應的表達式 |
Feign
支持由URI Template - RFC 6570定義的簡單字符串(Level 1)表達式, 表達式的值從相關方法上對應@Param
註解提供, 示例以下:json
public interface GitHub { @RequestLine("GET /repos/{owner}/{repo}/contributors") List<Contributor> getContributors(@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"); } }
表達式必須使用大括號({}
)包裹着, 而且支持使用冒號(:
)分隔的正則表達式來限定表達式的值. 如限定上述例子的owner
參數的值必須是字母: {owner:[a-zA-Z]*}
.後端
RequestLine
和QueryMap
遵循 URI Template - RFC 6570規範對一級模板(Level 1 templates
)的規定:api
@Param
註解標記爲已編碼(encoded
)的字符和變量值都使用pct編碼(pct-encoded)
.能夠從Advanced Usage一節查看更多示例.安全
What about slashes?/
默認狀況下,
@RequestLine
和@QueryMap
模板不會對正斜槓/
進行編碼, 若是須要默認對其進行編碼的話, 能夠將@RequestLine
的decodeSlash
屬性值設置爲false
.What about plus?
+
根據URI規範,
+
可使用在URI
地址和請求參數(query segments
)這兩個部分上, 然而在請求參數(query)上對該符號的處理卻有可能不一致, 在一些遺留的系統上,+
會被解析成一個空白符(space
). 對此,Feign
採用現代系統對+
的解釋, 不會將+
認爲是一個空白符(space
), 並將請求參數上的+
編碼爲%2B
.若是你但願將
+
當成空白符(space
), 那麼請直接使用一個空格或者直接將其編碼爲
%20
.
@Param
註解有一個可選的參數expander
能夠用來控制單個參數的展開行爲(expansion
), 該屬性的值必須指向一個實現了Expander
接口的類:
public interface Expander { String expand(Object value); }
對該方法的返回值的處理與上述規則相同, 若是返回值是null
或者是一個空字符串, 那麼該值會被忽略. 若是返回值不是使用pct
編碼(pct-encoded
)的, 將會自動轉換成pct
編碼. 能夠從 Custom @Param Expansion 一節查看更多示例.
@Headers
和HeaderMap
模板對 Request Parameter Expansion 一節闡述的規則作如下修改, 並遵循之:
pct
編碼(pct-encoding
).能夠從Headers一節查看示例.
關於
@Param
參數和參數名須要注意的點不管是在
@RequestLine
、@QueryMap
、@BodyTemplate
仍是@Headers
上的表達式, 只要表達式內的變量名字相同, 那麼它們的值也必然相同. 以下面的例子,contentType
的值會同時被解析到請求頭(header)和路徑(path)上:public interface ContentService { @RequestLine("GET /api/documents/{contentType}") @Headers("Accept: {contentType}") String getDocumentByType(@Param("contentType") String type); }當你在設計你的接口的必定要牢記這一點.
Body
模板對 Request Parameter Expansion 一節闡述的規則作如下修改, 並遵循之:
Encoder
處理.Content-Type
請求頭, 能夠從 Body Templates一節查看示例.你能夠在不少地方對Feign
進行定製. 好比, 你可使用Feign.builder()
對自定義的組件構建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"); } }
Feign
客戶以對使用Target<T>
(默認是HardCodedTarget<T>
)定義的對象生成多個API接口, 這樣你能夠在執行前動態發現服務或者對請求進行裝飾.
例如, 下面的代碼能夠實現爲
從身份服務中獲取當前url
和受權令牌(auth token)
, 而後設置到每一個請求上:
public class CloudService { public static void main(String[] args) { CloudDNS cloudDNS = Feign.builder() .target(new CloudIdentityTarget<CloudDNS>(user, apiKey)); } class CloudIdentityTarget extends Target<CloudDNS> { /* implementation of a Target */ } }
Feign
包含了GitHub和Wikipedia的客戶端示例代碼, 在實踐中也能夠參考這些項目, 尤爲是example daemon.
Feign
在設計上就但願可以和其餘開源項目很好的整合到一塊兒, 咱們也很樂於將你喜歡的模塊添加進來.
Gson包含了和JSON接口相關的編碼(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包含了和JSON接口相關的編碼(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"); } }
SaxDecoder提供了能夠與普通JVM和Android環境兼容的方式解析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包含了和XML接口相關的編碼器(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"); } }
JAXRSContract使用JAX-RS
規範提供的標準覆蓋了對註解的處理, 目前實現的是1.1
版的規範, 示例以下:
interface GitHub { @GET @Path("/repos/{owner}/{repo}/contributors") List<Contributor> contributors(@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請求直接交由OkHttp處理, 後者實現了SPDY協議和提供了更好的網絡控制能力.
將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"); } }
RibbonClient會覆蓋Feign
客戶端的URL解析, 以實現由Ribbon提供的智能路由和彈性能力.
將Ribbon
與Feign
整合須要你將url中的主機名(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"); } }
Http2Client直接將Feign
的http請求交給Java11 New HTTP/2 Client處理, 後者實現了HTTP/2協議.
要將New HTTP/2 Client
與Feign
整合使用, 你須要使用Java SDK 11, 並作以下配置:
GitHub github = Feign.builder() .client(new Http2Client()) .target(GitHub.class, "https://api.github.com");
HystrixFeign實現了由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包含了XML接口相關的編碼器(SOAPEncoder
)、解碼器(SOAPDecoder
).
該模塊經過JAXB和SOAPMessage實現了對SOAP Body
的編碼和解碼的支持, 經過將SOAPFault
包裝秤javax.xml.ws.soap.SOAPFaultException
實現了對SOAPFault
解碼的功能, 所以, 對於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
的響應使用了表示錯誤的狀態碼(4xx, 5xx, …)的話, 那麼你還須要添加一個SOAPErrorDecoder
.
SLF4JModule實現了將Feign
的日誌重定向到SLF4J, 這容許你很容易的就能使用你想用的日誌後端(Logback、Log4J等).
要將SLF4J
與Feign
整合, 你須要將SLF4J
模塊和對應的日誌後端模塊放到classpath
下, 並作以下配置:
public class Example { public static void main(String[] args) { GitHub github = Feign.builder() .logger(new Slf4jLogger()) .target(GitHub.class, "https://api.github.com"); } }
Feign.builder()
容許你手動指定額外的配置, 如配置如何對響應進行解析.
若是你接口定義的方法的返回值是除了Response
、String
、byte[]
或void
以外的類型, 那麼你必須配置一個非默認的Decoder
.
下面的代碼展現瞭如何配置使用feign-gson
對JSON解碼:
public class Example { public static void main(String[] args) { GitHub github = Feign.builder() .decoder(new GsonDecoder()) .target(GitHub.class, "https://api.github.com"); } }
若是你想在對響應進行解碼以前先對其作處理的話, 你可使用mapAndDecode
方法, 下面的代碼展現了對一個jsonp響應的處理, 在將響應交給JSON解碼器以前, 須要先對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"); } }
將一個請求體發送到服務器的最簡單的辦法是定義一個POST
請求方法, 該方法的參數類型是String
或byte[]
, 且參數上不帶任何註解, 而且你可能還須要設置Content-Type
請求頭(若是沒有的話):
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\"}"); } }
而經過配置Encoder
, 你能夠發送一個類型安全的請求體, 下面的例子展現了使用feign-gson
擴展來實現編碼:
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
註解的模板會使用@Param
註解的值來展開模板內部的表達式, 對於POST
請求你可能還須要設置Content-Type
請求頭(若是沒有的話):
interface LoginClient { @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); } public class Example { public static void main(String[] args) { client.xml("denominator", "secret"); // <login "user_name"="denominator" "password"="secret"/> client.json("denominator", "secret"); // {"user_name": "denominator", "password": "secret"} } }
Feign
支持在api上爲每一個請求設置請求頭, 也支持爲每一個客戶端的請求設置請求頭, 你能夠根據實際場景進行選擇.
對於那些明確須要設置某些請求頭的接口的狀況, 適用於將請求頭的定義做爲接口的一部分.
靜態配置的請求頭能夠經過在接口上使用@Headers
註解設置:
@Headers("Accept: application/json") interface BaseApi<V> { @Headers("Content-Type: application/json") @RequestLine("PUT /api/{key}") void put(@Param("key") String key, V value); }
也能夠在方法上的@Headers
使用變量展開動態指定請求頭的內容:
public interface Api { @RequestLine("POST /") @Headers("X-Ping: {token}") void post(@Param("token") String token); }
有時候, 對於同一個接口或客戶端的請求頭, 其鍵和值可能會隨着不一樣的方法調用而發生變化, 且不可預知(例如: 自定義元數據請求頭字段"x-amz-meta-"或"x-goog-meta-"), 此時能夠在接口上聲明一個Map參數, 並使用@HeaderMap
註解將Map的內容設置爲對應請求的請求頭:
public interface Api { @RequestLine("POST /") void post(@HeaderMap Map<String, Object> headerMap); }
上述的幾個方法均可以在接口上指定請求的請求頭, 且不須要在構造時對Feign
客戶端作任何的定製.
當同一個接口的請求須要針對不一樣的請求對象(endpoints
)配置不一樣的請求頭, 或者須要對同一個接口的每一個請求都定製其請求頭時, 能夠在Feign
客戶端上使用RequestInterceptor
或Target
來設置請求頭.
使用RequestInterceptor
設置請求頭的例子能夠在Request Interceptor
一節中查看示例.
使用Target
設置請求頭的示例以下:
static class DynamicAuthTokenTarget<T> implements Target<T> { public DynamicAuthTokenTarget(Class<T> clazz, UrlAndTokenProvider provider, ThreadLocal<String> 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)); } }
上述方法的最終效果取決於你對RequestInterceptor
或Target
內部的實現, 能夠經過這種方法對每一個Feign
客戶端的全部接口調用設置請求頭. 這在一些場景下是很是有用的, 如對每一個Feign
客戶端的全部請求設置認證令牌authentication token
. 這些方法是在接口調用者所在的線程中執行的(譯者注: 須要注意線程安全), 所以請求頭的值能夠是在調用時根據上下文動態地設置. 例如, 能夠根據不一樣的調用線程, 從ThreadLocal
裏讀取不一樣的數據設置請求頭.
大多數狀況下服務的接口都遵循相同的約定. Feign
使用單繼承的方式來實現, 好比下面的例子:
interface BaseAPI { @RequestLine("GET /health") String health(); @RequestLine("GET /all") List<Entity> all(); }
你能夠經過繼承的方式來擁有BaseAPI
的接口, 並實現其餘特定的接口:
interface CustomAPI extends BaseAPI { @RequestLine("GET /custom") String custom(); }
不少時候, 接口對資源的表示也是一致的, 所以, 也能夠在基類的接口中使用泛型參數:
@Headers("Accept: application/json") interface BaseApi<V> { @RequestLine("GET /api/{key}") V get(@Param("key") String key); @RequestLine("GET /api") List<V> list(); @Headers("Content-Type: application/json") @RequestLine("PUT /api/{key}") void put(@Param("key") String key, V value); } interface FooApi extends BaseApi<Foo> { } interface BarApi extends BaseApi<Bar> { }
你能夠經過爲Feign
客戶端設置Logger
來記錄其http日誌, 最簡單的實現以下:
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"); } }
若是你須要跨Feign
客戶端對全部請求都作修改, 那麼你能夠配置RequestInterceptor
來實現. 例如, 若是你是請求的一個代理, 那麼你可能會須要設置X-Forwarded-For
請求頭:
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"); } }
另外一個常見的使用攔截器的場景是受權, 好比使用內置的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"); } }
使用@Param
註解的參數會用其toString()
方法展開得到參數值, 也能夠經過制定一個自定義的Param.Expander
來控制. 如對日期的格式化:
public interface Api { @RequestLine("GET /?since={date}") Result list(@Param(value = "date", expander = DateToMillis.class) Date date); }
能夠經過對Map類型的參數加上QueryMap
註解, 將Map的內容構形成查詢參數(query parameters
):
public interface Api { @RequestLine("GET /find") V find(@QueryMap Map<String, Object> queryMap); }
一樣的, 也能夠經過使用QueryMapEncoder
實現用POJO對象生成查詢參數(query parameter
):
public interface Api { @RequestLine("GET /find") V find(@QueryMap CustomPojo customPojo); }
當用這種方式時, 若是沒有指定一個自定義的QueryMapEncoder
, 那麼查詢參數的(query parameter
)內容將根據對象的成員變量生成, 參數名對應變量名. 下面的例子中, 根據POJO對象生成的查詢參數(query parameter
)的內容是"/find?name={name}&number={number}", 生成的查詢參數的順序是不固定的, 按照慣例, 若是POJO對象的某個變量值爲null, 那麼該變量會被丟棄.
public class CustomPojo { private final String name; private final int number; public CustomPojo (String name, int number) { this.name = name; this.number = number; } }
設置自定義QueryMapEncoder
的方式以下:
public class Example { public static void main(String[] args) { MyApi myApi = Feign.builder() .queryMapEncoder(new MyCustomQueryMapEncoder()) .target(MyApi.class, "https://api.hostname.com"); } }
當用@QueryMao
註解時, 默認的編碼器(encoder
)會對對象的字段使用反射來將其展開成查詢參數(query string
). 若是但願經過對象的getter和setter方法來展開查詢參數(query string
), 請使用BeanQueryMapEncoder
:
public class Example { public static void main(String[] args) { MyApi myApi = Feign.builder() .queryMapEncoder(new BeanQueryMapEncoder()) .target(MyApi.class, "https://api.hostname.com"); } }
你能夠經過在Feign
實例構造時註冊一個自定義的ErrorDecoder
來實現對非正常響應的控制:
public class Example { public static void main(String[] args) { MyApi myApi = Feign.builder() .errorDecoder(new MyErrorDecoder()) .target(MyApi.class, "https://api.hostname.com"); } }
全部HTTP狀態碼不爲2xx的響應都會觸發ErrorDecoder
的decode
方法, 在這個方法內你能夠對這些響應針對性地拋出異常, 或作其餘額外的處理. 若是但願對請求進行重試, 那麼能夠拋出RetryableException
, 該異常會觸發Retryer
.
默認狀況下, Feign
會對產生IOException
的請求自動重試, 不管使用的是哪一種HTTP方法, 都認爲IOExcdeption
是由短暫的網絡問題產生的. 對ErrorDecoder
內拋出的RetryableException
也會進行請求重試. 你也能夠通在Feign
實例構造時設置自定義的Retryer
來定製重試行爲:
public class Example { public static void main(String[] args) { MyApi myApi = Feign.builder() .retryer(new MyRetryer()) .target(MyApi.class, "https://api.hostname.com"); } }
Retryer
的實現須要決定一個請求是否應該進行重試, 能夠經過continueOrPropagate(RetryableException e)
方法的返回值(true
或false
)來實現. 每一個Feign
客戶端執行時都會構造一個Retryer
實例, 這樣的話你能夠維護每一個請求的從新狀態.
若是最終重試也失敗了, 那麼會拋出RetryException
, 若是但願拋出致使重試失敗的異常, 能夠在構造Feign
客戶端時指定exceptionPropagationPolicy()
選項.
使用Feign
的接口多是靜態的或默認的方法(Java 8及以上支持), 這容許Feign
客戶端包含一些不適用底層接口定義的邏輯. 例如, 使用靜態方法能夠很輕易地指定通用客戶端構造配置, 使用默認方法能夠用於組合查詢或定義默認參數:
interface GitHub { @RequestLine("GET /repos/{owner}/{repo}/contributors") List<Contributor> contributors(@Param("owner") String owner, @Param("repo") String repo); @RequestLine("GET /users/{username}/repos?sort={sort}") List<Repo> repos(@Param("username") String owner, @Param("sort") String sort); default List<Repo> repos(String owner) { return repos(owner, "full_name"); } /** * Lists all contributors for all repos owned by a user. */ default List<Contributor> 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"); } }