Spring Cloud Feign 調用過程分析

前面已經學習了兩個Spring Cloud 組件:java

  • Eureka:實現服務註冊功能;
  • Ribbon:提供基於RestTemplate的HTTP客戶端而且支持服務負載均衡功能。

經過這兩個組件咱們暫時能夠完成服務註冊和可配置負載均衡的服務調用。今天咱們要學習的是Feign,那麼Feign解決了什麼問題呢?git

相對於Eureka,Ribbon來講,Feign的地位好像不是那麼重要,Feign是一個聲明式的REST客戶端,它的目的就是讓REST調用更加簡單。經過提供HTTP請求模板,讓Ribbon請求的書寫更加簡單和便捷。另外,在Feign中整合了Ribbon,從而不須要顯式的聲明Ribbon的jar包。github

前面在使用Ribbon+RestTemplate時,利用RestTemplate對http請求的封裝處理,造成了一套模版化的調用方法。可是在實際開發中,因爲對服務依賴的調用可能不止一處,每每一個接口會被多處調用,因此一般都會針對每一個微服務自行封裝一些客戶端類來包裝這些依賴服務的調用。因此,Feign在此基礎上作了進一步封裝,由他來幫助咱們定義和實現依賴服務接口的定義。在Feign的實現下,咱們只需建立一個接口並使用註解的方式來配置它(之前是Dao接口上面標註Mapper註解,如今是一個微服務接口上面標註一個Feign註解便可),便可完成對服務提供方的接口綁定,簡化了使用Spring Cloud Ribbon時,自動封裝服務調用客戶端的開發量。web

Feign的使用:

1.引入依賴:spring

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

另外,咱們須要添加spring-cloud-dependenciesapache

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.0.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.rickiyang.learn</groupId>
    <artifactId>feign-consumer</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>feign-consumer</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <spring-cloud.version>Finchley.RELEASE</spring-cloud.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-eureka</artifactId>
        </dependency>

    </dependencies>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

2.接下來,咱們須要在主類中添加 @EnableFeignClientsjson

package com.rickiyang.learn;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;

@EnableDiscoveryClient
@EnableFeignClients
@SpringBootApplication
public class FeignConsumerApplication {

    public static void main(String[] args) {
        SpringApplication.run(FeignConsumerApplication.class, args);
    }

}

3.再來看一下用Feign寫的HTTP請求的格式:app

package com.rickiyang.learn.service;

import com.rickiyang.learn.entity.Person;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.*;

/**
 * @author: rickiyang
 * @date: 2019/10/5
 * @description:
 */
@FeignClient(name= "eureka-client")
public interface HelloRemote {


    @RequestMapping(value = "/hello/{name}")
    String hello(@PathVariable(value = "name") String name);


    @PostMapping(value ="/add",produces = "application/json; charset=UTF-8")
    String addPerson(@RequestBody Person person);

    @GetMapping("/getPerson/{id}")
    String getPerson(@PathVariable("id") Integer id);

}

用FeignClient註解申明要調用的服務是哪一個,該服務中的方法都有咱們常見的Restful方式的API來聲明,這種方式你們是否是感受像是在寫Restful接口同樣。負載均衡

代碼示例:點擊這裏maven

note:

示例代碼的正確打開方式:先啓動服務端,而後啓動一個client端,再次啓動 feign-consumer,調用 feign-consumer中的接口便可。

還記得在Ribbon學習的時候使用RestTemplate發起HTTP請求的方式嗎:

restTemplate.getForEntity("http://eureka-client/hello/" + name, String.class).getBody();

將整個的請求URL和參數都放在一塊兒,雖然沒有什麼問題,總歸不是那麼優雅。使用Feign以後你可使用Restful方式進行調用,寫起來也會更加清晰。

Feign調用過程分析

上面簡單介紹了Feign的使用方式,你們能夠結合着代碼示例運行一下,瞭解基本的使用方式。接下來咱們一塊兒分析Feign的調用過程,咱們帶着兩個問題去跟蹤:

