前言
在上篇文档中@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
方法注册客户端,首先会获取客户端名称:
getClientName
方法传入的参数,就是@FeignClient
注解配置属性:
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
中了,这里上篇已经分析过了。
3. 注册客户端
接着就是registerFeignClient
注册客户端了,首先会将注解属性添加到definition
中,这里url、path没有配置,所以都为空,然后注意contextId
,没有配置时,默认使用name属性,这也就是为啥多个同名的@FeignClient
,没有指定contextId
会报错的原因了。
接着还会设置alias
,factoryBeanObjectType
,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);
最后就到了上篇文档分析的最后一步了。