Feign終結解析


 

概述

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


Feign架構圖

一些核心類及大致流程:

 

 

 

大體步驟:
一、注冊FeignClient配置類和FeignClient BeanDefinition
二、實例化Feign上下文對象FeignContext
三、創建 Feign.builder 對象
四、生成負載均衡代理類
五、生成默認代理類
六、注入到spring容器


源碼分析

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


一、注冊FeignClient配置類和FeignClient BeanDefinition

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

    @EnableFeignClients
    public class MyApplication {
    }

 

這是在啟動類開啟feign裝配的注解,跟進該注解,

@Import(FeignClientsRegistrar.class)
public @interface EnableFeignClients {

 

看看做了什么:

    @Import(FeignClientsRegistrar.class)
    public class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar,
    ResourceLoaderAware, BeanClassLoaderAware {
    // patterned after Spring Integration IntegrationComponentScanRegistrar
    // and RibbonClientsConfigurationRegistgrar
    private final Logger logger = LoggerFactory.getLogger(FeignClientsRegistrar.class);
    private ResourceLoader resourceLoader;
    private ClassLoader classLoader;
    public FeignClientsRegistrar() {
    }
    @Override
    public void setResourceLoader(ResourceLoader resourceLoader) {
    this.resourceLoader = resourceLoader;
    }
    @Override
    public void setBeanClassLoader(ClassLoader classLoader) {
    this.classLoader = classLoader;
    }
    @Override
    public void registerBeanDefinitions(AnnotationMetadata metadata,
    BeanDefinitionRegistry registry) {
       //1、先注冊默認配置
       registerDefaultConfiguration(metadata, registry);
       //2、注冊所有的feignClient beanDefinition
       registerFeignClients(metadata, registry);
    }
    //...
    }

 

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

    private void registerDefaultConfiguration(AnnotationMetadata metadata,
            BeanDefinitionRegistry registry) {
        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();
            }
            //name 默認以default開頭,后續會根據名稱選擇配置
 registerClientConfiguration(registry, name,
                    defaultAttrs.get("defaultConfiguration"));
        }
    }

 

上述方法為讀取啟動類上面@EnableFeignClients注解中聲明feign相關配置類,默認name為default,一般情況下無需配置。用默認的FeignAutoConfiguration即可。
上面有個比較重要的方法:注冊配置registerClientConfiguration,啟動流程一共有兩處讀取feign的配置類,這是第一處

跟進該registerClientConfiguration方法看一下:

    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());
    }

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

2、掃描FeignClient

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

    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);
            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)));
        }
        //獲取掃描目錄下面所有的bean deanDefinition
        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);
                    //這里是第二處
 registerClientConfiguration(registry, name,
                            attributes.get("configuration"));
                    //注冊feignClient
                    registerFeignClient(registry, annotationMetadata, attributes);
                }
            }
        }
    }

 

可以看到上面又調用了registerClientConfiguration注冊配置的方法,這里是第二處調用。這里主要是將掃描的目錄下,每個項目的配置類加載的容器當中。
注冊到容器中,什么時候會用到呢?具體又如何使用呢?別着急,后面會有介紹。

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

    private void registerFeignClient(BeanDefinitionRegistry registry,
            AnnotationMetadata annotationMetadata, Map<String, Object> attributes) {
        String className = annotationMetadata.getClassName();
        //FeignClientFactoryBean的名稱
        BeanDefinitionBuilder definition = BeanDefinitionBuilder
                .genericBeanDefinition(FeignClientFactoryBean.class);
        validate(attributes);
        //將annotationMetadata(注解中的參數)通過addPropertyValue方式設置到FeignClientFactoryBean里
        definition.addPropertyValue("url", getUrl(attributes));
        definition.addPropertyValue("path", getPath(attributes));
        String name = getName(attributes);
        definition.addPropertyValue("name", name);
        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 = name + "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 });
        //將bean definition加入到spring容器
        BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
    }

 

划重點,上面出現了一行相當關鍵代碼:

  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,則調用FeignClientFactoryBeanT getObject() throws Exception;方法生成代理bean。


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

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

    @Override
    public Object getObject() throws Exception {
        // 二、實例化Feign上下文對象FeignContext
        FeignContext context = applicationContext.getBean(FeignContext.class);
        // 三、生成builder對象,用來生成feign
        Feign.Builder builder = feign(context);
        
        // 判斷生成的代理對象類型,如果url為空,則走負載均衡,生成有負載均衡功能的代理類
        if (!StringUtils.hasText(this.url)) {
            String url;
            if (!this.name.startsWith("http")) {
                url = "http://" + this.name;
            }
            else {
                url = this.name;
            }
            url += cleanPath();
            // 四、生成負載均衡代理類
            return loadBalance(builder, context, new HardCodedTarget<>(this.type,
                    this.name, url));
        }
        //如果指定了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 lod 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 targeter.target(this, builder, context, new HardCodedTarget<>(
                this.type, this.name, url));
    }

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

