Spring Cloud Feign 源碼分析 - FeignClientFactoryBean

一. 前言

關(guān)于Feign的啟動原理分析,參照另一篇Spring Cloud Feign 源碼分析 - feign啟動原理

二. 源碼分析

書接上文,上篇最后提到所有帶@FeignClient注解的interface都被封裝成FeignClientFactoryBean的BeanDefinition。從名字上可以得知這個類是一個FactoryBean。關(guān)于FactoryBean的介紹參考...
因此直接找getObject()。

@Override
    public Object getObject() throws Exception {
        return getTarget();
    }
/**
     * @param <T> the target type of the Feign client
     * @return a {@link Feign} client created with the specified data and the context information
     */
    <T> T getTarget() {
        FeignContext context = applicationContext.getBean(FeignContext.class);
        Feign.Builder builder = feign(context);

        if (!StringUtils.hasText(this.url)) {
            if (!this.name.startsWith("http")) {
                url = "http://" + this.name;
            }
            else {
                url = this.name;
            }
            url += cleanPath();
            return (T) loadBalance(builder, context, new HardCodedTarget<>(this.type,
                    this.name, 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));
    }

getTarget方法首先獲取FeignContext的對象,基于這個context對當(dāng)前feign的配置信息存放到Builder中。

  FeignContext context = applicationContext.getBean(FeignContext.class);

首先實例化bean:FeignContext
FeignContext的定義在FeignAutoConfiguration

@Configuration
@ConditionalOnClass(Feign.class)
@EnableConfigurationProperties({FeignClientProperties.class, FeignHttpClientProperties.class})
public class FeignAutoConfiguration {

    @Autowired(required = false)
    private List<FeignClientSpecification> configurations = new ArrayList<>();

    @Bean
    public HasFeatures feignFeature() {
        return HasFeatures.namedFeature("Feign", Feign.class);
    }

    @Bean
    public FeignContext feignContext() {
        FeignContext context = new FeignContext();
        context.setConfigurations(this.configurations);
        return context;
    }

第一次除了創(chuàng)建新的FeignContext對象之外,還設(shè)置了一組configurations,
這組configurations是FeignClientSpecification類型,通過autowired注入。
在掃描EnableFeignClients和各個FeignClient時,將configuration對應(yīng)的class封裝成了FeignClientSpecification的BeanDefinition,這里從容器中取出來創(chuàng)建對象注入到configurations


image.png
image.png

通過斷點可以看到這里有15個FeignClientSpecification的對象


image.png

一個是default.開頭的在啟動類里配置的configuration,剩下的都是FeignClient的configuration。

public class FeignContext extends NamedContextFactory<FeignClientSpecification> {

    public FeignContext() {
        super(FeignClientsConfiguration.class, "feign", "feign.client.name");
    }
}

FeignContext繼承了NamedContextFactory,對應(yīng)的范型就是FeignClientSpecification,看下NamedContextFactory構(gòu)造方法

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

    public interface Specification {
        String getName();

        Class<?>[] getConfiguration();
    }

    private Map<String, AnnotationConfigApplicationContext> contexts = new ConcurrentHashMap<>();

    private Map<String, C> configurations = new ConcurrentHashMap<>();

    private ApplicationContext parent;

    private Class<?> defaultConfigType;
    private final String propertySourceName;
    private final String propertyName;

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

這里設(shè)置了默認的defaultConfigType,feign里用的是FeignClientsConfiguration,定義了一系列的默認值。

    //分析這一句
    Feign.Builder builder = feign(context);

    protected Feign.Builder feign(FeignContext context) {
        FeignLoggerFactory loggerFactory = get(context, FeignLoggerFactory.class);
        Logger logger = loggerFactory.create(this.type);

        // @formatter:off
        Feign.Builder builder = get(context, Feign.Builder.class)
                // required values
                .logger(logger)
                .encoder(get(context, Encoder.class))
                .decoder(get(context, Decoder.class))
                .contract(get(context, Contract.class));
        // @formatter:on

        configureFeign(context, builder);
        return builder;
    }

    protected <T> T get(FeignContext context, Class<T> type) {
        T instance = context.getInstance(this.contextId, type);
        if (instance == null) {
            throw new IllegalStateException("No bean found of type " + type + " for " + this.contextId);
        }
        return instance;
    }

在獲取到FeignContext之后,開始封裝Feign.Builder。
首先通過context實例化FeignLoggerFactory的對象,因為context是NamedContextFactory的子類,會給每個contextId創(chuàng)建一個獨立的AnnotationConfigApplicationContext上下文,每一個k-v會存儲在FeignContext的全局context中,key就是contextId

public <T> T getInstance(String name, Class<T> type) {
        AnnotationConfigApplicationContext context = getContext(name);
        if (BeanFactoryUtils.beanNamesForTypeIncludingAncestors(context,
                type).length > 0) {
            return context.getBean(type);
        }
        return null;
    }

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();
        //1 給FeignClient設(shè)置獨立的configuration
        if (this.configurations.containsKey(name)) {
            for (Class<?> configuration : this.configurations.get(name)
                    .getConfiguration()) {
                context.register(configuration);
            }
        }
        //2 給FeignClient設(shè)置全局defaultConfiguration
        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
            //給子上下文設(shè)置parentContext
            context.setParent(this.parent);
        }
        context.setDisplayName(generateDisplayName(name));
        //子上下文調(diào)用refresh方法,刷新操作
        context.refresh();
        return context;
    }