1.請求如何被Feign 統一託管;

2.Feign如何分發請求。

這兩個問題應該就涵蓋了Feign的功能,下面就出發去一探究竟。

咱們還和之前同樣從一個入口進行深刻,首先看啓動類上的 @EnableFeignClients 註解:

package org.springframework.cloud.openfeign;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import org.springframework.context.annotation.Import;

/**
 * Scans for interfaces that declare they are feign clients (via {@link FeignClient
 * <code>@FeignClient</code>}). Configures component scanning directives for use with
 * {@link org.springframework.context.annotation.Configuration
 * <code>@Configuration</code>} classes.
 *
 * @author Spencer Gibb
 * @author Dave Syer
 * @since 1.0
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(FeignClientsRegistrar.class)
public @interface EnableFeignClients {

    //等價於basePackages屬性,更簡潔的方式
	String[] value() default {};
    //指定多個包名進行掃描
	String[] basePackages() default {};

    //指定多個類或接口的class,掃描時會在這些指定的類和接口所屬的包進行掃描
	Class<?>[] basePackageClasses() default {};

	 //爲全部的Feign Client設置默認配置類
	Class<?>[] defaultConfiguration() default {};

	 //指定用@FeignClient註釋的類列表。若是該項配置不爲空,則不會進行類路徑掃描
	Class<?>[] clients() default {};
}

註釋上說了該註解用於掃描 FeignClient 聲明的類。咱們用 FeignClient 註解去聲明一個 Eureka 客戶端,那麼猜測這裏應該是取到咱們聲明的Eureka client名稱,而後去訪問Eureka server獲取服務提供者。

一樣的,爲全部Feign Client 也支持文件屬性的配置,以下 :

feign:
  client:
    config:                                         
    # 默認爲全部的feign client作配置(注意和上例github-client是同級的)
      default:                                      
        connectTimeout: 5000                        # 鏈接超時時間
        readTimeout: 5000                           # 讀超時時間設置

注 : 若是經過Java代碼進行了配置,又經過配置文件進行了配置,則配置文件的中的Feign配置會覆蓋Java代碼的配置。

但也能夠設置feign.client.defalult-to-properties=false,禁用掉feign配置文件的方式讓Java配置生效。

注意到類頭聲明的 @Import 註解引用的 FeignClientsRegistrar 類,這個類的做用是在 EnableFeignClients 初始化的時候掃描該註解對應的配置。

接着看 FeignClient 註解:

package org.springframework.cloud.openfeign;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import org.springframework.core.annotation.AliasFor;

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface FeignClient {

    //指定Feign Client的名稱,若是項目使用了 Ribbon,name屬性會做爲微服務的名稱,用於服務發現
	@AliasFor("name")
	String value() default "";
	//用serviceId作服務發現已經被廢棄,因此不推薦使用該配置
	@Deprecated
	String serviceId() default "";
	//指定Feign Client的serviceId,若是項目使用了 Ribbon,將使用serviceId用於服務發現,但上面能夠看到serviceId作服務發現已經被廢棄,因此也不推薦使用該配置
	@AliasFor("value")
	String name() default "";
	//爲Feign Client 新增註解@Qualifier
	String qualifier() default "";
    //請求地址的絕對URL,或者解析的主機名
	String url() default "";
    //調用該feign client發生了常見的404錯誤時,是否調用decoder進行解碼異常信息返回,不然拋出FeignException
	boolean decode404() default false;
     //Feign Client設置默認配置類
	Class<?>[] configuration() default {};
    //定義容錯的處理類,當調用遠程接口失敗或超時時,會調用對應接口的容錯邏輯,fallback 指定的類必須實現@FeignClient標記的接口。實現的法方法即對應接口的容錯處理邏輯
	Class<?> fallback() default void.class;
    //工廠類,用於生成fallback 類示例,經過這個屬性咱們能夠實現每一個接口通用的容錯邏輯,減小重複的代碼
	Class<?> fallbackFactory() default void.class;
    //定義當前FeignClient的全部方法映射加統一前綴
	String path() default "";
    //是否將此Feign代理標記爲一個Primary Bean,默認爲ture
	boolean primary() default true;
}

一樣在 FeignClientsRegistrar 類中也會去掃描 FeignClient 註解對應的配置信息。咱們直接看 FeignClientsRegistrar 的邏輯:

class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar,
		ResourceLoaderAware, EnvironmentAware {

	// patterned after Spring Integration IntegrationComponentScanRegistrar
	// and RibbonClientsConfigurationRegistgrar

	private ResourceLoader resourceLoader;

	private Environment environment;

	public FeignClientsRegistrar() {
	}

	@Override
	public void setResourceLoader(ResourceLoader resourceLoader) {
		this.resourceLoader = resourceLoader;
	}

   //在這個重載的方法裏面作了兩件事情:
   //1.將EnableFeignClients註解對應的配置屬性注入
   //2.將FeignClient註解對應的屬性注入
	@Override
	public void registerBeanDefinitions(AnnotationMetadata metadata,
			BeanDefinitionRegistry registry) {
        //注入EnableFeignClients註解對應的配置屬性
		registerDefaultConfiguration(metadata, registry);
       //注入FeignClient註解對應的屬性
		registerFeignClients(metadata, registry);
	}

   /**
   * 拿到 EnableFeignClients註解 defaultConfiguration 字段的值
   * 而後進行注入
   *
   **/
	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();
			}
			registerClientConfiguration(registry, name,
					defaultAttrs.get("defaultConfiguration"));
		}
	}

	public void registerFeignClients(AnnotationMetadata metadata,
								 BeanDefinitionRegistry registry) {
        // 獲取ClassPath掃描器
        ClassPathScanningCandidateComponentProvider scanner = getScanner();
        // 爲掃描器設置資源加載器
        scanner.setResourceLoader(this.resourceLoader);

        Set<String> basePackages;
        // 1. 從@EnableFeignClients註解中獲取到配置的各個屬性值
        Map<String, Object> attrs = metadata
                .getAnnotationAttributes(EnableFeignClients.class.getName());
        // 2. 註解類型過濾器,只過濾@FeignClient   
        AnnotationTypeFilter annotationTypeFilter = new AnnotationTypeFilter(
                FeignClient.class);
        // 3. 從1. 中的屬性值中獲取clients屬性的值        
        final Class<?>[] clients = attrs == null ? null
                : (Class<?>[]) attrs.get("clients");
        if (clients == null || clients.length == 0) {
            // 掃描器設置過濾器且獲取須要掃描的基礎包集合
            scanner.addIncludeFilter(annotationTypeFilter);
            basePackages = getBasePackages(metadata);
        }else {
            // clients屬性值不爲null,則將其clazz路徑轉爲包路徑
            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)));
        }

        // 3. 掃描基礎包,且知足過濾條件下的接口封裝成BeanDefinition
        for (String basePackage : basePackages) {
            Set<BeanDefinition> candidateComponents = scanner
                    .findCandidateComponents(basePackage);
            // 遍歷掃描到的bean定義        
            for (BeanDefinition candidateComponent : candidateComponents) {
                if (candidateComponent instanceof AnnotatedBeanDefinition) {
                    // 並校驗掃描到的bean定義類是一個接口
                    AnnotatedBeanDefinition beanDefinition = (AnnotatedBeanDefinition) candidateComponent;
                    AnnotationMetadata annotationMetadata = beanDefinition.getMetadata();
                    Assert.isTrue(annotationMetadata.isInterface(),
                            "@FeignClient can only be specified on an interface");

                    // 獲取@FeignClient註解上的各個屬性值
                    Map<String, Object> attributes = annotationMetadata
                            .getAnnotationAttributes(
                                    FeignClient.class.getCanonicalName());

                    String name = getClientName(attributes);
                    // 能夠看到這裏也註冊了一個FeignClient的配置bean
                    registerClientConfiguration(registry, name,
                            attributes.get("configuration"));
                    // 註冊bean定義到spring中
                    registerFeignClient(registry, annotationMetadata, attributes);
                }
            }
        }
	}

   /**
   * 註冊bean
   **/
	private void registerFeignClient(BeanDefinitionRegistry registry,
       AnnotationMetadata annotationMetadata, Map<String, Object> attributes) {
        // 1.獲取類名稱,也就是本例中的FeignService接口
        String className = annotationMetadata.getClassName();

        // 2.BeanDefinitionBuilder的主要做用就是構建一個AbstractBeanDefinition
        // AbstractBeanDefinition類最終被構建成一個BeanDefinitionHolder
        // 而後註冊到Spring中
        // 注意:beanDefinition類爲FeignClientFactoryBean,故在Spring獲取類的時候實際返回的是
        // FeignClientFactoryBean類
        BeanDefinitionBuilder definition = BeanDefinitionBuilder
            .genericBeanDefinition(FeignClientFactoryBean.class);
        validate(attributes);

        // 3.添加FeignClientFactoryBean的屬性,
        // 這些屬性也都是咱們在@FeignClient中定義的屬性
        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);

        // 4.設置別名 name就是咱們在@FeignClient中定義的name屬性
        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;
        }

        // 5.定義BeanDefinitionHolder,
        // 在本例中 名稱爲FeignService,類爲FeignClientFactoryBean
        BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className, new String[] { alias });
        BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
	}

	private void validate(Map<String, Object> attributes) {
		AnnotationAttributes annotation = AnnotationAttributes.fromMap(attributes);
		// This blows up if an aliased property is overspecified
		// FIXME annotation.getAliasedString("name", FeignClient.class, null);
		Assert.isTrue(
			!annotation.getClass("fallback").isInterface(),
			"Fallback class must implement the interface annotated by @FeignClient"
		);
		Assert.isTrue(
			!annotation.getClass("fallbackFactory").isInterface(),
			"Fallback factory must produce instances of fallback classes that implement the interface annotated by @FeignClient"
		);
	}

   ......
   ......
   ......
}

