Feign在Spring Cloud體系中被整合進來做爲web service客戶端,使用HTTP請求遠程服務時能就像調用本地方法,可見在將來一段時間內,大多數Spring Cloud架構的微服務之間調用都會使用Feign來完成。html
因此準備完整解讀一遍Feign的源碼,讀源碼,我我的以爲一方面,能夠在使用的基礎上對內部實現的細節的瞭解,提升使用時對組件功能的信心,另外一方面,開源組件的代碼質量通常都比較高,對代碼結構組織通常比較優秀,還有,內部實現的一些細節可能優秀開發的思考所得,值得仔細揣摩。我對後兩個好處比較感興趣,雖然現現在寫的代碼好與壞,其實不會太多的影響平時的工做,不過若是心裏是真的愛代碼,也會不斷追求細節的極致。java
由於是Spring Cloud體系下使用Feign,必然會涉及到:服務註冊(Euraka),負載均衡(Rinbon),熔斷器(Hystrix)等方面的整合知識。web
另外,能思考的高度和廣度必然有限,可是源碼閱讀學習又難以共同參與,因此恰好你也在這個位置,有本身的思路或想法,不吝留言。spring
大流程上,就是掃描FeignClient註解的接口,將接口方法動態代理成http客戶端的接口請求操做就完成了Feign的目的。因此一個FeignClient註解對應一個客戶端。express
@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使用場景進行了解釋,能夠作參考學習。
每一個FeignClient表明一個http客戶端,定義的每個方法對應這個一個接口。
/** * 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客戶端能夠作自定義的配置。
在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來定義接口。
@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"> * @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能提供的能力範圍和實現機制,而對應這些註解的源碼在後續文章中也將一一學習到。