1. 程式人生 > >FeignClient原始碼深度解析

FeignClient原始碼深度解析

微信公眾號:吉姆餐廳ak 學習更多原始碼知識,歡迎關注。 全文共16984字左右。


概述

springCloud feign主要對netflix feign進行了增強和包裝,本篇從原始碼角度帶你過一遍裝配流程,揭開feign底層的神祕面紗。 主要包括feign整合ribbon,hystrix,sleuth,以及生成的代理類最終注入到spring容器的過程。篇幅略長,耐心讀完,相信你會有所收穫。


Feign架構圖

一些核心類及大致流程:

大體步驟: 一、註冊FeignClient配置類和FeignClient BeanDefinition 二、例項化Feign上下文物件FeignContext 三、建立 Feign.builder 物件 四、生成負載均衡代理類 五、生成預設代理類 六、注入到spring容器


原始碼分析

主要圍繞上面6個步驟詳細分析。


一、註冊FeignClient配置類和FeignClient BeanDefinition

從啟動類註解開始,來看下 @EnableFeignClients註解:

 
  1. @EnableFeignClients

  2. public class MyApplication {

  3. }

這是在啟動類開啟feign裝配的註解,跟進該註解,看看做了什麼:

 
  1. @Import(FeignClientsRegistrar.class)

 
  1. public class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar,

  2.        ResourceLoaderAware, BeanClassLoaderAware {

  3.  

  4.    // patterned after Spring Integration IntegrationComponentScanRegistrar

  5.    // and RibbonClientsConfigurationRegistgrar

  6.    private final Logger logger = LoggerFactory.getLogger(FeignClientsRegistrar.class);

  7.    private ResourceLoader resourceLoader;

  8.  

  9.    private ClassLoader classLoader;

  10.  

  11.    public FeignClientsRegistrar() {

  12.    }

  13.  

  14.    @Override

  15.    public void setResourceLoader(ResourceLoader resourceLoader) {

  16.        this.resourceLoader = resourceLoader;

  17.    }

  18.  

  19.    @Override

  20.    public void setBeanClassLoader(ClassLoader classLoader) {

  21.        this.classLoader = classLoader;

  22.    }

  23.  

  24.    @Override

  25.    public void registerBeanDefinitions(AnnotationMetadata metadata,

  26.            BeanDefinitionRegistry registry) {

  27.        //1、先註冊預設配置

  28.        registerDefaultConfiguration(metadata, registry);

  29.        //2、註冊所有的feignClient beanDefinition

  30.        registerFeignClients(metadata, registry);

  31.    }

  32.    //...

  33. }

我們分別來看一下上面 registerBeanDefinitions中的兩個方法: 1) 註冊預設配置方法: registerDefaultConfiguration:

 
  1.    private void registerDefaultConfiguration(AnnotationMetadata metadata,

  2.            BeanDefinitionRegistry registry) {

  3.        Map<String, Object> defaultAttrs = metadata

  4.                .getAnnotationAttributes(EnableFeignClients.class.getName(), true);

  5.  

  6.        if (defaultAttrs != null && defaultAttrs.containsKey("defaultConfiguration")) {

  7.            String name;

  8.            if (metadata.hasEnclosingClass()) {

  9.                name = "default." + metadata.getEnclosingClassName();

  10.            }

  11.            else {

  12.                name = "default." + metadata.getClassName();

  13.            }

  14.            // name 預設以 default 開頭,後續會根據名稱選擇配置

  15.            registerClientConfiguration(registry, name,

  16.                    defaultAttrs.get("defaultConfiguration"));

  17.        }

  18.    }

上述方法為讀取啟動類上面 @EnableFeignClients註解中宣告feign相關配置類,預設name為default,一般情況下無需配置。用預設的 FeignAutoConfiguration即可。 上面有個比較重要的方法:註冊配置 registerClientConfiguration,啟動流程一共有兩處讀取feign的配置類,這是第一處。根據該方法看一下:

 
  1.    private void registerClientConfiguration(BeanDefinitionRegistry registry, Object name,

  2.            Object configuration) {

  3.        BeanDefinitionBuilder builder = BeanDefinitionBuilder

  4.                .genericBeanDefinition(FeignClientSpecification.class);

  5.        builder.addConstructorArgValue(name);

  6.        builder.addConstructorArgValue(configuration);

  7.        registry.registerBeanDefinition(

  8.                name + "." + FeignClientSpecification.class.getSimpleName(),

  9.                builder.getBeanDefinition());

  10.    }