在這裏作了兩件事情:

  1. 將EnableFeignClients註解對應的配置屬性注入;
  2. 將FeignClient註解對應的屬性注入。

生成FeignClient對應的bean,注入到Spring 的IOC容器。

在registerFeignClient方法中構造了一個BeanDefinitionBuilder對象,BeanDefinitionBuilder的主要做用就是構建一個AbstractBeanDefinition,AbstractBeanDefinition類最終被構建成一個BeanDefinitionHolder 而後註冊到Spring中。

beanDefinition類爲FeignClientFactoryBean,故在Spring獲取類的時候實際返回的是FeignClientFactoryBean類。

FeignClientFactoryBean做爲一個實現了FactoryBean的工廠類,那麼每次在Spring Context 建立實體類的時候會調用它的getObject()方法。

public Object getObject() throws Exception {
    FeignContext context = applicationContext.getBean(FeignContext.class);
    Feign.Builder builder = feign(context);

    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));
    }
    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()其實就是將@FeinClient中設置value值進行組裝起來,此時或許會有疑問,由於在配置FeignClientFactoryBean類時特地說過並無將Configuration傳過來,那麼Configuration中的屬性是如何配置的呢?看其第一句是:

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

從Spring容器中獲取FeignContext.class的類,咱們能夠看下這個類是從哪加載的。點擊該類查看被引用的地方,能夠找到在FeignAutoConfiguration類中有聲明bean:

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