這三個方法的實現(xiàn)完全體現(xiàn)了NamedContextFactory的作用:
給每個name創(chuàng)建一個單獨的ApplicationContext子上下文對象,后續(xù)凡是這個name的ioc操作,都由獨立的ApplicationContext來完成,name之間的context相互隔離。所有的子上下文保存在了Map<String, AnnotationConfigApplicationContext> contexts中。

在創(chuàng)建Context時,補充了configuration的設(shè)置:
首先(1的位置),從全局的configurations查找是否定義了只對當(dāng)前name生效的configuration,也就是判斷在當(dāng)前name所屬的FeignClient注解上是否定義了configuration。如果定義過,將這個configuration的Class封裝成BeanDefinition注冊到本name的子上下文中。

接著(2的位置),從全局的configurations查找是否定義了全局配置,也就是@EnableFeignClients的defaultConfiguration的值,這里固定前綴是default.。
如果也存在,就也將這個defaultConfiguration的Class封裝成BeanDefinition注冊到本name的子上下文中。

第一次調(diào)用完畢get方法后,給每個FeignClient創(chuàng)建的FeignContext就完成了configuration初始化的動作,后面的所有操作,如配置encoder、decoder都是給當(dāng)前的子上下文內(nèi)注冊BeanDefinition。最后將所有配置封裝成Builder返回。

三. 請求調(diào)度

在getTarget()構(gòu)造完成builder屬性之后,開始了整個請求調(diào)度過程。

先看第一段:

        if (!StringUtils.hasText(this.url)) {
            if (!this.name.startsWith("http")) {
                url = "http://" + this.name;
            }
            else {
                url = this.name;
            }
            url += cleanPath();
            return (T) loadBalance(builder, context, new HardCodedTarget<>(this.type,
                    this.name, url));
        }

如果沒有url屬性,就用name來處理,把http:// + name + path 拼裝成url,執(zhí)行l(wèi)oadBalance()

