SpringCloud原理 OpenFeign之FeignClient动态代理生成原理三

pivoteic
发布于 2022-6-13 17:50
浏览
0收藏

 

二、Feign客户端接口动态代理的生成源码剖析

 

(1)FeignAutoConfiguration源码剖析

 

FeignAutoConfiguration是feign在整个springcloud的配置类,我拎出这里面比较核心的代码。

@Autowired(required = false)
private List<FeignClientSpecification> configurations = new ArrayList<>();
@Bean
public FeignContext feignContext() {
    FeignContext context = new FeignContext();
    context.setConfigurations(this.configurations);
    return context;
}

 

注入了一堆FeignClientSpecification,FeignClientSpecification这玩意就是上文提到的调用registerClientConfiguration的时候注入到spring容器中的,一个Feign客户端的配置一个FeignClientSpecification,所以是个集合,然后封装到FeignContext中,最后将FeignContext注入到spring容器中。

 

FeignContext也是很重要的一个东西,我们来分析一下它的源码

public class FeignContext extends NamedContextFactory<FeignClientSpecification> {
  public FeignContext() {
    super(FeignClientsConfiguration.class, "feign", "feign.client.name");
  }

}

 

FeignContext继承了NamedContextFactory,构造的时候,传入了FeignClientsConfiguration,这个玩意也很重要,别急,我们慢慢来分析它们的作用。

 

(2)NamedContextFactory源码剖析

 

我先来说结论,NamedContextFactory的作用是用来进行配置隔离的,ribbon和feign的配置隔离都依赖这个抽象类。

 

何为配置隔离,因为每个Feign客户端都有可能有自己的配置,从@FeignClient注解的属性configuration可以看出,所以写了这个类,用来隔离每个客户端的配置,这就是为什么在构造FeignContext传入一堆FeignClientSpecification的原因,这里封装了每个客户端的配置类。

 

那是怎么实现的呢,我拎出来一部分核心的源码,不重要的我就忽略了。

public abstract class NamedContextFactory<C extends NamedContextFactory.Specification>
    implements DisposableBean, ApplicationContextAware {

  private final String propertySourceName;

  private final String propertyName;

  private Map<String, AnnotationConfigApplicationContext> contexts = new ConcurrentHashMap<>();
  private Map<String, C> configurations = new ConcurrentHashMap<>();
  
  //父类 ApplicationContext ,也就是springboot所使用的ApplicationContext
  private ApplicationContext parent;
  // 这个是默认的额配置类
  private Class<?> defaultConfigType;

  public NamedContextFactory(Class<?> defaultConfigType, String propertySourceName,
      String propertyName) {
    this.defaultConfigType = defaultConfigType;
    this.propertySourceName = propertySourceName;
    this.propertyName = propertyName;
  }

  @Override
  public void setApplicationContext(ApplicationContext parent) throws BeansException {
    this.parent = parent;
  }

  public void setConfigurations(List<C> configurations) {
    for (C client : configurations) {
      this.configurations.put(client.getName(), client);
    }
  }

  public Set<String> getContextNames() {
    return new HashSet<>(this.contexts.keySet());
  }

  protected AnnotationConfigApplicationContext getContext(String name) {
    if (!this.contexts.containsKey(name)) {
      synchronized (this.contexts) {
        if (!this.contexts.containsKey(name)) {
          this.contexts.put(name, createContext(name));
        }
      }
    }
    return this.contexts.get(name);
  }

  protected AnnotationConfigApplicationContext createContext(String name) {
    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
    if (this.configurations.containsKey(name)) {
      for (Class<?> configuration : this.configurations.get(name)
          .getConfiguration()) {
        context.register(configuration);
      }
    }
    for (Map.Entry<String, C> entry : this.configurations.entrySet()) {
      if (entry.getKey().startsWith("default.")) {
        for (Class<?> configuration : entry.getValue().getConfiguration()) {
          context.register(configuration);
        }
      }
    }
    context.register(PropertyPlaceholderAutoConfiguration.class,
        this.defaultConfigType);
    context.getEnvironment().getPropertySources().addFirst(new MapPropertySource(
        this.propertySourceName,
        Collections.<String, Object>singletonMap(this.propertyName, name)));
    if (this.parent != null) {
      // Uses Environment from parent as well as beans
      context.setParent(this.parent);
      // jdk11 issue
      // https://github.com/spring-cloud/spring-cloud-netflix/issues/3101
      context.setClassLoader(this.parent.getClassLoader());
    }
    context.setDisplayName(generateDisplayName(name));
    context.refresh();
    return context;
  }
  
  /**
   * Specification with name and configuration.
   */
  public interface Specification {

    String getName();

    Class<?>[] getConfiguration();

  }

}

 

分析一下每个成员变量的作用:

 

contexts:一个客户端一个对应的AnnotationConfigApplicationContext

configurations:一个客户端一个配置类的封装,对应到Feign的就是FeignClientSpecification

parent:springboot真正启动的就是这个ApplicationContext

defaultConfigType:默认的配置类,对应Feign就是构造FeignContext是传入的FeignClientsConfiguration

 

分析一下核心的方法:

 

getContext:这个方法很简单,就是根据客户端名称从contexts获取对应的AnnotationConfigApplicationContext,获取不到就去创建一个,然后放入contexts

createContext:就是直接new了一个AnnotationConfigApplicationContext对象,然后按照按照配置的优先级顺序,一步步放入配置类,最后放入parent容器,也就是说每个客户端对应的容器,都有一个共同的父容器,同时如果每个客户端对应的容器获取不到的配置,都会再次从父容器中获取。这个结论还是很重要的。

 

其实所谓的配置隔离就是为每个客户端构建一个AnnotationConfigApplicationContext,然后基于这个ApplicationContext来解析配置类,这样就实现了配置隔离。

 

不知道大家有么有遇到过这个坑,就是在spring cloud环境中,监听类似ContextRefreshedEvent这种事件的时候,这个事件会无缘无故地触发很多次,其实就是这个原因就在这,因为spring的事件是有传播机制的,每个客户端对应的容器都要进行refresh,refresh完就会发这个事件,然后这个事件就会传给parent容器,也就是springboot启动的容器,就会再次触发,所以如果客户端很多,那么就会触发很多次。解决办法就是进行唯一性校验,只能启动一次就行了。

 

文章转自公众号:三友的java日记

已于2022-6-13 17:50:59修改
收藏
回复
举报
回复
    相关推荐