上面將bean配置類包裝成 FeignClientSpecification,注入到容器。該物件非常重要,包含FeignClient需要的重試策略,超時策略,日誌等配置,如果某個服務沒有設定,則讀取預設的配置。

2、掃描FeignClient

該方法主要是掃描類路徑,對所有的FeignClient生成對應的 BeanDefinition:

 
  1. public void registerFeignClients(AnnotationMetadata metadata,

  2.            BeanDefinitionRegistry registry) {

  3.  

  4.        //...

  5.        //獲取掃描目錄下面所有的bean deanDefinition

  6.        for (String basePackage : basePackages) {

  7.            Set<BeanDefinition> candidateComponents = scanner

  8.                    .findCandidateComponents(basePackage);

  9.            for (BeanDefinition candidateComponent : candidateComponents) {

  10.                if (candidateComponent instanceof AnnotatedBeanDefinition) {

  11.                    // verify annotated class is an interface

  12.                    AnnotatedBeanDefinition beanDefinition = (AnnotatedBeanDefinition) candidateComponent;

  13.                    AnnotationMetadata annotationMetadata = beanDefinition.getMetadata();

  14.                    Assert.isTrue(annotationMetadata.isInterface(),

  15.                            "@FeignClient can only be specified on an interface");

  16.  

  17.                    Map<String, Object> attributes = annotationMetadata

  18.                            .getAnnotationAttributes(

  19.                                    FeignClient.class.getCanonicalName());

  20.  

  21.                    String name = getClientName(attributes);

  22.                    //這裡是第二處

  23.                    registerClientConfiguration(registry, name,

  24.                            attributes.get("configuration"));

  25.  

  26.                    //註冊feignClient

  27.                    registerFeignClient(registry, annotationMetadata, attributes);

  28.                }

  29.            }

  30.        }

  31.    }

可以看到上面又呼叫了 registerClientConfiguration註冊配置的方法,這裡是第二處呼叫。這裡主要是將掃描的目錄下,每個專案的配置類載入的容器當中。 註冊到容器中,什麼時候會用到呢?具體又如何使用呢?彆著急,後面會有介紹。

我們先會回到繼續主流程,繼續看註冊feignClient的方法,跟進 registerFeignClient

 
  1. private void registerFeignClient(BeanDefinitionRegistry registry,

  2.            AnnotationMetadata annotationMetadata, Map<String, Object> attributes) {

  3.        String className = annotationMetadata.getClassName();

  4.        //宣告代理類名稱

  5.        BeanDefinitionBuilder definition = BeanDefinitionBuilder

  6.                .genericBeanDefinition(FeignClientFactoryBean.class);

  7.        //logger.info("TEX do some replacement");

  8.            //attributes.put("value", ((String)attributes.get("value")).replace('_','-'));

  9.        validate(attributes);

  10.        definition.addPropertyValue("url", getUrl(attributes));

  11.        definition.addPropertyValue("path", getPath(attributes));

  12.        String name = getName(attributes);

  13.        definition.addPropertyValue("name", name);

  14.        definition.addPropertyValue("type", className);

  15.        definition.addPropertyValue("decode404", attributes.get("decode404"));

  16.        definition.addPropertyValue("fallback", attributes.get("fallback"));

  17.        definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);

  18.  

  19.        String alias = name + "FeignClient";

  20.        AbstractBeanDefinition beanDefinition = definition.getBeanDefinition();

  21.        beanDefinition.setPrimary(true);

  22.        BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className,

  23.                new String[] { alias });

  24.        //將bean definition加入到spring容器

  25.        BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);

  26.    }

