Feign源碼解析系列-那些註解們

来源:https://www.cnblogs.com/killbug/archive/2019/02/16/10389530.html
-Advertisement-
Play Games

開始 Feign在Spring Cloud體系中被整合進來作為web service客戶端,使用HTTP請求遠程服務時能就像調用本地方法,可見在未來一段時間內,大多數Spring Cloud架構的微服務之間調用都會使用Feign來完成。 所以準備完整解讀一遍Feign的源碼,讀源碼,我個人覺得一方面 ...


開始

Feign在Spring Cloud體系中被整合進來作為web service客戶端,使用HTTP請求遠程服務時能就像調用本地方法,可見在未來一段時間內,大多數Spring Cloud架構的微服務之間調用都會使用Feign來完成。

所以準備完整解讀一遍Feign的源碼,讀源碼,我個人覺得一方面,可以在使用的基礎上對內部實現的細節的瞭解,提高使用時對組件功能的信心,另一方面,開源組件的代碼質量一般都比較高,對代碼結構組織一般比較優秀,還有,內部實現的一些細節可能優秀開發的思考所得,值得仔細揣摩。我對後兩個好處比較感興趣,雖然現如今寫的代碼好與壞,其實不會太多的影響平時的工作,不過如果內心是真的愛代碼,也會不斷追求細節的極致。

因為是Spring Cloud體系下使用Feign,必然會涉及到:服務註冊(Euraka),負載均衡(Rinbon),熔斷器(Hystrix)等方面的整合知識。

另外,能思考的高度和廣度必然有限,但是源碼閱讀學習又難以共同參與,所以剛好你也在這個位置,有自己的思路或想法,不吝留言。

內容

1,EnableFeignClients註解

大流程上,就是掃描FeignClient註解的介面,將介面方法動態代理成http客戶端的介面請求操作就完成了Feign的目的。所以一個FeignClient註解對應一個客戶端。

  • EnableFeignClients這個註解可以配置掃描FeignClient註解的路徑。可以通過value屬性或basePackages屬性來制定掃描的包路徑。
  • basePackageClasses屬性並不是精準掃描哪幾個Class,而是指定這些指定的class在的package會被掃描。所以註釋中推薦寫一個空介面來標記這個package要被掃描的方式來關聯。
  • defaultConfiguration屬性是可以定義全局Feign配置的類,預設使用FeignClientsConfiguration類。想要自定義需要好好確認下FeignClientsConfiguration定義了那一些bean。當然如果只是想覆蓋部分bean,完全不用這個,直接在Configuration定義對應bean即可。
  • clients屬性才是精準指定Class掃描,與package掃描互斥。
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(FeignClientsRegistrar.class)
public @interface EnableFeignClients {
   /**
    * Alias for the {@link #basePackages()} attribute. Allows for more concise annotation
    * declarations e.g.: {@code @ComponentScan("org.my.pkg")} instead of
    * {@code @ComponentScan(basePackages="org.my.pkg")}.
    * @return the array of 'basePackages'.
    */
   String[] value() default {};
   /**
    * Base packages to scan for annotated components.
    * <p>
    * {@link #value()} is an alias for (and mutually exclusive with) this attribute.
    * <p>
    * Use {@link #basePackageClasses()} for a type-safe alternative to String-based
    * package names.
    *
    * @return the array of 'basePackages'.
    */
   String[] basePackages() default {};
   /**
    * Type-safe alternative to {@link #basePackages()} for specifying the packages to
    * scan for annotated components. The package of each class specified will be scanned.
    * <p>
    * Consider creating a special no-op marker class or interface in each package that
    * serves no purpose other than being referenced by this attribute.
    *
    * @return the array of 'basePackageClasses'.
    */
   Class<?>[] basePackageClasses() default {};
   /**
    * A custom <code>@Configuration</code> for all feign clients. Can contain override
    * <code>@Bean</code> definition for the pieces that make up the client, for instance
    * {@link feign.codec.Decoder}, {@link feign.codec.Encoder}, {@link feign.Contract}.
    *
    * @see FeignClientsConfiguration for the defaults
    */
   Class<?>[] defaultConfiguration() default {};
   /**
    * List of classes annotated with @FeignClient. If not empty, disables classpath scanning.
    * @return
    */
   Class<?>[] clients() default {};
}

