Feign深入学习(一)
介紹
Feign版本https://github.com/Netflix/feign不再維護,修改為https://github.com/OpenFeign/feign?。
Maven
<dependency><groupId>io.github.openfeign</groupId><artifactId>feign-core</artifactId> </dependency> 或者 <dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-openfeign</artifactId> </dependency>原理?
注解
@RequestLine
為請求定義HttpMethod和UriTemplate(標注在方法上的就是一個HttpMethod,且寫好了URI(可是絕對路徑,也可是相對的,一般寫后部分即可))。表達式、用大括號括起來的值{expression}最終會用對應的@Param注解填進去(根據key匹配)
@java.lang.annotation.Target(METHOD) @Retention(RUNTIME) public @interface RequestLine {// 請求方法 + URI 。首個單詞必須是HTTP方法,且必須頂格寫(前面不允許有空格)String value(); // 是否編碼/符號,默認是會編碼的,也就是轉義的意思boolean decodeSlash() default true;// 默認支持URL傳多值,是通過key來傳輸的。形如:key=value1&key=value2&key=value3// CollectionFormat不同的取值對應不同的分隔符,一般不建議改CollectionFormat collectionFormat() default CollectionFormat.EXPLODED;}示例:
? ? @RequestLine("GET /feign/demo1?name={name}")
? ? @Body("{age}")
? ? String testRequestLine8(@Param("name") String name, @Param("age") Integer age);
?
@Param
只能標注在方法參數Parameter上。?通過名稱定義模板變量,其值將用于填入模版:@Headers/@RequestLine/@Body均可使用模版表達式。
@Retention(RUNTIME) @java.lang.annotation.Target(PARAMETER) public @interface Param {// 名稱(key),和模版會進行匹配然后填充 必填項String value();// 如何把值填充上去,默認是調用其toString方法直接填上去Class<? extends Expander> expander() default ToStringExpander.class;// 是否轉義,默認不轉義,直接放上去boolean encoded() default false;interface Expander {String expand(Object value);}final class ToStringExpander implements Expander {@Overridepublic String expand(Object value) {return value.toString();}} }?@Headers
@Target({METHOD, TYPE}) @Retention(RUNTIME) public @interface Headers {String[] value(); }@Headers({"Accept:*/*", "Accept-Language:zh-cn"})
@QueryMap
@Retention(RUNTIME) @java.lang.annotation.Target(PARAMETER) public @interface QueryMap {boolean encoded() default false; }只能標注在方法參數上。用于傳遞多個查詢值,拼接在URL后面。
僅需注意一點:只能標注在Map類型的參數前面,否則報錯。
@HeaderMap
@Retention(RUNTIME) @java.lang.annotation.Target(PARAMETER) public @interface HeaderMap { }只能標注在Map類型的參數前面
@Body
@Target(METHOD) @Retention(RUNTIME) public @interface Body {String value(); }@Body("{body}"),這樣就可以通過方法參數的@Param("body") String body傳值。這個值最終是以http body體的形式發送的(并非URL參數哦),body體的內容并不要求必須是json,一般請配合請求頭使用。
可以看到body里是可以是任意格式的數據的,包括POJO(默認是調用它的toString方法)。Feign默認情況下只能支持文本消息,但后來feign提供了feign-form這個擴展模塊,所以也就能夠支持二進制、文件上傳
Template
它用于表示:按照 RFC 6570 標準書寫的表達式模版,有了此模版后續就可以用變量進行替換形成最終值。
public class Template {Template( ... ) { ... }// 一個{}就是一個Expression對象,根據name(key)進行匹配public String expand(Map<String, ?> variables) { ... }protected String resolveExpression(Expression expression, Map<String, ?> variables) { ... }// Uri Encode URI編碼// encodeSlash true的話連/也會給轉義,默認不轉義它private String encode(String value) { ... }// 拿到當前template下所有的變量的**名稱**public List<String> getVariables() { ... } }UriTemplate
用于處理URI的模版,用于處理@RequestLine的模版。若對應key不存在或值為null,那么此部分表達式將被忽略。
QueryTemplate
查詢參數(Query String parameter)的模版。用于處理@QueryMap的模版。該模版用于構造一個查詢參數,一次性構建一個,若有多個key需要構建多次。
public final class QueryTemplate extends Template {// 這里name沒有用字符串而是使用了模版類型,是因為name也可以是個模版{}// 大部分情況下它可以是字符串即可private final Template name;// 因為一個key可以對應多值,所以用Listprivate List<String> values; // 當一key多值時候用什么分隔符,支持:, \t | 等等// 默認它是CollectionFormat.EXPLODED,也就是會以foo=bar&foo=baz這種形式拼接起來private final CollectionFormat collectionFormat;// 請注意:默認這里使用的是CollectionFormat.EXPLODEDpublic static QueryTemplate create(String name, Iterable<String> values, Charset charset) {return create(name, values, charset, CollectionFormat.EXPLODED);}// 可以看到它expand實際上是對name進行expand@Overridepublic String expand(Map<String, ?> variables) {String name = this.name.expand(variables);return this.queryString(name, super.expand(variables));} }示例:
public void fun2() {QueryTemplate template = QueryTemplate.create("hobby-{arg}", Arrays.asList("basket", "foot"), StandardCharsets.UTF_8);Map<String, Object> params = new HashMap<>();//params擴展的是hobby-{arg},如果沒有值,則會轉義{arg}String result = template.expand(params);System.out.println(result);//out:hobby-%7Barg%7D=basket&hobby-%7Barg%7D=foot }HeaderTemplate
BodyTemplate
用于表示標注有@Body注解的模版。
Target
用于把請求模版RequestTemplate 轉換為實際請求實例 feign.Request,此Request后續交給Client發送Http請求。
public interface Target<T> {// 此target作用的接口的類型Class<T> type();String name();// 發送請求的Base URL。如:https://example/api/v1String url();// 用于把請求模版組裝、加上Base Url、轉換為一個真正的Request// 此input模版里包含有很多的:QueryTemplate/HeaderTemplate/UriTemplateRequest apply(RequestTemplate input); }HardCodedTarget
硬編碼目標類。它的三大參數都不能為null
Client
用來發送feign.Request這個Http請求的
public interface Client {// Options是一些選項參數,比如:// connectTimeoutMillis鏈接超時時間,默認是:10s// readTimeoutMillis鏈接超時時間,默認是:60sResponse execute(Request request, Options options) throws IOException; }Retryer
重試器。它能對每次執行Http請求Client#execute()的狀態克隆保持,從而根據配置決定是否應該重試。
默認情況下,Feign是有重試機制的,并且是100ms重試一次,默認重試5次。生產環境下建議務必關閉Feign的重試機制。
InvocationHandlerFactory
控制反射方法調度。它是一個工廠,用于為目標target創建一個java.lang.reflect.InvocationHandler反射調用對象。
public interface InvocationHandlerFactory {// Dispatcher:每個方法對應的MethodHandler -> SynchronousMethodHandler實例// 創建出來的是一個FeignInvocationHandler實例,實現了InvocationHandler接口InvocationHandler create(Target target, Map<Method, MethodHandler> dispatch); }Default
它有且僅有一個實現類:feign.InvocationHandlerFactory.Default。
static final class Default implements InvocationHandlerFactory {// 很簡單:調用FeignInvocationHandler構造器完成實例的創建@Overridepublic InvocationHandler create(Target target, Map<Method, MethodHandler> dispatch) {return new ReflectiveFeign.FeignInvocationHandler(target, dispatch);}}FeignInvocationHandler
static class FeignInvocationHandler implements InvocationHandler {private final Target target;//存儲方法的handler。private final Map<Method, MethodHandler> dispatch;@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {if ("equals".equals(method.getName())) {try {Object otherHandler =args.length > 0 && args[0] != null ? Proxy.getInvocationHandler(args[0]) : null;return equals(otherHandler);} catch (IllegalArgumentException e) {return false;}} else if ("hashCode".equals(method.getName())) { //target方法return hashCode();} else if ("toString".equals(method.getName())) { //target方法return toString();}return dispatch.get(method).invoke(args);} }Contract
它決定了哪些注解可以標注在接口/接口方法上是有效的,并且提取出有效的信息,組裝成為MethodMetadata元信息。
public interface Contract {// 此方法來解析類中鏈接到HTTP請求的方法:提取有效信息到元信息存儲List<MethodMetadata> parseAndValidatateMetadata(Class<?> targetType); }?
總結
以上是生活随笔為你收集整理的Feign深入学习(一)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Feign深入学习(二)
- 下一篇: Ribbon服务器状态:ServerSt