protected <T> T loadBalance(Feign.Builder builder, FeignContext context,
            HardCodedTarget<T> target) {
        Client client = getOptional(context, Client.class);
        if (client != null) {
            builder.client(client);
            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?");
    }

首先實例化Client的bean對象,默認返回LoadBalancerFeignClient的實例。

@Configuration
class DefaultFeignLoadBalancedConfiguration {
    @Bean
    @ConditionalOnMissingBean
    public Client feignClient(CachingSpringLoadBalancerFactory cachingFactory,
                              SpringClientFactory clientFactory) {
        return new LoadBalancerFeignClient(new Client.Default(null, null),
                cachingFactory, clientFactory);
    }
}
public class LoadBalancerFeignClient implements Client {

    //...
    private final Client delegate;

    public LoadBalancerFeignClient(Client delegate, CachingSpringLoadBalancerFactory lbClientFactory, SpringClientFactory clientFactory) {
        this.delegate = delegate;
        this.lbClientFactory = lbClientFactory;
        this.clientFactory = clientFactory;
    }

從LoadBalancerFeignClient的構(gòu)造方法可以看到,這里使用了delegate的設(shè)計模式來代理Client.Default,擴展execute的實現(xiàn)。

然后則繼續(xù)實例化Targeter的bean。默認有兩種實現(xiàn)類。


    @Configuration
    @ConditionalOnClass(name = "feign.hystrix.HystrixFeign")
    protected static class HystrixFeignTargeterConfiguration {
        @Bean
        @ConditionalOnMissingBean
        public Targeter feignTargeter() {
            return new HystrixTargeter();
        }
    }

    @Configuration
    @ConditionalOnMissingClass("feign.hystrix.HystrixFeign")
    protected static class DefaultFeignTargeterConfiguration {
        @Bean
        @ConditionalOnMissingBean
        public Targeter feignTargeter() {
            return new DefaultTargeter();
        }
    }

我這里返回HystrixTargeter。調(diào)用target方法。

@Override
    public <T> T target(FeignClientFactoryBean factory, Feign.Builder feign, FeignContext context,
                        Target.HardCodedTarget<T> target) {
        if (!(feign instanceof feign.hystrix.HystrixFeign.Builder)) {
            return feign.target(target);
        }
        feign.hystrix.HystrixFeign.Builder builder = (feign.hystrix.HystrixFeign.Builder) feign;
        SetterFactory setterFactory = getOptional(factory.getName(), context,
            SetterFactory.class);
        if (setterFactory != null) {
            builder.setterFactory(setterFactory);
        }
        Class<?> fallback = factory.getFallback();
        if (fallback != void.class) {
            return targetWithFallback(factory.getName(), context, target, builder, fallback);
        }
        Class<?> fallbackFactory = factory.getFallbackFactory();
        if (fallbackFactory != void.class) {
            return targetWithFallbackFactory(factory.getName(), context, target, builder, fallbackFactory);
        }

        return feign.target(target);
    }

這里重點看下feign.target(target)的實現(xiàn)。

public abstract class Feign {

//...
    
    public <T> T target(Target<T> target) {
      return build().newInstance(target);
    }

    public Feign build() {
      SynchronousMethodHandler.Factory synchronousMethodHandlerFactory =
          new SynchronousMethodHandler.Factory(client, retryer, requestInterceptors, logger,
              logLevel, decode404, closeAfterDecode, propagationPolicy);
      ParseHandlersByName handlersByName =
          new ParseHandlersByName(contract, options, encoder, decoder, queryMapEncoder,
              errorDecoder, synchronousMethodHandlerFactory);
      return new ReflectiveFeign(handlersByName, invocationHandlerFactory, queryMapEncoder);
    }
  }
}

可以看到,通過build()構(gòu)造了一個ReflectiveFeign的對象,將一系列feign的參數(shù)封裝成了SynchronousMethodHandler和ParseHandlersByName。封裝的這兩個對象都是為了給后面newInstance用的。

newInstance返回了擴展后的Targeter的代理類。


image.png

下面介紹下newInstance的詳細過程。

 @Override
  public <T> T newInstance(Target<T> target) {
    Map<String, MethodHandler> nameToHandler = targetToHandlersByName.apply(target);
    Map<Method, MethodHandler> methodToHandler = new LinkedHashMap<Method, MethodHandler>();
    List<DefaultMethodHandler> defaultMethodHandlers = new LinkedList<DefaultMethodHandler>();

    for (Method method : target.type().getMethods()) {
      if (method.getDeclaringClass() == Object.class) {
        continue;
      } else if (Util.isDefault(method)) {
        DefaultMethodHandler handler = new DefaultMethodHandler(method);
        defaultMethodHandlers.add(handler);
        methodToHandler.put(method, handler);
      } else {
        methodToHandler.put(method, nameToHandler.get(Feign.configKey(target.type(), method)));
      }
    }
    InvocationHandler handler = factory.create(target, methodToHandler);
    T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(),
        new Class<?>[] {target.type()}, handler);

    for (DefaultMethodHandler defaultMethodHandler : defaultMethodHandlers) {
      defaultMethodHandler.bindTo(proxy);
    }
    return proxy;
  }
public Map<String, MethodHandler> apply(Target key) {
      List<MethodMetadata> metadata = contract.parseAndValidatateMetadata(key.type());
      Map<String, MethodHandler> result = new LinkedHashMap<String, MethodHandler>();
      for (MethodMetadata md : metadata) {
        BuildTemplateByResolvingArgs buildTemplate;
        if (!md.formParams().isEmpty() && md.template().bodyTemplate() == null) {
          buildTemplate = new BuildFormEncodedTemplateFromArgs(md, encoder, queryMapEncoder);
        } else if (md.bodyIndex() != null) {
          buildTemplate = new BuildEncodedTemplateFromArgs(md, encoder, queryMapEncoder);
        } else {
          buildTemplate = new BuildTemplateByResolvingArgs(md, queryMapEncoder);
        }
        result.put(md.configKey(),
            factory.create(key, md, buildTemplate, options, decoder, errorDecoder));
      }
      return result;
    }
  }