從上面的代碼中能夠看到在set屬性的時候將 FeignClientSpecification 類型的類所有加入此類的屬性中。還記得在上面分析registerFeignClients方法的時候裏面有一行代碼調用: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());
}

在註冊BeanDefinition的時候, configuration 其實也被做爲參數,傳給了 FeignClientSpecification。 因此這時候在FeignContext中是帶着configuration配置信息的。

至此咱們已經完成了配置屬性的裝配工做,那麼是如何執行的呢?咱們能夠看getObject()最後一句能夠看到返回了Targeter.target的方法。

return targeter.target(this, builder, context, new HardCodedTarget<>(this.type, this.name, url));

那麼這個Targeter是哪來的?咱們仍是看上面的FeignAutoConfiguration類,能夠看到其中有兩個Targeter類,一個是DefaultTargeter,一個是HystrixTargeter。當配置了feign.hystrix.enabled = true的時候,Spring容器中就會配置HystrixTargeter此類,若是爲false那麼Spring容器中配置的就是DefaultTargeter

咱們以DefaultTargeter爲例介紹一下接下來是如何經過建立代理對象的:

class DefaultTargeter implements Targeter {

	@Override
	public <T> T target(FeignClientFactoryBean factory, Feign.Builder feign, FeignContext context,
						Target.HardCodedTarget<T> target) {
		return feign.target(target);
	}
}