劃重點,上面出現了一行相當關鍵程式碼:

 
  1. BeanDefinitionBuilder definition = BeanDefinitionBuilder.genericBeanDefinition(FeignClientFactoryBean.class);

springCloud FeignClient其實是利用了spring的代理工廠來生成代理類,所以這裡將所有的 feignClient的描述資訊 BeanDefinition設定為 FeignClientFactoryBean型別,該類又繼承 FactoryBean,很明顯,這是一個代理類。 在spring中, FactoryBean是一個工廠bean,用作建立代理bean,所以得出結論,feign將所有的feignClient bean包裝成 FeignClientFactoryBean。掃描方法到此結束。

代理類什麼時候會觸發生成呢? 在spring重新整理容器時,當例項化我們的業務service時,如果發現註冊了FeignClient,spring就會去例項化該FeignClient,同時會進行判斷是否是代理bean,如果為代理bean,則呼叫 FeignClientFactoryBean的 T getObject() throws Exception;方法生成代理bean。


先來隆重介紹一下 FeignClientFactoryBean,後面四步都基於此類。

先看一下代理feignClient代理生成入口: getObject方法:

 
  1. @Override

  2.    public Object getObject() throws Exception {

  3.        // 二、例項化Feign上下文物件FeignContext

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

  5.        // 三、生成builder物件,用來生成feign

  6.        Feign.Builder builder = feign(context);

  7.  

  8.        // 判斷生成的代理物件型別,如果url為空,則走負載均衡,生成有負載均衡功能的代理類

  9.        if (!StringUtils.hasText(this.url)) {

  10.            String url;

  11.            if (!this.name.startsWith("http")) {

  12.                url = "http://" + this.name;

  13.            }

  14.            else {

  15.                url = this.name;

  16.            }

  17.            url += cleanPath();

  18.            // 四、生成負載均衡代理類

  19.            return loadBalance(builder, context, new HardCodedTarget<>(this.type,

  20.                    this.name, url));

  21.        }

  22.        //如果指定了url,則生成預設的代理類

  23.        if (StringUtils.hasText(this.url) && !this.url.startsWith("http")) {

  24.            this.url = "http://" + this.url;

  25.        }

  26.        String url = this.url + cleanPath();

  27.        // 五、生成預設代理類

  28.        return targeter.target(this, builder, context, new HardCodedTarget<>(

  29.                this.type, this.name, url));

  30.    }

getObject()邏輯比較多,每一行都會做一些初始化配置,來逐步分析。

二、例項化Feign上下文物件FeignContext

上述方法中第一行便是例項化 FeignContext

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

獲取 FeignContext物件,如果沒有例項化,則主動例項化,如下:

 
  1. @Configuration

  2. @ConditionalOnClass(Feign.class)

  3. public class FeignAutoConfiguration {

  4.  

  5.    @Autowired(required = false)

  6.    private List<FeignClientSpecification> configurations = new ArrayList<>();

  7.  

  8.    @Bean

  9.    public HasFeatures feignFeature() {

  10.        return HasFeatures.namedFeature("Feign", Feign.class);

  11.    }

  12.  

  13.    @Bean

  14.    public FeignContext feignContext() {

  15.        FeignContext context = new FeignContext();

  16.        //將feign的配置類設定到feign的容器當中

  17.        context.setConfigurations(this.configurations);

  18.        return context;

  19.    }

  20. }

可以看到feign的配置類設定到feign的容器當中,而集合中的元素 正是上面我們提到的兩處呼叫 registerClientConfiguration方法新增進去的,前後呼應。

然而,當我們引入了 sleuth之後,獲取的 feignContext確是 TraceFeignClientAutoConfiguration中配置的例項 sleuthFeignContext:

可以看到上面建立了一個 TraceFeignContext例項,因為該物件繼承 FeignContext,同時又加了 @Primary註解,所以在上面第2步中通過型別獲取: applicationContext.getBean(FeignContext.class);,最終拿到的是 TraceFeignContext


三、構造 FeignBuilder

繼續跟進該方法:

