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能提供的能力范围和实现机制,而对应这些注解的源码在后续文章中也将一一学习到。