【Feign】Feign源碼分析(二): FeignClient實例化的過程


  FeignClient實例化的主要目的是為了項目中使用@AutoWired 注解注入的被@FeignClient修飾的接口的實現類,顯然這里是通過動態代理的方式生成接口的動態代理對象,將生成動態代理對象放入Spring容器中

這里的觸發點也就是在FeignClientFactoryBean的getObject()方法中,

一.源碼分析入口 

     1.FeignClientFactoryBean.getObject()

    //獲取@FeignClient修飾接口的代理對象,注入到Spring容器中
    public Object getObject() throws Exception {
        return getTarget();
    }

   2.進入getTarget()方法中

    <T> T getTarget() {
//從Spring容器中,獲取FeignContext上下文的容器,該容器是和Feign相關服務相關的容器,
//每個服務都對應了一個了一個FeignContext,這個FeignContext主要是封裝了FeignClient相關的配置信息;
//FeignContext的初始化操作在@FeignAutoConfiguration中,主要是加載了FeignClientSpecification(注解中的配置信息)和FeignClientsConfiguration(默認的組件Encoder,Decoder,等等)
//因此 FeignContext中具備和動態代理Bean相關的很多屬性
FeignContext context
= this.applicationContext.getBean(FeignContext.class);
//通過FeignContext上下文獲取Feign.Builder對象,FeignBuilder對象也就是創建Feign的構建者對象,源碼解析,參考 2.1 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; } this.url += cleanPath();
            //通過HardCodedTarget對象,Feign.Builder,上下文對象構建LoadBalanceFeignClient對象(負載均衡的FeignClient對象) 參考2.2
return (T) loadBalance(builder, context,
            
//將1.name(服務名) 2.url(http://服務名) 3.@FeignClient修飾的接口類型構建 HardCodedTarget對象
            new HardCodedTarget<>(this.type, this.name, this.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();
            }
            if (client instanceof FeignBlockingLoadBalancerClient) {
                // not load balancing because we have a url,
                // but Spring Cloud LoadBalancer is on the classpath, so unwrap
                client = ((FeignBlockingLoadBalancerClient) 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));
    }
  2.1創建  Feign.Builder 的過程如下代碼
protected Feign.Builder feign(FeignContext context) {
        FeignLoggerFactory loggerFactory = get(context, FeignLoggerFactory.class);
        Logger logger = loggerFactory.create(this.type);
        // @formatter:off
        //從上下文中獲取日志工廠,Encoder,Decoder,Contract組件,通過這些組件封裝到Feign.Builder中,注意:這里如果配置了feign.hystrix.enable=true,
     //則返回的是HystrixFeign.builder,否則為Feign.Builder

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 // 從配置屬性FeignClientProperties中加載配置,如果存在用戶屬性,覆蓋默認的配置 源碼參考2.1.1 configureFeign(context, builder); return builder; }
  2.1.1  configureFeign()配置Feign.Builder,同時指定上下文環境
protected void configureFeign(FeignContext context, Feign.Builder builder) {
//從上下文中獲取配置屬性相關的類FeignClientProperties FeignClientProperties properties
= this.applicationContext .getBean(FeignClientProperties.class);
//如果屬性不為空
if (properties != null) {
        //判斷默認配置文件的加載順序 全局上下文->默認上下文->指定FeignClient上下文
if (properties.isDefaultToProperties()) {
//讀取全局上下文的配置 configureUsingConfiguration(context, builder);
//先讀取默認的配置 configureUsingProperties( properties.getConfig().get(properties.getDefaultConfig()), builder);
//再讀取FeignClient上下文中自定義的配置 configureUsingProperties(properties.getConfig().get(
this.contextId), builder); } else {
//非默認的配置文件加載順序 默認上下文->指定FeignClient上下文->全局上下文順序加載 configureUsingProperties( properties.getConfig().get(properties.getDefaultConfig()), builder); configureUsingProperties(properties.getConfig().get(
this.contextId), builder); configureUsingConfiguration(context, builder); } } else {
        //如果沒有配置指定的用戶屬性feign.client ,則直接使用全局的上下文環境 configureUsingConfiguration(context, builder); } }
   2.2  loadBalance()  方法 
    protected <T> T loadBalance(Feign.Builder builder, FeignContext context,
            HardCodedTarget<T> target) {
//從上下文中獲取LoadBalanceFeignClient對象 Client client
= getOptional(context, Client.class);
     //將LoadBalanceFeignClient對象放入Feign.builder中
if (client != null) { builder.client(client);
       //從上下文中獲取Target對象 Targeter targeter
= get(context, Targeter.class);
       //通過Target.target()方法,獲取Feign動態代理的實例 這段代碼很重要,見2.2.1
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?"); }
  2.2.1 targeter.target()
 @Override
  public <T> T newInstance(Target<T> target) {
Map
<String, MethodHandler> nameToHandler = targetToHandlersByName.apply(target);
//將@FeignClient中的方法名和SynchronizedMethodHandler映射 Map
<Method, MethodHandler> methodToHandler = new LinkedHashMap<Method, MethodHandler>(); List<DefaultMethodHandler> defaultMethodHandlers = new LinkedList<DefaultMethodHandler>();   
    //通過HardCodeTarget(具有服務名,http+服務名,接口類型),獲取接口中的方法列表
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)));
      }
    }
//這里生成的是FeignInvocationHandler(實現了InvocationHandler),里面重寫了invoke()方法,也就是Feign發送http請求的邏輯,在下一部分,Feign源碼分析三說明
  //也就是說在HardCodeTarget中對應的接口類型中的方法,都會找到對應的SynchronizeMethodHanlder,最終執行到FeignInvocationHanlder里面的invoke()方法 InvocationHandler handler
= factory.create(target, methodToHandler);
//基於JDK的動態代理生成動態代理的對象(動態代理三要素,類加載器,實現InvocationHandler的接口實現,被代理的類型) T proxy
= (T) Proxy.newProxyInstance(target.type().getClassLoader(), new Class<?>[]{target.type()}, handler); for(DefaultMethodHandler defaultMethodHandler : defaultMethodHandlers) { defaultMethodHandler.bindTo(proxy); } return proxy; }

二.流程圖

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM