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

開始

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

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

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

另外,能思考的高度和廣度必然有限,可是源碼閱讀學習又難以共同參與,因此恰好你也在這個位置,有本身的思路或想法,不吝留言。spring

內容

1,EnableFeignClients註解

大流程上,就是掃描FeignClient註解的接口,將接口方法動態代理成http客戶端的接口請求操做就完成了Feign的目的。因此一個FeignClient註解對應一個客戶端。express

  • 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相關的配置。json

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

關於這個註解,咱們能夠理解成導入mvc

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

因此通常@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能提供的能力範圍和實現機制,而對應這些註解的源碼在後續文章中也將一一學習到。

相關文章
相關標籤/搜索