二、實例化Feign上下文對象FeignContext

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

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

獲取FeignContext對象,如果沒有實例化,則主動實例化,如下:

@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;
    }

 

可以看到feign的配置類設置到feign的容器當中,而configurations集合中的元素 正是上面我們提到的兩處調用registerClientConfiguration方法添加進去的,前后呼應。

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

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


三、構造FeignBuilder

繼續跟進FeignClientFactoryBean.getObject()方法:

Feign.Builder builder = feign(context);

 

feign()

    protected Feign.Builder feign(FeignContext context) {
    Logger logger = getOptional(context, Logger.class);
    if (logger == null) {
    logger = new Slf4jLogger(this.type);
    }
    // 1、構造 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));
    // 2、設置重試策略,log等組件
    //設置log級別
    Logger.Level level = getOptional(context, Logger.Level.class);
    if (level != null) {
    builder.logLevel(level);
    }
    //設置重試策略
    Retryer retryer = getOptional(context, Retryer.class);
    if (retryer != null) {
    builder.retryer(retryer);
    }
    //feign的錯誤code解析接口
    ErrorDecoder errorDecoder = getOptional(context, ErrorDecoder.class);
    if (errorDecoder != null) {
    builder.errorDecoder(errorDecoder);
    }
    //超時時間設置,連接超時時間:connectTimeout默認10s,請求請求超時時間:readTimeout默認60s
    Request.Options options = getOptional(context, Request.Options.class);
    if (options != null) {
    builder.options(options);
    }
    //攔截器設置,可以看出攔截器也是可以針對單獨的feignClient設置
    Map<String, RequestInterceptor> requestInterceptors = context.getInstances(
    this.name, RequestInterceptor.class);
    if (requestInterceptors != null) {
    builder.requestInterceptors(requestInterceptors.values());
    }
    if (decode404) {
    builder.decode404();
    }
    return builder;
    }

 

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

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

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

     @Bean
    @Scope("prototype")
    @ConditionalOnMissingBean
    public Feign.Builder feignBuilder(Retryer retryer) {
    return Feign.builder().retryer(retryer);
    }

 

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

     @Configuration
    @ConditionalOnClass({ HystrixCommand.class, HystrixFeign.class })
    protected static class HystrixFeignConfiguration {
    @Bean
    @Scope("prototype")
    @ConditionalOnMissingBean
    @ConditionalOnProperty(name = "feign.hystrix.enabled", matchIfMissing = false)
    public Feign.Builder feignHystrixBuilder() {
    return HystrixFeign.builder();
    }
    }

3)同時引入hystrix 和 sleuth:
加載TraceFeignClientAutoConfiguration的配置創建:HystrixFeign.Builder

注意:

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

    1. @AutoConfigureBefore({FeignAutoConfiguration.class})
  • 創建創建的builder對象和第二種情況一下,只是做了一層包裝:

         final class SleuthFeignBuilder {
        private SleuthFeignBuilder() {}
        static Feign.Builder builder(Tracer tracer, HttpTraceKeysInjector keysInjector) {
        return HystrixFeign.builder()
        //各組件`client,retryer,decoder`進行增強,裝飾器模式。
        .client(new TraceFeignClient(tracer, keysInjector))
        .retryer(new TraceFeignRetryer(tracer))
        .decoder(new TraceFeignDecoder(tracer))
        .errorDecoder(new TraceFeignErrorDecoder(tracer));
        }
        }

     

