按照SpringCloud集成套路寻找线索

按照Spring的套路,肯定是提供一个EnableXXX的注解开启某项技术,Feign当然也不例外,还记得我们在入门篇里的EnableFeignClients吗?

1
2
3
4
5
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(FeignClientsRegistrar.class)
public @interface EnableFeignClients {
  • @Import(FeignClientsRegistrar.class)
    还是熟悉的配方-@Import,不用多说,这个肯定是处理FeignClient等注解的类
1
2
3
4
5
6
7
class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
registerDefaultConfiguration(metadata, registry);
registerFeignClients(metadata, registry);
}]
  • 这里的FeignClientsRegistar实现了Spring的一堆接口,我们先不管那么多,只关心这个registerBeanDefinitions
    这里显然就是Spring将扫描到的注解元数据和他的注册表一起交给了我们,让我们根据自己的需要(此处就是处理被@FeignClient等情况)处理。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware

private void registerDefaultConfiguration(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
// 此处显然就是根据EnableFeignClients这个注解名,从当前这个注解元数据中获取对应的属性
Map<String, Object> defaultAttrs = metadata
.getAnnotationAttributes(EnableFeignClients.class.getName(), true);

if (defaultAttrs != null && defaultAttrs.containsKey("defaultConfiguration")) {
String name;
if (metadata.hasEnclosingClass()) {
name = "default." + metadata.getEnclosingClassName();
}
else {
name = "default." + metadata.getClassName();
}
registerClientConfiguration(registry, name,
defaultAttrs.get("defaultConfiguration"));
}
}
  • 看看当前注解是不是EnableFeignClients

  • 如果是,并且指定了defaultConfiguration这个属性的话,那就将这个属性处理一下

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    class 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
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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
class FeignClientsRegistrar

public void registerFeignClients(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
ClassPathScanningCandidateComponentProvider scanner = getScanner();
scanner.setResourceLoader(this.resourceLoader);

Set<String> basePackages;

Map<String, Object> attrs = metadata
.getAnnotationAttributes(EnableFeignClients.class.getName());

AnnotationTypeFilter annotationTypeFilter = new AnnotationTypeFilter(
FeignClient.class);
final Class<?>[] clients = attrs == null ? null
: (Class<?>[]) attrs.get("clients");
if (clients == null || clients.length == 0) {
scanner.addIncludeFilter(annotationTypeFilter);
// 这里就是处理下EnableFeignClients里咱们指定的那个basePackages,将咱们配的属性值收集起来
basePackages = getBasePackages(metadata);
}
else {
final Set<String> clientClasses = new HashSet<>();
basePackages = new HashSet<>();
for (Class<?> clazz : clients) {
basePackages.add(ClassUtils.getPackageName(clazz));
clientClasses.add(clazz.getCanonicalName());
}
AbstractClassTestingTypeFilter filter = new AbstractClassTestingTypeFilter() {
@Override
protected boolean match(ClassMetadata metadata) {
String cleaned = metadata.getClassName().replaceAll("\\$", ".");
return clientClasses.contains(cleaned);
}
};
scanner.addIncludeFilter(
new AllTypeFilter(Arrays.asList(filter, annotationTypeFilter)));
}

// 这里就到了真正处理basePackages的地方了
// 不难推测,肯定就是将这个路径下的所有被FeignClient注解修饰的类都特殊处理一把
for (String basePackage : basePackages) {
Set<BeanDefinition> candidateComponents = scanner
.findCandidateComponents(basePackage);
for (BeanDefinition candidateComponent : candidateComponents) {
if (candidateComponent instanceof AnnotatedBeanDefinition) {
// verify annotated class is an interface
AnnotatedBeanDefinition beanDefinition = (AnnotatedBeanDefinition) candidateComponent;
AnnotationMetadata annotationMetadata = beanDefinition.getMetadata();
Assert.isTrue(annotationMetadata.isInterface(),
"@FeignClient can only be specified on an interface");

Map<String, Object> attributes = annotationMetadata
.getAnnotationAttributes(
FeignClient.class.getCanonicalName());

String name = getClientName(attributes);
// 这里其实和上面相同,就是把咱们在FeignClient里指定的自定义configuration给包装成那个FeignClientSpecification
// 然后搞到Spring容器中去
registerClientConfiguration(registry, name,
attributes.get("configuration"));

registerFeignClient(registry, annotationMetadata, attributes);
}
}
}
}
  • 处理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
      37
      class 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
2
3
4
5
6
7
class FeignClientFactoryBean
implements FactoryBean<Object>, InitializingBean, ApplicationContextAware

@Override
public Object getObject() throws Exception {
return getTarget();
}
  • 这个getTarget()就是我们关心的核心逻辑了,猜测一下肯定是创建了FeignClient(或者代理类?)
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
37
38
39
class FeignClientFactoryBean

<T> T getTarget() {
FeignContext context = this.applicationContext.getBean(FeignContext.class);
// 就是用Spring之前自动配置的那些Feign组件,比如编解码器、协议之类的东东构建一个Feign的构造器
// 这里还将咱们在配置文件里配置的feign.client开头的那些配置也一并搞到这个Feign构造器里去了
Feign.Builder builder = feign(context);

if (!StringUtils.hasText(this.url)) {
if (!this.name.startsWith("http")) {
this.url = "http://" + this.name;
}
else {
this.url = this.name;
}
// 处理url,最后的url形式是`http://serviceA`
this.url += cleanPath();
return (T) loadBalance(builder, context,
new HardCodedTarget<>(this.type, this.name, this.url));
}

// 如果你在`FeignClient`注解里指定`url`属性,那么会走一段逻辑,这个不常用,我们先不用看了
if (StringUtils.hasText(this.url) && !this.url.startsWith("http")) {
this.url = "http://" + this.url;
}
String url = this.url + cleanPath();
Client client = getOptional(context, Client.class);
if (client != null) {
if (client instanceof LoadBalancerFeignClient) {
// not load balancing because we have a url,
// but ribbon is on the classpath, so unwrap
client = ((LoadBalancerFeignClient) client).getDelegate();
}
builder.client(client);
}
Targeter targeter = get(context, Targeter.class);
return (T) targeter.target(this, builder, context,
new HardCodedTarget<>(this.type, this.name, url));
}
  • loadBalance

    这是非常关键的一个方法,完成了创建Feign动态代理的逻辑,不过此处我们先不用关心

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    class 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?");
    }

总结

FeignClient注解原理

Comments