15、@FeignClient注解源码分析

前言

在上篇文档中@EnableFeignClients注解,扫描@FeignClient注解表示的接口,并将加载到容器中,接下来分析下@FeignClient注解配置项及加载流程。

作用

标记接口为Feign 客户端,Feign 启动会扫描当前接口及注解属性,加载到容器中,执行时,通过IOC中当前注解对应的FactoryBean对象,创建动态代理对象,然后去执行请求流程

属性配置

@Target({

     ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface FeignClient {

    // 指定FeignClient的名称,如果项目使用了Ribbon,name属性会作为微服务的名称,用于服务发现
    @AliasFor("name")
    String value() default "";
    // 
    /** @deprecated */
    @Deprecated
    String serviceId() default "";
    // 每个客户端,对应不用的上下文,这里就是这个上下文的ID,
    // 当name属性一样时,可以自定义这个ID,来解决冲突问题
    String contextId() default "";

    @AliasFor("value")
    String name() default "";

    String qualifier() default "";
    // 指定@FeignClient调用的IP地址,一般都是服务发现,所以不需要配置
    String url() default "";
    // 是否解码404,404时,会返回NULL对象,不建议配置
    boolean decode404() default false;
    // Feign配置类,可以自定义Feign的Encoder、Decoder、LogLevel、Contract
    Class<?>[] configuration() default {

     };
    // 定义容错的处理类,当调用远程接口失败或超时时,
    // 会调用对应接口的容错逻辑,fallback指定的类必须实现@FeignClient标记的接口
    Class<?> fallback() default void.class;
    // 工厂类,用于生成fallback类示例,通过这个属性我们可以实现每个接口通用的容错逻辑,减少重复的代码
    Class<?> fallbackFactory() default void.class;
    // 定义当前FeignClient的统一前缀
    String path() default "";
    // 是否是primary 
    boolean primary() default true;
}

流程解析

还是一样分析FeignClientsRegistrar类。

1. 获取客户端名称

之前分析过,扫描之后,就会在registerFeignClients方法注册客户端,首先会获取客户端名称:
&nbsp;
getClientName方法传入的参数,就是@FeignClient注解配置属性:
&nbsp;

getClientName处理逻辑如下,也就是名称会从配置项中选择,优先级为contextId=》value=》name=》serviceId,没有则会报错。

    // 参数为
    private String getClientName(Map<String, Object> client) {

        if (client == null) {

            return null;
        } else {

            // 先设置名称为 contextId配置项,这里没有为“”
            String value = (String)client.get("contextId");
            // 如果为空查看value配置项
            if (!StringUtils.hasText(value)) {

                value = (String)client.get("value");
            }
             // 如果为空查看name配置项
            if (!StringUtils.hasText(value)) {

                value = (String)client.get("name");
            }
             // 如果为空查看serviceId配置项
            if (!StringUtils.hasText(value)) {

                value = (String)client.get("serviceId");
            }
            // 如果有,则直接返回,没有报错
            if (StringUtils.hasText(value)) {

                return value;
            } else {

                throw new IllegalStateException("Either 'name' or 'value' must be provided in @" + FeignClient.class.getSimpleName());
            }
        }
    }

2. 获取configuration

接着就是将configuration中的配置类加载到BeanDefinitionRegistry中了,这里上篇已经分析过了。
&nbsp;

3. 注册客户端

接着就是registerFeignClient 注册客户端了,首先会将注解属性添加到definition中,这里url、path没有配置,所以都为空,然后注意contextId,没有配置时,默认使用name属性,这也就是为啥多个同名的@FeignClient,没有指定contextId会报错的原因了。
&nbsp;
接着还会设置aliasfactoryBeanObjectType,primary属性:

        // 别名 :order-serviceFeignClient
        String alias = contextId + "FeignClient";
        // factoryBeanObjectType=》account.OrderFeign
        // 告诉factoryBean,要生成对象的类
        beanDefinition.setAttribute("factoryBeanObjectType", className);
        // 是否primary ,也就是@Primary 注解,多个相同Bean时,优先使用这一个
        boolean primary = (Boolean)attributes.get("primary");
        beanDefinition.setPrimary(primary);

最后就到了上篇文档分析的最后一步了。

&nbsp;