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

  • 相关阅读:
    PhoneApplicationPage 之观察 触摸事件 GIS
    读书笔记 之 image GIS
    button 样式 GIS
    TextBlock GIS
    textgame GIS
    c#对List或ListArray或string组数 用linq进行分组统计
    彗星撞地球 怀念下Warez组织的经典力作(15G动画压缩成64Kb的那个)
    .NET Framework各版本独立下载.NET Framework 3.5下载.NET Framework 2.0下载
    asp.net 导出excel 表之后 按钮 页面控件失效不可用,没反应的解决办法。
    SQL Sever 各版本下载 SQL Server 2012下载SQL Server 2008下载SQL Server 2005 下载SQL Server 2000 下载
  • 原文地址:https://www.cnblogs.com/killbug/p/10389530.html
Copyright © 2011-2022 走看看