Feign.Builder builder = feign(context);

 
  1. protected Feign.Builder feign(FeignContext context) {

  2.        Logger logger = getOptional(context, Logger.class);

  3.  

  4.        if (logger == null) {

  5.            logger = new Slf4jLogger(this.type);

  6.        }

  7.  

  8.        // 1、構造 Feign.Builder

  9.        Feign.Builder builder = get(context, Feign.Builder.class)

  10.                // required values

  11.                .logger(logger)

  12.                .encoder(get(context, Encoder.class))

  13.                .decoder(get(context, Decoder.class))

  14.                .contract(get(context, Contract.class));

  15.  

  16.  

  17.        // 2、設定重試策略,log等元件

  18.  

  19.         //設定log級別

  20.        Logger.Level level = getOptional(context, Logger.Level.class);

  21.        if (level != null) {

  22.            builder.logLevel(level);

  23.        }

  24.        //設定重試策略

  25.        Retryer retryer = getOptional(context, Retryer.class);

  26.        if (retryer != null) {

  27.            builder.retryer(retryer);

  28.        }

  29.        //feign的錯誤code解析介面

  30.        ErrorDecoder errorDecoder = getOptional(context, ErrorDecoder.class);

  31.        if (errorDecoder != null) {

  32.            builder.errorDecoder(errorDecoder);

  33.        }

  34.        //超時時間設定,連線超時時間:connectTimeout預設10s,請求請求超時時間:readTimeout預設60s

  35.        Request.Options options = getOptional(context, Request.Options.class);

  36.        if (options != null) {

  37.            builder.options(options);

  38.        }

  39.        //攔截器設定,可以看出攔截器也是可以針對單獨的feignClient設定

  40.        Map<String, RequestInterceptor> requestInterceptors = context.getInstances(

  41.                this.name, RequestInterceptor.class);

  42.        if (requestInterceptors != null) {

  43.            builder.requestInterceptors(requestInterceptors.values());

  44.        }

  45.  

  46.        if (decode404) {

  47.            builder.decode404();

  48.        }

  49.  

  50.        return builder;

  51.    }

上述程式碼有兩處邏輯,分別來看:

1、 Feign.Builder builder = get(context, Feign.Builder.class) ,又會有以下三種情況:

1)單獨使用Feign,沒有引入 sleuth、 hystrix: 通過載入FeignClientsConfiguration的配置建立 Feign的靜態內部類: Feign.Builder

 
  1.    @Bean

  2.    @Scope("prototype")

  3.    @ConditionalOnMissingBean

  4.    public Feign.Builder feignBuilder(Retryer retryer) {

  5.        return Feign.builder().retryer(retryer);

  6.    }

2)引入了 hystrix,沒有引入 sleuth: 通過載入 FeignClientsConfiguration的配置建立 HystrixFeign的靜態內部類: HystrixFeign.Builder

 
  1.    @Configuration

  2.    @ConditionalOnClass({ HystrixCommand.class, HystrixFeign.class })

  3.    protected static class HystrixFeignConfiguration {

  4.        @Bean

  5.        @Scope("prototype")

  6.        @ConditionalOnMissingBean

  7.        @ConditionalOnProperty(name = "feign.hystrix.enabled", matchIfMissing = false)

  8.        public Feign.Builder feignHystrixBuilder() {

  9.            return HystrixFeign.builder();

  10.        }

  11.    }

3)同時引入 hystrix 和 sleuth: 載入 TraceFeignClientAutoConfiguration的配置建立: HystrixFeign.Builder注意:

  • TraceFeignClientAutoConfiguration的配置類載入一定是在 FeignClientsConfiguration之前(先載入先生效),而 FeignClientsConfiguration載入是通過 FeignAutoConfiguration完成的,所以上圖中引入了條件註解:

     
    1. @AutoConfigureBefore({FeignAutoConfiguration.class})

  • 建立建立的 builder物件和第二種情況一下,只是做了一層包裝:

 
  1. final class SleuthFeignBuilder {

  2.  

  3.    private SleuthFeignBuilder() {}

  4.  

  5.    static Feign.Builder builder(Tracer tracer, HttpTraceKeysInjector keysInjector) {

  6.        return HystrixFeign.builder()

  7.                //各元件`client,retryer,decoder`進行增強,裝飾器模式。

  8.                .client(new TraceFeignClient(tracer, keysInjector))

  9.                .retryer(new TraceFeignRetryer(tracer))

  10.                .decoder(new TraceFeignDecoder(tracer))

  11.                .errorDecoder(new TraceFeignErrorDecoder(tracer));

  12.    }

  13. }