從EnableFeignClients註解的屬性看,我們可以瞭解到,在解析這個註解屬性的時候,需要利用配置的掃描的package或Class,掃描FeignClient註解,進而解析那些FeignClient註解的配置屬性。並且我們還可以配置全局的Feign相關的配置。

回頭我們再看一下EnableFeignClients定義的元數據,@Import註解的使用值得學習一下。

關於這個註解,我們可以理解成導入

@Import註解導入的類 FeignClientsRegistrar 是繼承 ImportBeanDefinitionRegistrar 的,ImportBeanDefinitionRegistrar的方法一般實現動態註冊bean使用,在由@Import註解導入後,Spring容器啟動時會執行registerBeanDefinitions方法。

所以一般@Import註解和ImportBeanDefinitionRegistrar實現動態註冊bean而配合使用。

前面提到大流程,這篇文章的思路基本描述了:掃描+動態代理介面+http請求,其中也對@Import和ImportBeanDefinitionRegistrar使用場景進行瞭解釋,可以做參考學習。

2,FeignClient註解

每個FeignClient代表一個http客戶端,定義的每一個方法對應這個一個介面。

  • value和name用於定義http客戶端服務的名稱,在spring cloud為服務之間調用服務總要有負載均衡的,比如Rinbon。所以這裡定義的會是服務提供方的應用名(serviceId)。
  • qualifier屬性在spring容器中定義FeignClient的bean時,配置名稱,在裝配bean的時候可以用這個名稱裝配。使用spring的註解:Qualifier。
  • url屬性用來定義請求的絕對URL。
  • decode404屬性,在客戶端返回404時是進行decode操作還是拋出異常的標記。
  • configuration屬性,自定義配置類,可以定義Decoder, Encoder,Contract來覆蓋預設的配置,可以參考預設的配置類:FeignAutoConfiguration
  • fallback屬性 使用fallback機制時可以配置的類屬性,繼承客戶端介面,實現fallback邏輯。如果要使用fallback機制需要配合Hystrix一起,所以需要開啟Hystrix。
  • fallbackFactory屬性 生產fallback實例,生產的自然是繼承客戶端介面的實例。
  • path屬性 每個介面url的統一首碼
  • primary屬性 標記在spring容器中為primary bean
/**
 * Annotation for interfaces declaring that a REST client with that interface should be
 * created (e.g. for autowiring into another component). If ribbon is available it will be
 * used to load balance the backend requests, and the load balancer can be configured
 * using a <code>@RibbonClient</code> with the same name (i.e. value) as the feign client.
 *
 * @author Spencer Gibb
 * @author Venil Noronha
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface FeignClient {
   /**
    * The name of the service with optional protocol prefix. Synonym for {@link #name()
    * name}. A name must be specified for all clients, whether or not a url is provided.
    * Can be specified as property key, eg: ${propertyKey}.
    */
   @AliasFor("name")
   String value() default "";
   /**
    * The service id with optional protocol prefix. Synonym for {@link #value() value}.
    *
    * @deprecated use {@link #name() name} instead
    */
   @Deprecated
   String serviceId() default "";
   /**
    * The service id with optional protocol prefix. Synonym for {@link #value() value}.
    */
   @AliasFor("value")
   String name() default "";
   
   /**
    * Sets the <code>@Qualifier</code> value for the feign client.
    */
   String qualifier() default "";
   /**
    * An absolute URL or resolvable hostname (the protocol is optional).
    */
   String url() default "";
   /**
    * Whether 404s should be decoded instead of throwing FeignExceptions
    */
   boolean decode404() default false;
   /**
    * A custom <code>@Configuration</code> for the feign client. Can contain override
    * <code>@Bean</code> definition for the pieces that make up the client, for instance
    * {@link feign.codec.Decoder}, {@link feign.codec.Encoder}, {@link feign.Contract}.
    *
    * @see FeignClientsConfiguration for the defaults
    */
   Class<?>[] configuration() default {};
   /**
    * Fallback class for the specified Feign client interface. The fallback class must
    * implement the interface annotated by this annotation and be a valid spring bean.
    */
   Class<?> fallback() default void.class;
   /**
    * Define a fallback factory for the specified Feign client interface. The fallback
    * factory must produce instances of fallback classes that implement the interface
    * annotated by {@link FeignClient}. The fallback factory must be a valid spring
    * bean.
    *
    * @see feign.hystrix.FallbackFactory for details.
    */
   Class<?> fallbackFactory() default void.class;
   /**
    * Path prefix to be used by all method-level mappings. Can be used with or without
    * <code>@RibbonClient</code>.
    */
   String path() default "";
   /**
    * Whether to mark the feign proxy as a primary bean. Defaults to true.
    */
   boolean primary() default true;
}