2、設置重試策略,log等組件
Feign.builder在獲取之后又分別指定了重試策略,日志級別,錯誤代碼code等,在上一步中調用SleuthFeignBuilder.build()時已經設置過默認值了,這里為什么要重復設置呢?

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

     protected <T> T get(FeignContext context, Class<T> type) {
    //根據name,也就是服務名稱來生成builder
    T instance = context.getInstance(this.name, type);
    if (instance == null) {
    throw new IllegalStateException("No bean found of type " + type + " for "
    + this.name);
    }
    return instance;
    }

    public <T> T getInstance(String name, Class<T> type) {
    //這里獲取AnnotationConfigApplicationContext容器
    AnnotationConfigApplicationContext context = getContext(name);
    if (BeanFactoryUtils.beanNamesForTypeIncludingAncestors(context,
    type).length > 0) {
    return context.getBean(type);
    }
    return null;
    }
    private Map<String, AnnotationConfigApplicationContext> contexts = new ConcurrentHashMap<>();
    protected AnnotationConfigApplicationContext getContext(String name) {
    if (!this.contexts.containsKey(name)) {
    synchronized (this.contexts) {
    if (!this.contexts.containsKey(name)) {
    //這里創建容器createContext(name)
    this.contexts.put(name, createContext(name));
    }
    }
    }
    return this.contexts.get(name);
    }

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

    protected AnnotationConfigApplicationContext createContext(String name) {
    //注意:這里的容器並不是spring的容器,而是每次都重新創建一個
    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
    //加載每個服務對應的配置類
    if (this.configurations.containsKey(name)) {
    for (Class<?> configuration : this.configurations.get(name)
    .getConfiguration()) {
    context.register(configuration);
    }
    }
    //加載啟動類@EnableFeignClients注解指定的配置類
    for (Map.Entry<String, C> entry : this.configurations.entrySet()) {
    if (entry.getKey().startsWith("default.")) {
    for (Class<?> configuration : entry.getValue().getConfiguration()) {
    context.register(configuration);
    }
    }
    }
    //注冊默認的配置類:FeignClientsConfiguration
    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);
    }
    //刷新容器
    context.refresh();
    return context;
    }

 

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

重點來了,上面加載配置的順序為:先加載每個服務的配置類,然后加載啟動類注解上的配置類,最后加載默認的配置類。這樣做有什么好處?
spring刷新容器的方法也是對所有的bean進行了緩存,如果已經創建,則不再實例化。所以優先選取每個FeignClient的配置類,最后默認的配置類兜底。

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


四、生成負載均衡代理類

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

     //判斷url是否為空
    if (!StringUtils.hasText(this.url)) {
    //......
    return loadBalance(builder, context, new HardCodedTarget<>(this.type,
    this.name, url));
    }
    //......
    return targeter.target(this, builder, context, new HardCodedTarget<>(
    this.type, this.name, url));

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

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

我們跟進loadBalance方法:


     protected <T> T loadBalance(Feign.Builder builder, FeignContext context,
    HardCodedTarget<T> target) {
    //獲得FeignClient
    Client client = getOptional(context, Client.class);
    if (client != null) {
    builder.client(client);
    return targeter.target(this, builder, context, target);
    }
    throw new IllegalStateException(
    "No Feign Client for loadBalancing defined. Did you forget to include spring-cloud-starter-ribbon?");
    }

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

這里又會有三種情況:
1)沒有整合ribbonsleuth
獲取默認的ClientDefault實例。

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. static {
  3. Targeter targeterToUse;
  4. //判斷類路徑是否引入了hystrixFeign
  5. if (ClassUtils.isPresent("feign.hystrix.HystrixFeign",
  6. FeignClientFactoryBean.class.getClassLoader())) {
  7. targeterToUse = new HystrixTargeter();
  8. }
  9. else {
  10. targeterToUse = new DefaultTargeter();
  11. }
  12. targeter = targeterToUse;
  13. }

這里會初始化Targeter,該類是生成feign代理類的工具類,有兩種實現,正是上面的HystrixTargeter,DefaultTargeter
因為我們引入了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的方法工廠synchronousMethodHandlerFactory,Feign通過該工廠為每個方法創建一個methodHandler,每個methodHandler中包含Feign對應的配置:retryerrequestInterceptors等。

繼續跟進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. for (Method method : target.type().getMethods()) {
  7. if (method.getDeclaringClass() == Object.class) {
  8. continue;
  9. //判斷是否啟用默認handler
  10. } else if(Util.isDefault(method)) {
  11. DefaultMethodHandler handler = new DefaultMethodHandler(method);
  12. defaultMethodHandlers.add(handler);
  13. methodToHandler.put(method, handler);
  14. } else {
  15. methodToHandler.put(method, nameToHandler.get(Feign.configKey(target.type(), method)));
  16. }
  17. }
  18. //創建InvocationHandler,接收請求,轉發到methodHandler
  19. InvocationHandler handler = factory.create(target, methodToHandler);
  20. //生成代理類
  21. T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(), new Class<?>[]{target.type()}, handler);
  22. //將默認方法綁定到代理類
  23. for(DefaultMethodHandler defaultMethodHandler : defaultMethodHandlers) {
  24. defaultMethodHandler.bindTo(proxy);
  25. }
  26. return proxy;
  27. }

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. @Override
  13. protected Object getFallback() {
  14. //......
  15. }
  16. };

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


五、生成默認代理類

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

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


六、注入spring容器

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

 

轉:http://springcloud.cn/view/409


免責聲明!

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



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