2、設定重試策略,log等元件 Feign.builder在獲取之後又分別指定了重試策略,日誌級別,錯誤程式碼code等,在上一步中呼叫 SleuthFeignBuilder.build()時已經設定過預設值了,這裡為什麼要重複設定呢?

我們跟進去get()方法,一探究竟:

 
  1.    protected <T> T get(FeignContext context, Class<T> type) {

  2.        //根據name,也就是服務名稱來生成builder

  3.        T instance = context.getInstance(this.name, type);

  4.        if (instance == null) {

  5.            throw new IllegalStateException("No bean found of type " + type + " for "

  6.                    + this.name);

  7.        }

  8.        return instance;

  9.    }

 
  1.    public <T> T getInstance(String name, Class<T> type) {

  2.        //這裡獲取AnnotationConfigApplicationContext容器

  3.        AnnotationConfigApplicationContext context = getContext(name);

  4.        if (BeanFactoryUtils.beanNamesForTypeIncludingAncestors(context,

  5.                type).length > 0) {

  6.            return context.getBean(type);

  7.        }

  8.        return null;

  9.    }

  10.  

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

  12.  

  13.    protected AnnotationConfigApplicationContext getContext(String name) {

  14.        if (!this.contexts.containsKey(name)) {

  15.            synchronized (this.contexts) {

  16.                if (!this.contexts.containsKey(name)) {

  17.                    //這裡建立容器createContext(name)

  18.                    this.contexts.put(name, createContext(name));

  19.                }

  20.            }

  21.        }

  22.        return this.contexts.get(name);

  23.    }

重點來了,上述程式碼將FeignContext做了快取,每個服務對應一個FeignContext,服務名作為key。 繼續跟進 createContext(name)方法:

 
  1. protected AnnotationConfigApplicationContext createContext(String name) {

  2.        //注意:這裡的容器並不是spring的容器,而是每次都重新建立一個

  3.        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();

  4.        //載入每個服務對應的配置類

  5.        if (this.configurations.containsKey(name)) {

  6.            for (Class<?> configuration : this.configurations.get(name)

  7.                    .getConfiguration()) {

  8.                context.register(configuration);

  9.            }

  10.        }

  11.        //載入啟動類@EnableFeignClients註解指定的配置類

  12.        for (Map.Entry<String, C> entry : this.configurations.entrySet()) {

  13.            if (entry.getKey().startsWith("default.")) {

  14.                for (Class<?> configuration : entry.getValue().getConfiguration()) {

  15.                    context.register(configuration);

  16.                }

  17.            }

  18.        }

  19.        //註冊預設的配置類:FeignClientsConfiguration

  20.        context.register(PropertyPlaceholderAutoConfiguration.class,

  21.                this.defaultConfigType);

  22.        context.getEnvironment().getPropertySources().addFirst(new MapPropertySource(

  23.                this.propertySourceName,

  24.                Collections.<String, Object> singletonMap(this.propertyName, name)));

  25.        if (this.parent != null) {

  26.            // Uses Environment from parent as well as beans

  27.            context.setParent(this.parent);

  28.        }

  29.        //重新整理容器

  30.        context.refresh();

  31.        return context;

  32.    }

可以看到上述AnnotationConfigApplicationContext容器並非spring容器,只是利用了spring重新整理容器的方法來例項化配置類,以服務名作為key,配置隔離。

重點來了,上面載入配置的順序為:先載入每個服務的配置類,然後載入啟動類註解上的配置類,最後載入預設的配置類。這樣做有什麼好處? spring重新整理容器的方法也是對所有的bean進行了快取,如果已經建立,則不再例項化。所以優先選取每個FeignClient的配置類,最後預設的配置類兜底。