public static class Builder {

    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);
      ParseHandlersByName handlersByName =
          new ParseHandlersByName(contract, options, encoder, decoder,
                                  errorDecoder, synchronousMethodHandlerFactory);
      return new ReflectiveFeign(handlersByName, invocationHandlerFactory);
    }
 }

在target方法中有個參數:Feign.Builder:

public static class Builder {

    private final List<RequestInterceptor> requestInterceptors =
        new ArrayList<RequestInterceptor>();
    private Logger.Level logLevel = Logger.Level.NONE;
    private Contract contract = new Contract.Default();
    private Client client = new Client.Default(null, null);
    private Retryer retryer = new Retryer.Default();
    private Logger logger = new NoOpLogger();
    private Encoder encoder = new Encoder.Default();
    private Decoder decoder = new Decoder.Default();
    private ErrorDecoder errorDecoder = new ErrorDecoder.Default();
    private Options options = new Options();
    private InvocationHandlerFactory invocationHandlerFactory =
        new InvocationHandlerFactory.Default();
    private boolean decode404;
    
    ......
    ......
    ......
}

構建feign.builder時會向FeignContext獲取配置的Encoder,Decoder等各類信息 。Builder中的參數來自於配置文件的 feign.client.config裏面的屬性。

查看ReflectiveFeign類中newInstance方法是返回一個代理對象:

public <T> T newInstance(Target<T> target) {
    //爲每一個方法建立一個SynchronousMethodHandler對象,並放在 Map 裏面
    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)) {
            //若是是 default 方法,說明已經有實現了,用 DefaultHandler
            DefaultMethodHandler handler = new DefaultMethodHandler(method);
            defaultMethodHandlers.add(handler);
            methodToHandler.put(method, handler);
        } else {
            //不然就用上面的 SynchronousMethodHandler
            methodToHandler.put(method, nameToHandler.get(Feign.configKey(target.type(), method)));
        }
    }
    // 設置攔截器
    // 建立動態代理,factory 是 InvocationHandlerFactory.Default,建立出來的是 
    // ReflectiveFeign.FeignInvocationHanlder,也就是說後續對方法的調用都會進入到該對象的 inovke 方法
    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;
}

這個方法大概的邏輯是:

  1. 根據target,解析生成MethodHandler對象;
  2. MethodHandler對象進行分類整理,整理成兩類:default 方法和 SynchronousMethodHandler 方法;
  3. 經過jdk動態代理生成代理對象,這裏是最關鍵的地方;
  4. DefaultMethodHandler綁定到代理對象。

最終都是執行了SynchronousMethodHandler攔截器中的invoke方法:

@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) {
        retryer.continueOrPropagate(e);
        if (logLevel != Logger.Level.NONE) {
          logger.logRetry(metadata.configKey(), logLevel);
        }
        continue;
      }
    }
  }

