声明式接口调用:Feign源码剖析(1)@FeignClient注解
按照SpringCloud集成套路寻找线索
按照Spring的套路,肯定是提供一个
EnableXXX
的注解开启某项技术,Feign当然也不例外,还记得我们在入门篇里的EnableFeignClients
吗?
1 | (RetentionPolicy.RUNTIME) |
@Import(FeignClientsRegistrar.class)
还是熟悉的配方-@Import
,不用多说,这个肯定是处理FeignClient
等注解的类
1 | class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware |
- 这里的
FeignClientsRegistar
实现了Spring的一堆接口,我们先不管那么多,只关心这个registerBeanDefinitions
这里显然就是Spring将扫描到的注解元数据和他的注册表一起交给了我们,让我们根据自己的需要(此处就是处理被@FeignClient
等情况)处理。
1 | class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware |
看看当前注解是不是
EnableFeignClients
如果是,并且指定了
defaultConfiguration
这个属性的话,那就将这个属性处理一下1
2
3
4
5
6
7
8
9
10
11class FeignClientsRegistrar
private void registerClientConfiguration(BeanDefinitionRegistry registry, Object name,
Object configuration) {
BeanDefinitionBuilder builder = BeanDefinitionBuilder
.genericBeanDefinition(FeignClientSpecification.class);
builder.addConstructorArgValue(name);
builder.addConstructorArgValue(configuration);
registry.registerBeanDefinition(
name + "." + FeignClientSpecification.class.getSimpleName(),
builder.getBeanDefinition());
}依旧是熟悉的配方,和Ribbon的处理简直一模一样。将这个
defaultConfiguration
封装成一个FeignClientSpecification
,然后注册到容器中。如果还记得之前的文章,这里肯定在后续会有一个刷新上下文的操作,只有那个时候才会真正的实例化这些bean
1 | class FeignClientsRegistrar |
处理
EnableFeignClients
中指定的basePackages
将所有该路径下被
FeignClient
注解修饰的类做如下处理将用户自定义的
configuration
包装成FeignClientSpecification
注册到Spring上下文中包装一个
FeignClientFactoryBean
代表此类,注册到上下文中去1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37class FeignClientsRegistrar
private void registerFeignClient(BeanDefinitionRegistry registry,
AnnotationMetadata annotationMetadata, Map<String, Object> attributes) {
String className = annotationMetadata.getClassName();
BeanDefinitionBuilder definition = BeanDefinitionBuilder
.genericBeanDefinition(FeignClientFactoryBean.class);
validate(attributes);
definition.addPropertyValue("url", getUrl(attributes));
definition.addPropertyValue("path", getPath(attributes));
String name = getName(attributes);
definition.addPropertyValue("name", name);
String contextId = getContextId(attributes);
definition.addPropertyValue("contextId", contextId);
definition.addPropertyValue("type", className);
definition.addPropertyValue("decode404", attributes.get("decode404"));
definition.addPropertyValue("fallback", attributes.get("fallback"));
definition.addPropertyValue("fallbackFactory", attributes.get("fallbackFactory"));
definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
String alias = contextId + "FeignClient";
AbstractBeanDefinition beanDefinition = definition.getBeanDefinition();
boolean primary = (Boolean) attributes.get("primary"); // has a default, won't be
// null
beanDefinition.setPrimary(primary);
String qualifier = getQualifier(attributes);
if (StringUtils.hasText(qualifier)) {
alias = qualifier;
}
BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className,
new String[] { alias });
BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
}- 这里的处理属于核心逻辑了,一看就是根据注解的属性值,构建出一个
FeignClientFactoryBean
,然后注册到Spring上下文中去
- 这里的处理属于核心逻辑了,一看就是根据注解的属性值,构建出一个
找到了核心组件FeignClientFactoryBean
按照Spring惯例,一般叫xxFactoryBean的东东作用就是为了创建这个bean而提供的一种工厂类抽象。
一般情况下是用不到的,但如果这个bean的构建比较复杂,那使用这个FactoryBean
接口是非常恰当的。他的主要方法就是一个getObject()
,获取这个bean实例,简单粗暴。
1 | class FeignClientFactoryBean |
- 这个
getTarget()
就是我们关心的核心逻辑了,猜测一下肯定是创建了FeignClient(或者代理类?)
1 | class FeignClientFactoryBean |
loadBalance
这是非常关键的一个方法,完成了创建Feign动态代理的逻辑,不过此处我们先不用关心
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16class FeignClientFactoryBean
protected <T> T loadBalance(Feign.Builder builder, FeignContext context,
HardCodedTarget<T> target) {
// 此处获取到的就是那个LoadBalancerFeignClient
Client client = getOptional(context, Client.class);
if (client != null) {
builder.client(client);
// 此处获取到的其实就是那个HystrixTargeter
Targeter targeter = get(context, Targeter.class);
return targeter.target(this, builder, context, target);
}
throw new IllegalStateException(
"No Feign Client for loadBalancing defined. Did you forget to include spring-cloud-starter-netflix-ribbon?");
}