apply方法就是給feignClient的每個方法都封裝了一個SynchronousMethodHandler,
factory.create(...)就是為了根據(jù)當(dāng)前方法的各個參數(shù)+new SynchronousMethodHandler.Factory定義的默認參數(shù)來構(gòu)造SynchronousMethodHandler
key對應(yīng)的是類名#方法名,如:MasterClientLocal#getPersons()。
for循環(huán)則是為了封裝methodToHandler,k-v分別是reflect的Method和SynchronousMethodHandler。遍歷完成后,構(gòu)建一個InvocationHandler的實現(xiàn)類:FeignInvocationHandler

 @Override
    public InvocationHandler create(Target target, Map<Method, MethodHandler> dispatch) {
      return new ReflectiveFeign.FeignInvocationHandler(target, dispatch);
    }
  }
static class FeignInvocationHandler implements InvocationHandler {

    private final Target target;
    private final Map<Method, MethodHandler> dispatch;

    FeignInvocationHandler(Target target, Map<Method, MethodHandler> dispatch) {
      this.target = checkNotNull(target, "target");
      this.dispatch = checkNotNull(dispatch, "dispatch for %s", target);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
      if ("equals".equals(method.getName())) {
        try {
          Object otherHandler =
              args.length > 0 && args[0] != null ? Proxy.getInvocationHandler(args[0]) : null;
          return equals(otherHandler);
        } catch (IllegalArgumentException e) {
          return false;
        }
      } else if ("hashCode".equals(method.getName())) {
        return hashCode();
      } else if ("toString".equals(method.getName())) {
        return toString();
      }

      return dispatch.get(method).invoke(args);
    }

通過傳入target和dispatch,其實本質(zhì)就是在調(diào)用SynchronousMethodHandler的invoke方法。而invoke方法則是擴展了http的調(diào)用動作,包括請求重試,decode處理,decode404判斷等。

@Override
  public Object invoke(Object[] argv) throws Throwable {
    RequestTemplate template = buildTemplateFromArgs.create(argv);
    Retryer retryer = this.retryer.clone();
    while (true) {
      try {
        return executeAndDecode(template);
      } catch (RetryableException e) {
           //...
          continue;
      }
    }
  }

  Object executeAndDecode(RequestTemplate template) throws Throwable {
    Request request = targetRequest(template);
    Response response;
    long start = System.nanoTime();
    try {
      response = client.execute(request, options);
    } catch (IOException e) {
      //...
    }
    long elapsedTime = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start);

    boolean shouldClose = true;
    try {
      if (Response.class == metadata.returnType()) {
        if (response.body() == null) {
          return response;
        }
        if (response.body().length() == null ||
            response.body().length() > MAX_RESPONSE_BUFFER_SIZE) {
          shouldClose = false;
          return response;
        }
        // Ensure the response body is disconnected
        byte[] bodyData = Util.toByteArray(response.body().asInputStream());
        return response.toBuilder().body(bodyData).build();
      }
      if (response.status() >= 200 && response.status() < 300) {
        if (void.class == metadata.returnType()) {
          return null;
        } else {
          Object result = decode(response);
          shouldClose = closeAfterDecode;
          return result;
        }
      } else if (decode404 && response.status() == 404 && void.class != metadata.returnType()) {
        Object result = decode(response);
        shouldClose = closeAfterDecode;
        return result;
      } else {
        throw errorDecoder.decode(metadata.configKey(), response);
      }
    } catch (IOException e) {
      //...
    } finally {
      if (shouldClose) {
        ensureClosed(response.body());
      }
    }
  }

最重要的執(zhí)行則是client.execute(...);
client有兩種實現(xiàn)類:


image.png

Default是帶url的execute的實現(xiàn),封裝了最普通的http調(diào)用。
LoadBalanceFeignClient是eureka的實現(xiàn),通過獲取server列表來實現(xiàn)loadBalance。
也就是最開始getTarget() 方法的兩段不同的實現(xiàn)過程的最本質(zhì)區(qū)別。

至此,F(xiàn)eignClientFactoryBean的源碼分析告一段落。

四. 總結(jié)

  1. getTarget的最終目的是給每個feignClient的方法封裝一個HardCodedTarget的代理對象。代理的目的是實現(xiàn)通用擴展(重試、decode、decode404等)和loadbalance擴展
  2. 區(qū)別在于feign的注解里是否有url的屬性
  3. 如果有則執(zhí)行的是Default的實現(xiàn)類,封裝普通的http調(diào)用,
  4. 如果沒有url則執(zhí)行LoadBalanceFeignClient的execute方法,包裝了一層獲取server列表來實現(xiàn)負載均衡的功能。
  5. 這里的擴展方式使用的是delegate的設(shè)計模式,如果想繼續(xù)擴展,依然可以沿用這種方式。

本人通過delegate方式在此基礎(chǔ)上實現(xiàn)了traceId的跨feign傳遞。將在下一篇文章中做具體說明。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容