invoke方法方法首先生成 RequestTemplate 對象,應用 encoder,decoder 以及 retry 等配置,下面有一個死循環調用:executeAndDecode,從名字上看就是執行調用邏輯並對返回結果解析。

Object executeAndDecode(RequestTemplate template) throws Throwable {
    //根據  RequestTemplate生成Request對象
    Request request = targetRequest(template);

    if (logLevel != Logger.Level.NONE) {
        logger.logRequest(metadata.configKey(), logLevel, request);
    }

    Response response;
    long start = System.nanoTime();
    try {
        // 調用client對象的execute()方法執行http調用邏輯,
        //execute()內部可能設置request對象,也可能不設置,因此須要response.toBuilder().request(request).build();這一行代碼
        response = client.execute(request, options);
        // ensure the request is set. TODO: remove in Feign 10
        response.toBuilder().request(request).build();
    } catch (IOException e) {
        if (logLevel != Logger.Level.NONE) {
            logger.logIOException(metadata.configKey(), logLevel, e, elapsedTime(start));
        }
        // IOException的時候,包裝成 RetryableException異常,上面的while循環 catch裏捕捉的就是這個異常
        throw errorExecuting(request, e);
    }
    //統計 執行調用花費的時間
    long elapsedTime = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start);

    boolean shouldClose = true;
    try {
        if (logLevel != Logger.Level.NONE) {
            response =
                logger.logAndRebufferResponse(metadata.configKey(), logLevel, response, elapsedTime);
            // ensure the request is set. TODO: remove in Feign 10
            response.toBuilder().request(request).build();
        }
        //若是元數據返回類型是 Response,直接返回回去便可,不須要decode()解碼
        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();
        }
        //主要對2xx和404等進行解碼,404須要特別的開關控制。其餘狀況,使用errorDecoder進行解碼,以異常的方式返回
        if (response.status() >= 200 && response.status() < 300) {
            if (void.class == metadata.returnType()) {
                return null;
            } else {
                return decode(response);
            }
        } else if (decode404 && response.status() == 404 && void.class != metadata.returnType()) {
            return decode(response);
        } else {
            throw errorDecoder.decode(metadata.configKey(), response);
        }
    } catch (IOException e) {
        if (logLevel != Logger.Level.NONE) {
            logger.logIOException(metadata.configKey(), logLevel, e, elapsedTime);
        }
        throw errorReading(request, response, e);
    } finally {
        if (shouldClose) {
            ensureClosed(response.body());
        }
    }
}

這裏主要就是使用:client.execute(request, options) 來發起調用,下面基本都是處理返回結果的邏輯。到此咱們的整個調用生態已經解析完畢。

咱們能夠整理一下上面的分析:

首先調用接口爲何會直接發送請求?

緣由就是Spring掃描了@FeignClient註解,而且根據配置的信息生成代理類,調用的接口實際上調用的是生成的代理類。

其次請求是如何被Feign接管的?

  1. Feign經過掃描@EnableFeignClients註解中配置包路徑,掃描@FeignClient註解並將註解配置的信息注入到Spring容器中,類型爲FeignClientFactoryBean
  2. 而後經過FeignClientFactoryBeangetObject()方法獲得不一樣動態代理的類併爲每一個方法建立一個SynchronousMethodHandler對象;
  3. 爲每個方法建立一個動態代理對象, 動態代理的實現是 ReflectiveFeign.FeignInvocationHanlder,代理被調用的時候,會根據當前調用的方法,轉到對應的 SynchronousMethodHandler

這樣咱們發出的請求就可以被已經配置好各類參數的Feign handler進行處理,從而被Feign託管。

請求如何被Feign分發的?

上一個問題已經回答了Feign將每一個方法都封裝成爲代理對象,那麼當該方法被調用時,真正執行邏輯的是封裝好的代理對象進行處理,執行對應的服務調用邏輯。

相關文章
相關標籤/搜索