Feign用法教程
Maven依赖
创建java项目,引入maven
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| <dependency> <groupId>io.github.openfeign</groupId> <artifactId>feign-core</artifactId> <version>10.7.0</version> </dependency>
<dependency> <groupId>io.github.openfeign</groupId> <artifactId>feign-slf4j</artifactId> <version>10.7.0</version> </dependency> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> </dependency>
|
典型示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| public class App {
interface GitHub { @RequestLine("GET /repos/{owner}/{repo}/contributors") String contributors(@Param("owner") String owner, @Param("repo") String repo); }
public static void main(String... args) { GitHub github = Feign.builder() .logger(new Slf4jLogger()) .logLevel(Logger.Level.FULL) .target(GitHub.class, "https://api.github.com"); String contributors = github.contributors("OpenFeign", "feign"); } }
|
支持的注解
注解 |
target |
说明 |
@RequestLine |
方法上 |
按照上面的示例一样,可以在此注解中表名请求method,和请求地址,并且可以使用{表达式} ,这样的形式来从参数中提取数据,动态构造请求地址。 |
@Param |
参数上 |
和上面示例一样,标记一个参数,这样在各种表达式中才能使用 |
@Headers |
方法上 类上 |
这个注解有一个value参数,里面放请求头,参数中和@requestLine 类似,可以使用表达式来动态创建,如果标记在方法上则此方法对应的请求有效,如果标记在类上,则整个类所有方法对应的请求有效。 |
@QeuryMap |
参数上 |
将一个map或pojo标记为查询参数,取提取数据扩展成查询参数拼接在url后面 |
@HeaderMap |
参数上 |
和queryyMap同理,将键值对当成head处理 |
@Body |
方法上 |
用法和RequestLine 类似,它的值会被当作请求体看待,如可以是一个json字符串,里面也可以用表达式 |
覆盖请求host
如果不希望Feign.builder
这样生成接口代理类时指定请求的host,那么可以在方法上添加一个 java.net.URI
的参数,此参数将覆盖builder生成代理类时传入的host。
1 2
| @RequestLine("POST /repos/{owner}/{repo}/issues") void createIssue(URI host, Issue issue, @Param("owner") String owner, @Param("repo") String repo);
|
模板和表达式
Feign 表达式遵守 这里定义的String表达式(Level 1),使用Param
注解来扩展表达式
例子
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| public interface GitHub { @RequestLine("GET /repos/{owner}/{repo}/contributors") List<Contributor> contributors(@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() .target(GitHub.class, "https://api.github.com");
github.contributors("OpenFeign", "feign"); } }
|
表达式必须用大括号{}
包起来的形式,也可以使用正则来限制表达式解析后的值,正则使用冒号:
进行分隔,比如 {owner:[a-zA-Z]*}
这样来限制owner
必须是大小写字母。即前面的值必须满足后面的正则表达式, feign.template.Expressions#125
请求路径和参数扩展
RequestLine
和QueryMap
模板符合 URI Template -RFC6570规范,下面需要注重指出:
Undefined vs Empty Values
Undefined的意思是 null
,根据 URI Template - RFC 6570规范,可以为表达式提供一个空值。当Feign解析一个表达式时,它首先确认该值知否被定义,如果被定义了,则query parameter
会被保留,如果没有被定义,则query parameter
会被移除,下文会详细解释。
表达式出现在path中
如果是空字符串
1 2 3 4 5 6 7 8 9 10 11 12 13
| interface Fq { @RequestLine(value = "GET /item/{id}/create") String getItemCoupon(@Param("id") String id); } public static void main(String... args) { Fq target = Feign.builder() .logger(new Slf4jLogger()) .logLevel(Logger.Level.FULL) .target(Fq.class, "http://localhost"); String itemCoupon = target.getItemCoupon("");
|
如果是null
1 2 3 4 5 6 7 8 9 10 11 12
| interface Fq { @RequestLine(value = "GET /item/{id}/create") String getItemCoupon(@Param("id") String id); }
public static void main(String... args) { Fq target = Feign.builder() .logger(new Slf4jLogger()) .logLevel(Logger.Level.FULL) .target(Fq.class, "http://localhost"); String itemCoupon = target.getItemCoupon(null);
|
如果是+
或/
这种特殊字符
1 2 3 4 5 6 7 8 9 10 11 12 13
| interface Fq { @RequestLine(value = "GET /item/{id}/create") String getItemCoupon(@Param("id") String id); } public static void main(String... args) { Fq target = Feign.builder() .logger(new Slf4jLogger()) .logLevel(Logger.Level.FULL) .target(Fq.class, "http://localhost"); String itemCoupon = target.getItemCoupon("a+b/c");
|
由上我们可以看出,如果参数表达式出现在path中,是不会被转码的,且如果是null的会变成空字符串。
表达式出现在queryString中
空字符串
1 2 3 4 5 6 7 8 9 10 11 12
| interface Fq { @RequestLine(value = "GET /item?id={id}") String getItemCoupon(@Param("id") String id); }
public static void main(String... args) { Fq target = Feign.builder() .logger(new Slf4jLogger()) .logLevel(Logger.Level.FULL) .target(Fq.class, "http://localhost"); String itemCoupon = target.getItemCoupon("");
|
如果是null
1 2 3 4 5 6 7 8 9 10 11 12 13
| interface Fq { @RequestLine(value = "GET /item?id={id}") String getItemCoupon(@Param("id") String id); }
public static void main(String... args) { Fq target = Feign.builder() .logger(new Slf4jLogger()) .logLevel(Logger.Level.FULL) .target(Fq.class, "http://localhost"); String itemCoupon = target.getItemCoupon(null);
|
如果是+
或/
这种特殊字符
1 2 3 4 5 6 7 8 9 10 11 12
| interface Fq { @RequestLine(value = "GET /item?id={id}") String getItemCoupon(@Param("id") String id); }
public static void main(String... args) { Fq target = Feign.builder() .logger(new Slf4jLogger()) .logLevel(Logger.Level.FULL) .target(Fq.class, "http://localhost"); String itemCoupon = target.getItemCoupon("a+b/c");
|
由上可以看出,出现在参数中的模板表达式,如果是空字符串则参数名会被保留下来,如果是null则移除参数名,如果有特殊字符,则进行转码。
如果是@QueryMap
拼接查询参数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| interface Fq { @RequestLine(value = "GET /item") String getItemCoupon(@QueryMap Map map); }
public static void main(String... args) { Fq target = Feign.builder() .logger(new Slf4jLogger()) .logLevel(Logger.Level.FULL) .target(Fq.class, "http://localhost"); Map<String, Object> map = new HashMap<>(); map.put("a","1+1/2"); map.put("b",""); map.put("c",null);
String itemCoupon = target.getItemCoupon(map);
|
由上可以看出使用RequestMap
传递参数时,会对参数进行编码,且如果参数的值为null或空字符串,则保留参数的键
查看Advanced Usage 有更多的示例。
如何处理斜杠 /
默认情况下@RequestLine
不会 encode 斜杠,要改变这种默认行为,可以将@ReqeustLine
的属性decodeSlash
设置为false
,参考这个issus,即默认情况下:
1 2 3 4 5 6
| @RequestMapping("/foo/{id}") String getFooById(String id) { } myFeignClient.getFooById("/1/2/3")
|
如何处理加号+
根据url规范,加号是允许出现在path和查询参数中的,但是现实中处理起来可能不一样,有一些老系统,会把+
当成空格,但是现代的新系统在查询参数中不会将+
当成空格,而是显示成%2B
如果你希望使用+
作为空格,你可以直接只用
字符来当成空格,或者编码成 @2B
,
例如
1 2 3 4 5
| @RequestMapping("/foo/{id}") String getFooById(String id) { } myFeignClient.getFooById("/1+1/2")
|
上面这说的+和/ 在路径中不会转码,但在参数中会被转码
Expander接口
@Param
注解有一个可选的属性 expander
允许控制该参数的解析,expander
属性必须指向一个Expander
接口的实现类,接口如下:
1 2 3
| public interface Expander { String expand(Object value); }
|
示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| interface Fq { @RequestLine(value = "GET /item?date={date}") String getItemCoupon(@Param(value = "date", expander = DateExpander.class) Date date); }
public static class DateExpander implements Param.Expander { @Override public String expand(Object value) { if (!(value instanceof Date)) { throw new RuntimeException("不支持的类型"); } Date date = (Date) value; return new SimpleDateFormat("yyyy-MM-dd").format(date); } }
public static void main(String... args) { Fq target = Feign.builder() .logger(new Slf4jLogger()) .logLevel(Logger.Level.FULL) .target(Fq.class, "http://localhost");
String itemCoupon = target.getItemCoupon(new Date());
|
框架在调用此接口前判断参数是否为null,如果为null则按照上面规范忽略此参数的键值对,不再调用此接口,所以此接口的参数会被保证非null。
此接口的返回值可以是空字符串,也可以是null。返回值,按照上面[Undefined vs Empty Values](#Undefined vs Empty Values)规范处理。
返回值中的特殊字符会被pct-encoded处理,查看Custom @Param Expansion有更多示例。
请求头扩展
和请求参数扩展类似,但进行了如下修改:
- 默认不执行pct-encode ,(而请求参数扩展是默认encode的)。
- 未解析的表达式将被忽略,如果解析结果是空字符串,则这条请求头会被移除 (而请求参数空字符串会添加没有值的参数而不是忽略整个参数)
示例如下
1 2 3 4 5
| public interface ContentService { @RequestLine("GET /api/documents/{contentType}") @Headers("Accept: {contentType}") String getDocumentByType(@Param("contentType") String type); }
|
请求体扩展
@Body
和请求参数扩展类似,但进行了如下修改:
- 值不会进行encoder
- 不能解析的表达式将被忽略,(把模板当成字符串)。
Content-Type
头是必须要有的
定制化
Feign允许定制,它内置了一些可以实现的接口,通过Feign.builder()
来实现定制,如定制自定义decoder:
1 2 3 4 5 6 7 8 9 10 11 12
| 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"); } }
|
多种接口
这个例子中,target传递的不是(class,String) 而是一个target接口的实现类,上面传递的参数(class,String)会被构造为一个HardCodedTarget (T class,String url) ,而你可以自己实现这一步。
1 2 3 4 5 6 7 8 9 10
| public class CloudService { public static void main(String[] args) { CloudDNS cloudDNS = Feign.builder() .target(new CloudIdentityTarget<CloudDNS>(user, apiKey)); } class CloudIdentityTarget extends Target<CloudDNS> { } }
|
使用自定义的target挺不错的,大家可以实现一个试试看。
整合示例
Gson
Gson包含一个编码器一个解码器,和JSON API 一起工作
1 2 3 4 5 6 7 8 9
| 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 使用Jackson来处理JSON也很好
1 2 3 4 5 6 7 8
| 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"); } }
|
OkHttp
底层使用OkHttpClient来处理发送请求,因为默认是使用java.net.URL connection来发送请求的,性能比较低
1 2 3 4 5 6 7
| 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 提供负载均衡,需要负载均衡的可以使用这个
1 2 3 4 5 6 7
| public class Example { public static void main(String[] args) { MyService api = Feign.builder() .client(RibbonClient.create()) .target(MyService.class, "https://myAppProd"); } }
|
Java 11 Http2
使用java11提供的Http2,高版本jdk使用这个是很好的
1 2 3
| GitHub github = Feign.builder() .client(new Http2Client()) .target(GitHub.class, "https://api.github.com");
|
Hystrix
HystrixFeign使用这个让Feign使用Hystrix,带有熔断器功能
1 2 3 4 5
| public class Example { public static void main(String[] args) { MyService api = HystrixFeign.builder().target(MyService.class, "https://myAppProd"); } }
|
SLF4J
SLF4JModule 这个组件允许让Feign底层使用SLF4j来记录日志
1 2 3 4 5 6 7
| public class Example { public static void main(String[] args) { GitHub github = Feign.builder() .logger(new Slf4jLogger()) .target(GitHub.class, "https://api.github.com"); } }
|
Decoders
配置一个解码器来处理相应Response
,默认的支持返回值为String
,byte[]
,void
,可以看一下源码
1 2 3 4 5 6 7
| public class Example { public static void main(String[] args) { GitHub github = Feign.builder() .decoder(new GsonDecoder()) .target(GitHub.class, "https://api.github.com"); } }
|
如果想让结果进入解码器前进行预处理,可以使用mapAndDecode
方法,如下,传递解码器时传递一个lambda来预处理响应。
1 2 3 4 5 6 7
| 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
发送一个带请求体的post请求最简单的方法就是定义一个post请求,参数是一个不带任何注解的String或者byte[],然后不要忘了添加Content-Type
请求头。
1 2 3 4 5 6 7 8 9 10 11
| 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\"}"); } }
|
上面这么做很麻烦,你不得不手工处理json字符串,更简单的方法是定义一个encoder
,如下编码器会将参数Credentials
处理成json字符串。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| 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
注解可以定义一个模板,从@Param
标记的参数中提取数据,不要忘记添加Content-Type
头
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| 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") @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"); } }
|
设置请求头的api
@Headers
注解可以设置在类上,以表示对所有接口生效,设置在方法上表示对该方法生效。
如果类中的所有方法均要添加这个请求头,就会很有用。
1 2 3 4 5 6
| @Headers("Accept: application/json") interface BaseApi<V> { @Headers("Content-Type: application/json") @RequestLine("PUT /api/{key}") void put(@Param("key") String key, V value); }
|
并且@Header
注解是支持动态创建请求头的,从参数中提出数据:
1 2 3 4 5
| public interface Api { @RequestLine("POST /") @Headers("X-Ping: {token}") void post(@Param("token") String token); }
|
如果请求头的键值都是动态的,而且还不知道具体有多少个,可以使用@HeaderMap
注解处理
1 2 3 4
| public interface Api { @RequestLine("POST /") void post(@HeaderMap Map<String, Object> headerMap); }
|
为每个target设置请求头
有时候,如果同一个接口针对不同host(注:host是可以通过注入一个java.net.URI在运行中改变的)使用不同的请求头,或者请求头是根据具体请求变化的,则可以使用RequestInterceptor
或 Target
来实现。
使用RequestInterceptor
的例子在RequestInterceptor
章节再讲。
下面是使用Target
的解决灵活的请求头。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| 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)); } }
|
高级用法
基础Api
在很多情况下,服务端的接口遵循一致的约定,这时可以通过继承接口来处理。
示例
1 2 3 4 5 6 7
| interface BaseAPI { @RequestLine("GET /health") String health();
@RequestLine("GET /all") List<Entity> all(); }
|
你可以使用继承,来获取父类中的方法
1 2 3 4
| interface CustomAPI extends BaseAPI { @RequestLine("GET /custom") String custom(); }
|
有些情况下,返回的Response表现形式也是一样的,如创建用户发送的json,获取用户得到的json,他们两个的形式都是一样的,可以定义公共泛型父类。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| @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> { }
|
日志
你可以设置一个Logger
和 Logger.Level
来选择用什么处理日志以及日志级别
1 2 3 4 5 6 7 8 9
| public class Example { public static void main(String[] args) { GitHub github = Feign.builder() .decoder(new GsonDecoder()) .logger(new Logger.JavaLogger("GitHub.Logger").appendToFile("logs/http.log")) .logLevel(Logger.Level.FULL) .target(GitHub.class, "https://api.github.com"); } }
|
注意:使用JavaLogger()时要避免使用无参的构造函数JavaLogger()
来创建它,他有问题被弃用了,以后可能会删除。
Request Interceptors
当你需要改变所有的请求,无论这个请求的地址是什么时,可以Request Interceptors
。比如你想添加X-Forwarded-For
请求头。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| 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
1 2 3 4 5 6 7 8
| 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"); } }
|
拦截器在Target前面执行,在Target的apply前一行代码。
@Param扩展
上面讲到过@Param
注解的expander 接口,下面这个例子将如果对参数进行修改,他将date格式化成毫秒值
1 2 3 4
| public interface Api { @RequestLine("GET /?since={date}") Result list(@Param(value = "date", expander = DateToMillis.class) Date date); }
|
注意一点,如果有两个@Param
标记的参数的名字是一样的,则后面的会覆盖前面的
@QueryMap 动态查询参数
一个普通的map,添加上@QueryMap
注解,它里面的值会被拿出来作为查询参数处理
1 2 3 4
| public interface Api { @RequestLine("GET /find") V find(@QueryMap Map<String, Object> queryMap); }
|
不是map是pojo也是可以的,默认通过反射获取pojo内的字段拼接成查询参数。
也可以定义一个QueryMapEncoder
来处理如何从pojo中拿值。
1 2 3 4
| public interface Api { @RequestLine("GET /find") V find(@QueryMap CustomPojo customPojo); }
|
配置一个queryMapEncoder
的例子。
1 2 3 4 5 6 7
| public class Example { public static void main(String[] args) { MyApi myApi = Feign.builder() .queryMapEncoder(new MyCustomQueryMapEncoder()) .target(MyApi.class, "https://api.hostname.com"); } }
|
默认通过反射获取值,如果希望使用java bean
规范通过 get set
来获取值,可以配置一个BeanQueryMapEncoder
1 2 3 4 5 6 7
| public class Example { public static void main(String[] args) { MyApi myApi = Feign.builder() .queryMapEncoder(new BeanQueryMapEncoder()) .target(MyApi.class, "https://api.hostname.com"); } }
|
注意:@QueryMap
注解的解析在@Requestline
之后,所以@Requestline
上面定义的参数会被后来的@QueryMap
定义的参数覆盖,且如果@QueryMap
内定义一个空值参数会把@Requestline
中定义的删掉。
Error Handling
配置一个error handDecoder,所有不再2xx范围内的响应,都会被此handler的decode方法处理。
1 2 3 4 5 6 7
| public class Example { public static void main(String[] args) { MyApi myApi = Feign.builder() .errorDecoder(new MyErrorDecoder()) .target(MyApi.class, "https://api.hostname.com"); } }
|
如果你想进行重试,可以抛出一个RetryableException
,这样框架接收到了此异常调用注册的Retryer
来处理重试。
Retry 重试
默认情况下会重试所有的IoException
,和ErrorHandling里面抛出的RetryableException
,通过在builder是设置一个Retryer来定制这种行为
1 2 3 4 5 6 7
| 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)
来决定是否重试,无返回值。
如果允许重试,则直接返回。
如果不允许重试,请将参数中的RetryableException重新抛出。
框架为每个Client
执行器clone一个Retryer
,所以你可以在retryer上维护状态,而不用担心冲突。
注:为每个client创建Retryer的方式是调用Feign.build() 时传入的Retryer的clone()方法
如果不能重试成功,则抛出最后一次重试的RetryException
,如果你想要导致异常的真正Exception,请使用
exceptionPropagationPolicy()
构建Feign
Options配置
可以在build Feign时设置默认的Options。
如果想为每个请求设置独立Options则,接口的参数如果有类型是feign.Request.Options
类型的,会作为配置传入,存在多个的情况下只取第一个。
静态和默认方法
java8+ 支持接口的静态方法和默认方法,这样就允许Feign客户端包含逻辑,
比如你想提供默认参数,或者两个接口聚合成一个一个接口返回等
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| 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"); }
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"); } }
|
一些内置类
feign.Response
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| public final class Response implements Closeable { private final int status; private final String reason; private final Map<String, Collection<String>> headers; private final Body body; private final Request request; . . .
|
feign.Response.Body
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| public interface Body extends Closeable { Integer length(); boolean isRepeatable(); InputStream asInputStream() throws IOException; @Deprecated default Reader asReader() throws IOException { return asReader(StandardCharsets.UTF_8); } Reader asReader(Charset charset) throws IOException; }
|