通過FeignClient註解的屬性,可以看到針對單個Feign客戶端可以做自定義的配置。

3,定義客戶端介面的註解

在Feign中需要定義http介面的辦法,註解是個好解決方案。這裡就看到Contract的介面,解析這些註解用的,下麵是抽象類BaseContract,它有預設實現,即Contract.Default,解析了自定義註解:feign.Headers,feign.RequestLine,feign.Body,feign.Param,feign.QueryMap,feign.HeaderMap,這些註解都是用來定義描述http客戶端提供的介面信息的。

但是因為這裡預設將Feign和Spring Cloud體系中使用,而提供了SpringMvcContract類來解析使用的註解,而這個註解就是RequestMapping。這個註解使用過spring mvc的同學必然非常熟悉,這裡就是利用了這個註解的定義進行解析,只是功能上並不是和spring保持完全一致,畢竟它這裡只需要考慮將介面信息定義出來即可。

在SpringMvcContract的代碼里,可以看到解析RequestMapping註解屬性的邏輯代碼,如此在使用中可以直接使用RequestMapping來定義介面。

  • value屬性和path屬性定義介面路徑
  • method屬性配置HTTP請求方法
  • params屬性在feign中不支持
  • headers屬性配置http頭信息
  • consumes屬性配置http頭信息,只解析使用配置了 Content-Type 屬性的值
  • produces屬性配置http頭信息,只解析使用配置了 Accept 屬性的值
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Mapping
public @interface RequestMapping {
   /**
    * Assign a name to this mapping.
    * <p><b>Supported at the type level as well as at the method level!</b>
    * When used on both levels, a combined name is derived by concatenation
    * with "#" as separator.
    * @see org.springframework.web.servlet.mvc.method.annotation.MvcUriComponentsBuilder
    * @see org.springframework.web.servlet.handler.HandlerMethodMappingNamingStrategy
    */
   String name() default "";
   /**
    * The primary mapping expressed by this annotation.
    * <p>In a Servlet environment this is an alias for {@link #path}.
    * For example {@code @RequestMapping("/foo")} is equivalent to
    * {@code @RequestMapping(path="/foo")}.
    * <p>In a Portlet environment this is the mapped portlet modes
    * (i.e. "EDIT", "VIEW", "HELP" or any custom modes).
    * <p><b>Supported at the type level as well as at the method level!</b>
    * When used at the type level, all method-level mappings inherit
    * this primary mapping, narrowing it for a specific handler method.
    */
   @AliasFor("path")
   String[] value() default {};
   /**
    * In a Servlet environment only: the path mapping URIs (e.g. "/myPath.do").
    * Ant-style path patterns are also supported (e.g. "/myPath/*.do").
    * At the method level, relative paths (e.g. "edit.do") are supported within
    * the primary mapping expressed at the type level. Path mapping URIs may
    * contain placeholders (e.g. "/${connect}")
    * <p><b>Supported at the type level as well as at the method level!</b>
    * When used at the type level, all method-level mappings inherit
    * this primary mapping, narrowing it for a specific handler method.
    * @see org.springframework.web.bind.annotation.ValueConstants#DEFAULT_NONE
    * @since 4.2
    */
   @AliasFor("value")
   String[] path() default {};
   /**
    * The HTTP request methods to map to, narrowing the primary mapping:
    * GET, POST, HEAD, OPTIONS, PUT, PATCH, DELETE, TRACE.
    * <p><b>Supported at the type level as well as at the method level!</b>
    * When used at the type level, all method-level mappings inherit
    * this HTTP method restriction (i.e. the type-level restriction
    * gets checked before the handler method is even resolved).
    * <p>Supported for Servlet environments as well as Portlet 2.0 environments.
    */
   RequestMethod[] method() default {};
   /**
    * The parameters of the mapped request, narrowing the primary mapping.
    * <p>Same format for any environment: a sequence of "myParam=myValue" style
    * expressions, with a request only mapped if each such parameter is found
    * to have the given value. Expressions can be negated by using the "!=" operator,
    * as in "myParam!=myValue". "myParam" style expressions are also supported,
    * with such parameters having to be present in the request (allowed to have
    * any value). Finally, "!myParam" style expressions indicate that the
    * specified parameter is <i>not</i> supposed to be present in the request.
    * <p><b>Supported at the type level as well as at the method level!</b>
    * When used at the type level, all method-level mappings inherit
    * this parameter restriction (i.e. the type-level restriction
    * gets checked before the handler method is even resolved).
    * <p>In a Servlet environment, parameter mappings are considered as restrictions
    * that are enforced at the type level. The primary path mapping (i.e. the
    * specified URI value) still has to uniquely identify the target handler, with
    * parameter mappings simply expressing preconditions for invoking the handler.
    * <p>In a Portlet environment, parameters are taken into account as mapping
    * differentiators, i.e. the primary portlet mode mapping plus the parameter
    * conditions uniquely identify the target handler. Different handlers may be
    * mapped onto the same portlet mode, as long as their parameter mappings differ.
    */
   String[] params() default {};
   /**
    * The headers of the mapped request, narrowing the primary mapping.
    * <p>Same format for any environment: a sequence of "My-Header=myValue" style
    * expressions, with a request only mapped if each such header is found
    * to have the given value. Expressions can be negated by using the "!=" operator,
    * as in "My-Header!=myValue". "My-Header" style expressions are also supported,
    * with such headers having to be present in the request (allowed to have
    * any value). Finally, "!My-Header" style expressions indicate that the
    * specified header is <i>not</i> supposed to be present in the request.
    * <p>Also supports media type wildcards (*), for headers such as Accept
    * and Content-Type. For instance,
    * <pre class="code">
    * &#064;RequestMapping(value = "/something", headers = "content-type=text/*")
    * </pre>
    * will match requests with a Content-Type of "text/html", "text/plain", etc.
    * <p><b>Supported at the type level as well as at the method level!</b>
    * When used at the type level, all method-level mappings inherit
    * this header restriction (i.e. the type-level restriction
    * gets checked before the handler method is even resolved).
    * <p>Maps against HttpServletRequest headers in a Servlet environment,
    * and against PortletRequest properties in a Portlet 2.0 environment.
    * @see org.springframework.http.MediaType
    */
   String[] headers() default {};
   /**
    * The consumable media types of the mapped request, narrowing the primary mapping.
    * <p>The format is a single media type or a sequence of media types,
    * with a request only mapped if the {@code Content-Type} matches one of these media types.
    * Examples:
    * <pre class="code">
    * consumes = "text/plain"
    * consumes = {"text/plain", "application/*"}
    * </pre>
    * Expressions can be negated by using the "!" operator, as in "!text/plain", which matches
    * all requests with a {@code Content-Type} other than "text/plain".
    * <p><b>Supported at the type level as well as at the method level!</b>
    * When used at the type level, all method-level mappings override
    * this consumes restriction.
    * @see org.springframework.http.MediaType
    * @see javax.servlet.http.HttpServletRequest#getContentType()
    */
   String[] consumes() default {};
   /**
    * The producible media types of the mapped request, narrowing the primary mapping.
    * <p>The format is a single media type or a sequence of media types,
    * with a request only mapped if the {@code Accept} matches one of these media types.
    * Examples:
    * <pre class="code">
    * produces = "text/plain"
    * produces = {"text/plain", "application/*"}
    * produces = "application/json; charset=UTF-8"
    * </pre>
    * <p>It affects the actual content type written, for example to produce a JSON response
    * with UTF-8 encoding, {@code "application/json; charset=UTF-8"} should be used.
    * <p>Expressions can be negated by using the "!" operator, as in "!text/plain", which matches
    * all requests with a {@code Accept} other than "text/plain".
    * <p><b>Supported at the type level as well as at the method level!</b>
    * When used at the type level, all method-level mappings override
    * this produces restriction.
    * @see org.springframework.http.MediaType
    */
   String[] produces() default {};
}