所以這也證明了 sleuth的配置一定在 feign的配置類之前載入。 至此, FeignBuilder構造流程結束。


四、生成負載均衡代理類

再貼一下生成代理類的入口:

 
  1.        //判斷url是否為空

  2.        if (!StringUtils.hasText(this.url)) {

  3.          //......

  4.            return loadBalance(builder, context, new HardCodedTarget<>(this.type,

  5.                    this.name, url));

  6.        }

  7.        //......

  8.        return targeter.target(this, builder, context, new HardCodedTarget<>(

  9.                this.type, this.name, url));

這裡有個重要判斷:判斷FeignClient宣告的url是否為空,來判斷具體要生成的代理類。如下: 這麼做有什麼意義? 1)如果為空,則預設走Ribbon代理,也就是這個入口,會有載入ribbon的處理。 @FeignClient("MyFeignClient") 2)如果不為空,指定url,則走預設生成代理類的方式,也就是所謂的硬編碼。 @FeignClient(value = "MyFeignClient",url = "http://localhost:8081") 這樣處理方便開發人員進行測試,無需關注註冊中心,直接http呼叫,是個不錯的開發小技巧。

生產環境也可以用上述第二種方式,指定域名的方式。

我們跟進 loadBalance方法:


 
  1.    protected <T> T loadBalance(Feign.Builder builder, FeignContext context,

  2.            HardCodedTarget<T> target) {

  3.        //獲得FeignClient

  4.        Client client = getOptional(context, Client.class);

  5.        if (client != null) {

  6.            builder.client(client);

  7.            return targeter.target(this, builder, context, target);

  8.        }

  9.        throw new IllegalStateException(

  10.                "No Feign Client for loadBalancing defined. Did you forget to include spring-cloud-starter-ribbon?");

  11.    }

Client client = getOptional(context, Client.class);這裡會從 FeignContext上下文中獲取 Client物件,該物件有三種例項,具體是哪個實現呢? 

這裡又會有三種情況: 1)沒有整合 ribbon、 sleuth: 獲取預設的 Client: Default例項。

2)整合了 ribbon,沒有整合 sleuth: 獲取 LoadBalanceFeignClient例項。

3)整合了 ribbon 和 sleuth: 會獲取 TraceFeignClient例項,該例項是對 LoadBalanceFeignClient的一種包裝,實現方式通過 BeanPostProcessor實現: FeignBeanPostProcessor中定義了包裝邏輯:

 
  1.    @Override

  2.    public Object postProcessBeforeInitialization(Object bean, String beanName)

  3.            throws BeansException {

  4.        return this.traceFeignObjectWrapper.wrap(bean);

  5.    }

通過 wrap方法最終返回 TraceFeignClient例項。

繼續回到主流程,先來看下 Targeter介面:

 
  1. interface Targeter {

  2.        <T> T target(FeignClientFactoryBean factory, Feign.Builder feign, FeignContext context,

  3.                HardCodedTarget<T> target);

  4.    }

該物件定義在 FeignClientFactoryBean靜靜態程式碼塊中:

 
  1.    private static final Targeter targeter;

  2.  

  3.    static {

  4.        Targeter targeterToUse;

  5.        //判斷類路徑是否引入了hystrixFeign

  6.        if (ClassUtils.isPresent("feign.hystrix.HystrixFeign",

  7.                FeignClientFactoryBean.class.getClassLoader())) {

  8.            targeterToUse = new HystrixTargeter();

  9.        }

  10.        else {

  11.            targeterToUse = new DefaultTargeter();

  12.        }

  13.        targeter = targeterToUse;

  14.    }

這裡會初始化 Targeter,該類是生成feign代理類的工具類,有兩種實現,正是上面的 HystrixTargeterDefaultTargeter。 因為我們引入了 hystrix,所以 Targeter實現類為 HystrixTargeter。我們繼續跟進 targeter.target方法:

 
  1.  public <T> T target(Target<T> target) {

  2.      return build().newInstance(target);

  3.    }