和註解RequestMapping組合使用在傳參的註解目前包含:PathVariable,RequestHeader,RequestParam。

PathVariable:url占位符參數綁定
RequestHeader:可以設置業務header
RequestParam:將傳參映射到http請求的參數,get/post請求都支持

關於RequestParam,前面有文章涉及到細節:鏈接

結束

先看一眼將涉及到的註解,通過這些註解,我們可以大致瞭解到Feign能提供的能力範圍和實現機制,而對應這些註解的源碼在後續文章中也將一一學習到。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • Failed to execute 'setLocalDescription' on 'RTCPeerConnection': Failed to parse SessionDescription. a=msid: Missing track ID in msid attribute. ...
  • docker簡介 Docker 是一個開源的應用容器引擎,基於 Go 語言 並遵從Apache2.0協議開源。Docker 可以讓開發者打包他們的應用以及依賴包到一個輕量級、可移植的容器中,然後發佈到任何流行的 Linux 機器上,也可以實現虛擬化。 在Linux上安裝Docker;1.輸入yum ...
  • 分散式緩存Hazelcast案例一Hazelcast IMDG Architecture 今天先到這兒,希望對您技術領導力, 企業管理,物聯網, 系統架構設計與評估,團隊管理, 項目管理, 產品管理,團隊建設 有參考作用 , 您可能感興趣的文章: 2017-2018年Scrum狀態調查報告2016年... ...
  • collections模塊 namedtuple #命名元組 #生成可以使用名字來訪問元素內容的tuple from collections import namedtuple Point = namedtuple('point',['x','y']) #一個點的命名元組 p = Point(1,2... ...
  • 前言 RabbitMQ是消息隊列中間件(Message Queue Middleware)中一種,工作雖然有用到,但是卻沒有形成很好的整體包括,主要是一些基礎概念的認識,這裡通過閱讀《RabbitMQ實戰指南》整理筆記併進行代碼實踐,更好地理解RabbitMQ! 本文只通過講解RabbitMQ的一些 ...
  • 原 推薦10個Java方向最熱門的開源項目(8月) 2018年08月28日 17:54:32 SnailClimb在CSDN 閱讀數:849 原 推薦10個Java方向最熱門的開源項目(8月) 2018年08月28日 17:54:32 SnailClimb在CSDN 閱讀數:849 原 推薦10個J ...
  • Anaconda的安裝步驟不在本文的討論中,我們主要是學習一下如何配置conda的鏡像,以及一些問題的解決過程 配置鏡像 在conda安裝好之後,預設的鏡像是官方的,由於官網的鏡像在境外,我們使用國內的鏡像能夠加快訪問的速度。這裡我選擇了清華的的鏡像。鏡像的地址如下:點我進入tuna 在命令行中運行 ...
  • 1.概述 jupyter記事本是一個基於Web的前端,被分成單個的代碼塊或單元。根據需要,單元可以單獨運行,也可以一次全部運行。這使得我們可以運行某個場景,看到輸出結果,然後回到代碼,根據輸出結果對代碼做出相應的調整(說白了就是可以直接在瀏覽器中編寫Python程式,然後執行程式並輸出結果,是不是感 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...