上面通過 build()方法獲取生成代理類的工具類 ReflectiveFeign,再通過 newInstance正式建立代理類。 繼續跟進:

 
  1.    public Feign build() {

  2.      SynchronousMethodHandler.Factory synchronousMethodHandlerFactory =

  3.          new SynchronousMethodHandler.Factory(client, retryer, requestInterceptors, logger,

  4.                                               logLevel, decode404);

  5.      ParseHandlersByName handlersByName =

  6.          new ParseHandlersByName(contract, options, encoder, decoder,

  7.                                  errorDecoder, synchronousMethodHandlerFactory);

  8.      return new ReflectiveFeign(handlersByName, invocationHandlerFactory);

  9.    }

這裡會建立Feign的方法工廠 synchronousMethodHandlerFactoryFeign通過該工廠為每個方法建立一個 methodHandler,每個 methodHandler中包含Feign對應的配置: retryer、 requestInterceptors等。

繼續跟進 newInstance方法:

 
  1. public <T> T newInstance(Target<T> target) {

  2.     //建立所有的 MethodHandler

  3.    Map<String, MethodHandler> nameToHandler = targetToHandlersByName.apply(target);

  4.    Map<Method, MethodHandler> methodToHandler = new LinkedHashMap<Method, MethodHandler>();

  5.    List<DefaultMethodHandler> defaultMethodHandlers = new LinkedList<DefaultMethodHandler>();

  6.  

  7.    for (Method method : target.type().getMethods()) {

  8.      if (method.getDeclaringClass() == Object.class) {

  9.        continue;

  10.       //判斷是否啟用預設handler

  11.      } else if(Util.isDefault(method)) {

  12.        DefaultMethodHandler handler = new DefaultMethodHandler(method);

  13.        defaultMethodHandlers.add(handler);

  14.        methodToHandler.put(method, handler);

  15.      } else {

  16.        methodToHandler.put(method, nameToHandler.get(Feign.configKey(target.type(), method)));

  17.      }

  18.    }

  19.    //建立InvocationHandler,接收請求,轉發到methodHandler

  20.    InvocationHandler handler = factory.create(target, methodToHandler);

  21.    //生成代理類

  22.    T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(), new Class<?>[]{target.type()}, handler);

  23.  

  24.   //將預設方法繫結到代理類

  25.    for(DefaultMethodHandler defaultMethodHandler : defaultMethodHandlers) {

  26.      defaultMethodHandler.bindTo(proxy);

  27.    }

  28.    return proxy;

  29.  }

InvocationHandler最終建立的例項為 HystrixInvocationHandler,核心方法如下:

 
  1. HystrixCommand<Object> hystrixCommand = new HystrixCommand<Object>(setter) {

  2.      @Override

  3.      protected Object run() throws Exception {

  4.        try {

  5.          return HystrixInvocationHandler.this.dispatch.get(method).invoke(args);

  6.        } catch (Exception e) {

  7.          throw e;

  8.        } catch (Throwable t) {

  9.          throw (Error) t;

  10.        }

  11.      }

  12.  

  13.      @Override

  14.      protected Object getFallback() {

  15.      //......

  16.      }

  17.    };

整個流程:Feign呼叫方發起請求,傳送至hystrix的HystrixInvocationHandler,通過服務名稱,找到對應方法的methodHandler,methodHandler中封裝了loadBalanceClient、retryer、RequestInterceptor等元件,如果引入了sleuth,這幾個元件均是sleuth的包裝類。然後通過以上元件構造 http請求完成整個過程。


五、生成預設代理類

理解了第四步的邏輯,生成預設代理類就很容易理解了,唯一不同點就是 client的實現類為 loadBalanceClient

注意:不管是哪種代理類,最終發起請求還是由 Feign.Default中的 execute方法完成,預設使用 HttpUrlConnection實現。


六、注入spring容器

總結:通過 spring refresh()方法,觸發 FeignClientFactoryBean.getObject()方法獲得了代理類,然後完成注入 spring容器的過程。該實現方式同 Dubbo的實現方式類似,有興趣的可以自行研究噢。

 

                                                         
                                                                    掃一掃,支援下作者吧

                                             (轉載本站文章請註明作者和出處 方誌朋的部落格