前面已经学习了两个Spring Cloud 组件:

  • Eureka:实现服务注册功能;
  • Ribbon:提供基于RestTemplate的HTTP客户端并且支持服务负载均衡功能。

通过这两个组件我们暂时可以完成服务注册和可配置负载均衡的服务调用。今天我们要学习的是Feign,那么Feign解决了什么问题呢?

相对于Eureka,Ribbon来说,Feign的地位好像不是那么重要,Feign是一个声明式的REST客户端,它的目的就是让REST调用更加简单。通过提供HTTP请求模板,让Ribbon请求的书写更加简单和便捷。另外,在Feign中整合了Ribbon,从而不需要显式的声明Ribbon的jar包。

前面在使用Ribbon+RestTemplate时,利用RestTemplate对http请求的封装处理,形成了一套模版化的调用方法。但是在实际开发中,由于对服务依赖的调用可能不止一处,往往一个接口会被多处调用,所以通常都会针对每个微服务自行封装一些客户端类来包装这些依赖服务的调用。所以,Feign在此基础上做了进一步封装,由他来帮助我们定义和实现依赖服务接口的定义。在Feign的实现下,我们只需创建一个接口并使用注解的方式来配置它(以前是Dao接口上面标注Mapper注解,现在是一个微服务接口上面标注一个Feign注解即可),即可完成对服务提供方的接口绑定,简化了使用Spring Cloud Ribbon时,自动封装服务调用客户端的开发量。

Feign的使用:

1.引入依赖:

  1. <dependency>
  2. <groupId>org.springframework.cloud</groupId>
  3. <artifactId>spring-cloud-starter-openfeign</artifactId>
  4. </dependency>

另外,我们需要添加spring-cloud-dependencies

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  3. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
  4. <modelVersion>4.0.0</modelVersion>
  5. <parent>
  6. <groupId>org.springframework.boot</groupId>
  7. <artifactId>spring-boot-starter-parent</artifactId>
  8. <version>2.2.0.RELEASE</version>
  9. <relativePath/> <!-- lookup parent from repository -->
  10. </parent>
  11. <groupId>com.rickiyang.learn</groupId>
  12. <artifactId>feign-consumer</artifactId>
  13. <version>0.0.1-SNAPSHOT</version>
  14. <name>feign-consumer</name>
  15. <description>Demo project for Spring Boot</description>
  16. <properties>
  17. <java.version>1.8</java.version>
  18. <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  19. <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
  20. <spring-cloud.version>Finchley.RELEASE</spring-cloud.version>
  21. </properties>
  22. <dependencies>
  23. <dependency>
  24. <groupId>org.springframework.boot</groupId>
  25. <artifactId>spring-boot-starter</artifactId>
  26. </dependency>
  27. <dependency>
  28. <groupId>org.springframework.boot</groupId>
  29. <artifactId>spring-boot-starter-test</artifactId>
  30. <scope>test</scope>
  31. </dependency>
  32. <dependency>
  33. <groupId>org.springframework.cloud</groupId>
  34. <artifactId>spring-cloud-starter-openfeign</artifactId>
  35. </dependency>
  36. <dependency>
  37. <groupId>org.springframework.boot</groupId>
  38. <artifactId>spring-boot-starter-web</artifactId>
  39. </dependency>
  40. <dependency>
  41. <groupId>org.springframework.cloud</groupId>
  42. <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
  43. </dependency>
  44. <dependency>
  45. <groupId>org.springframework.cloud</groupId>
  46. <artifactId>spring-cloud-starter-eureka</artifactId>
  47. </dependency>
  48. </dependencies>
  49. <dependencyManagement>
  50. <dependencies>
  51. <dependency>
  52. <groupId>org.springframework.cloud</groupId>
  53. <artifactId>spring-cloud-dependencies</artifactId>
  54. <version>${spring-cloud.version}</version>
  55. <type>pom</type>
  56. <scope>import</scope>
  57. </dependency>
  58. </dependencies>
  59. </dependencyManagement>
  60. <build>
  61. <plugins>
  62. <plugin>
  63. <groupId>org.springframework.boot</groupId>
  64. <artifactId>spring-boot-maven-plugin</artifactId>
  65. </plugin>
  66. </plugins>
  67. </build>
  68. </project>

2.接下来,我们需要在主类中添加 @EnableFeignClients

  1. package com.rickiyang.learn;
  2. import org.springframework.boot.SpringApplication;
  3. import org.springframework.boot.autoconfigure.SpringBootApplication;
  4. import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
  5. import org.springframework.cloud.openfeign.EnableFeignClients;
  6. @EnableDiscoveryClient
  7. @EnableFeignClients
  8. @SpringBootApplication
  9. public class FeignConsumerApplication {
  10. public static void main(String[] args) {
  11. SpringApplication.run(FeignConsumerApplication.class, args);
  12. }
  13. }

3.再来看一下用Feign写的HTTP请求的格式:

  1. package com.rickiyang.learn.service;
  2. import com.rickiyang.learn.entity.Person;
  3. import org.springframework.cloud.openfeign.FeignClient;
  4. import org.springframework.web.bind.annotation.*;
  5. /**
  6. * @author: rickiyang
  7. * @date: 2019/10/5
  8. * @description:
  9. */
  10. @FeignClient(name= "eureka-client")
  11. public interface HelloRemote {
  12. @RequestMapping(value = "/hello/{name}")
  13. String hello(@PathVariable(value = "name") String name);
  14. @PostMapping(value ="/add",produces = "application/json; charset=UTF-8")
  15. String addPerson(@RequestBody Person person);
  16. @GetMapping("/getPerson/{id}")
  17. String getPerson(@PathVariable("id") Integer id);
  18. }

用FeignClient注解申明要调用的服务是哪个,该服务中的方法都有我们常见的Restful方式的API来声明,这种方式大家是不是感觉像是在写Restful接口一样。

代码示例:点击这里

note:

示例代码的正确打开方式:先启动服务端,然后启动一个client端,再次启动 feign-consumer,调用 feign-consumer中的接口即可。

还记得在Ribbon学习的时候使用RestTemplate发起HTTP请求的方式吗:

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

将整个的请求URL和参数都放在一起,虽然没有什么问题,总归不是那么优雅。使用Feign之后你可以使用Restful方式进行调用,写起来也会更加清晰。

Feign调用过程分析

上面简单介绍了Feign的使用方式,大家可以结合着代码示例运行一下,了解基本的使用方式。接下来我们一起分析Feign的调用过程,我们带着两个问题去跟踪:

1.请求如何被Feign 统一托管;

2.Feign如何分发请求。

这两个问题应该就涵盖了Feign的功能,下面就出发去一探究竟。

我们还和以前一样从一个入口进行深入,首先看启动类上的 @EnableFeignClients 注解:

  1. package org.springframework.cloud.openfeign;
  2. import java.lang.annotation.Documented;
  3. import java.lang.annotation.ElementType;
  4. import java.lang.annotation.Retention;
  5. import java.lang.annotation.RetentionPolicy;
  6. import java.lang.annotation.Target;
  7. import org.springframework.context.annotation.Import;
  8. /**
  9. * Scans for interfaces that declare they are feign clients (via {@link FeignClient
  10. * <code>@FeignClient</code>}). Configures component scanning directives for use with
  11. * {@link org.springframework.context.annotation.Configuration
  12. * <code>@Configuration</code>} classes.
  13. *
  14. * @author Spencer Gibb
  15. * @author Dave Syer
  16. * @since 1.0
  17. */
  18. @Retention(RetentionPolicy.RUNTIME)
  19. @Target(ElementType.TYPE)
  20. @Documented
  21. @Import(FeignClientsRegistrar.class)
  22. public @interface EnableFeignClients {
  23. //等价于basePackages属性,更简洁的方式
  24. String[] value() default {};
  25. //指定多个包名进行扫描
  26. String[] basePackages() default {};
  27. //指定多个类或接口的class,扫描时会在这些指定的类和接口所属的包进行扫描
  28. Class<?>[] basePackageClasses() default {};
  29. //为所有的Feign Client设置默认配置类
  30. Class<?>[] defaultConfiguration() default {};
  31. //指定用@FeignClient注释的类列表。如果该项配置不为空,则不会进行类路径扫描
  32. Class<?>[] clients() default {};
  33. }

注释上说了该注解用于扫描 FeignClient 声明的类。我们用 FeignClient 注解去声明一个 Eureka 客户端,那么猜想这里应该是取到我们声明的Eureka client名称,然后去访问Eureka server获取服务提供者。

同样的,为所有Feign Client 也支持文件属性的配置,如下 :

  1. feign:
  2. client:
  3. config:
  4. # 默认为所有的feign client做配置(注意和上例github-client是同级的)
  5. default:
  6. connectTimeout: 5000 # 连接超时时间
  7. readTimeout: 5000 # 读超时时间设置

注 : 如果通过Java代码进行了配置,又通过配置文件进行了配置,则配置文件的中的Feign配置会覆盖Java代码的配置。

但也可以设置feign.client.defalult-to-properties=false,禁用掉feign配置文件的方式让Java配置生效。

注意到类头声明的 @Import 注解引用的 FeignClientsRegistrar 类,这个类的作用是在 EnableFeignClients 初始化的时候扫描该注解对应的配置。

接着看 FeignClient 注解:

  1. package org.springframework.cloud.openfeign;
  2. import java.lang.annotation.Documented;
  3. import java.lang.annotation.ElementType;
  4. import java.lang.annotation.Retention;
  5. import java.lang.annotation.RetentionPolicy;
  6. import java.lang.annotation.Target;
  7. import org.springframework.core.annotation.AliasFor;
  8. @Target(ElementType.TYPE)
  9. @Retention(RetentionPolicy.RUNTIME)
  10. @Documented
  11. public @interface FeignClient {
  12. //指定Feign Client的名称,如果项目使用了 Ribbon,name属性会作为微服务的名称,用于服务发现
  13. @AliasFor("name")
  14. String value() default "";
  15. //用serviceId做服务发现已经被废弃,所以不推荐使用该配置
  16. @Deprecated
  17. String serviceId() default "";
  18. //指定Feign Client的serviceId,如果项目使用了 Ribbon,将使用serviceId用于服务发现,但上面可以看到serviceId做服务发现已经被废弃,所以也不推荐使用该配置
  19. @AliasFor("value")
  20. String name() default "";
  21. //为Feign Client 新增注解@Qualifier
  22. String qualifier() default "";
  23. //请求地址的绝对URL,或者解析的主机名
  24. String url() default "";
  25. //调用该feign client发生了常见的404错误时,是否调用decoder进行解码异常信息返回,否则抛出FeignException
  26. boolean decode404() default false;
  27. //Feign Client设置默认配置类
  28. Class<?>[] configuration() default {};
  29. //定义容错的处理类,当调用远程接口失败或超时时,会调用对应接口的容错逻辑,fallback 指定的类必须实现@FeignClient标记的接口。实现的法方法即对应接口的容错处理逻辑
  30. Class<?> fallback() default void.class;
  31. //工厂类,用于生成fallback 类示例,通过这个属性我们可以实现每个接口通用的容错逻辑,减少重复的代码
  32. Class<?> fallbackFactory() default void.class;
  33. //定义当前FeignClient的所有方法映射加统一前缀
  34. String path() default "";
  35. //是否将此Feign代理标记为一个Primary Bean,默认为ture
  36. boolean primary() default true;
  37. }

同样在 FeignClientsRegistrar 类中也会去扫描 FeignClient 注解对应的配置信息。我们直接看 FeignClientsRegistrar 的逻辑:

  1. class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar,
  2. ResourceLoaderAware, EnvironmentAware {
  3. // patterned after Spring Integration IntegrationComponentScanRegistrar
  4. // and RibbonClientsConfigurationRegistgrar
  5. private ResourceLoader resourceLoader;
  6. private Environment environment;
  7. public FeignClientsRegistrar() {
  8. }
  9. @Override
  10. public void setResourceLoader(ResourceLoader resourceLoader) {
  11. this.resourceLoader = resourceLoader;
  12. }
  13. //在这个重载的方法里面做了两件事情:
  14. //1.将EnableFeignClients注解对应的配置属性注入
  15. //2.将FeignClient注解对应的属性注入
  16. @Override
  17. public void registerBeanDefinitions(AnnotationMetadata metadata,
  18. BeanDefinitionRegistry registry) {
  19. //注入EnableFeignClients注解对应的配置属性
  20. registerDefaultConfiguration(metadata, registry);
  21. //注入FeignClient注解对应的属性
  22. registerFeignClients(metadata, registry);
  23. }
  24. /**
  25. * 拿到 EnableFeignClients注解 defaultConfiguration 字段的值
  26. * 然后进行注入
  27. *
  28. **/
  29. private void registerDefaultConfiguration(AnnotationMetadata metadata,
  30. BeanDefinitionRegistry registry) {
  31. Map<String, Object> defaultAttrs = metadata
  32. .getAnnotationAttributes(EnableFeignClients.class.getName(), true);
  33. if (defaultAttrs != null && defaultAttrs.containsKey("defaultConfiguration")) {
  34. String name;
  35. if (metadata.hasEnclosingClass()) {
  36. name = "default." + metadata.getEnclosingClassName();
  37. }
  38. else {
  39. name = "default." + metadata.getClassName();
  40. }
  41. registerClientConfiguration(registry, name,
  42. defaultAttrs.get("defaultConfiguration"));
  43. }
  44. }
  45. public void registerFeignClients(AnnotationMetadata metadata,
  46. BeanDefinitionRegistry registry) {
  47. // 获取ClassPath扫描器
  48. ClassPathScanningCandidateComponentProvider scanner = getScanner();
  49. // 为扫描器设置资源加载器
  50. scanner.setResourceLoader(this.resourceLoader);
  51. Set<String> basePackages;
  52. // 1. 从@EnableFeignClients注解中获取到配置的各个属性值
  53. Map<String, Object> attrs = metadata
  54. .getAnnotationAttributes(EnableFeignClients.class.getName());
  55. // 2. 注解类型过滤器,只过滤@FeignClient
  56. AnnotationTypeFilter annotationTypeFilter = new AnnotationTypeFilter(
  57. FeignClient.class);
  58. // 3. 从1. 中的属性值中获取clients属性的值
  59. final Class<?>[] clients = attrs == null ? null
  60. : (Class<?>[]) attrs.get("clients");
  61. if (clients == null || clients.length == 0) {
  62. // 扫描器设置过滤器且获取需要扫描的基础包集合
  63. scanner.addIncludeFilter(annotationTypeFilter);
  64. basePackages = getBasePackages(metadata);
  65. }else {
  66. // clients属性值不为null,则将其clazz路径转为包路径
  67. final Set<String> clientClasses = new HashSet<>();
  68. basePackages = new HashSet<>();
  69. for (Class<?> clazz : clients) {
  70. basePackages.add(ClassUtils.getPackageName(clazz));
  71. clientClasses.add(clazz.getCanonicalName());
  72. }
  73. AbstractClassTestingTypeFilter filter = new AbstractClassTestingTypeFilter() {
  74. @Override
  75. protected boolean match(ClassMetadata metadata) {
  76. String cleaned = metadata.getClassName().replaceAll("\\$", ".");
  77. return clientClasses.contains(cleaned);
  78. }
  79. };
  80. scanner.addIncludeFilter(
  81. new AllTypeFilter(Arrays.asList(filter, annotationTypeFilter)));
  82. }
  83. // 3. 扫描基础包,且满足过滤条件下的接口封装成BeanDefinition
  84. for (String basePackage : basePackages) {
  85. Set<BeanDefinition> candidateComponents = scanner
  86. .findCandidateComponents(basePackage);
  87. // 遍历扫描到的bean定义
  88. for (BeanDefinition candidateComponent : candidateComponents) {
  89. if (candidateComponent instanceof AnnotatedBeanDefinition) {
  90. // 并校验扫描到的bean定义类是一个接口
  91. AnnotatedBeanDefinition beanDefinition = (AnnotatedBeanDefinition) candidateComponent;
  92. AnnotationMetadata annotationMetadata = beanDefinition.getMetadata();
  93. Assert.isTrue(annotationMetadata.isInterface(),
  94. "@FeignClient can only be specified on an interface");
  95. // 获取@FeignClient注解上的各个属性值
  96. Map<String, Object> attributes = annotationMetadata
  97. .getAnnotationAttributes(
  98. FeignClient.class.getCanonicalName());
  99. String name = getClientName(attributes);
  100. // 可以看到这里也注册了一个FeignClient的配置bean
  101. registerClientConfiguration(registry, name,
  102. attributes.get("configuration"));
  103. // 注册bean定义到spring中
  104. registerFeignClient(registry, annotationMetadata, attributes);
  105. }
  106. }
  107. }
  108. }
  109. /**
  110. * 注册bean
  111. **/
  112. private void registerFeignClient(BeanDefinitionRegistry registry,
  113. AnnotationMetadata annotationMetadata, Map<String, Object> attributes) {
  114. // 1.获取类名称,也就是本例中的FeignService接口
  115. String className = annotationMetadata.getClassName();
  116. // 2.BeanDefinitionBuilder的主要作用就是构建一个AbstractBeanDefinition
  117. // AbstractBeanDefinition类最终被构建成一个BeanDefinitionHolder
  118. // 然后注册到Spring中
  119. // 注意:beanDefinition类为FeignClientFactoryBean,故在Spring获取类的时候实际返回的是
  120. // FeignClientFactoryBean类
  121. BeanDefinitionBuilder definition = BeanDefinitionBuilder
  122. .genericBeanDefinition(FeignClientFactoryBean.class);
  123. validate(attributes);
  124. // 3.添加FeignClientFactoryBean的属性,
  125. // 这些属性也都是我们在@FeignClient中定义的属性
  126. definition.addPropertyValue("url", getUrl(attributes));
  127. definition.addPropertyValue("path", getPath(attributes));
  128. String name = getName(attributes);
  129. definition.addPropertyValue("name", name);
  130. definition.addPropertyValue("type", className);
  131. definition.addPropertyValue("decode404", attributes.get("decode404"));
  132. definition.addPropertyValue("fallback", attributes.get("fallback"));
  133. definition.addPropertyValue("fallbackFactory", attributes.get("fallbackFactory"));
  134. definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
  135. // 4.设置别名 name就是我们在@FeignClient中定义的name属性
  136. String alias = name + "FeignClient";
  137. AbstractBeanDefinition beanDefinition = definition.getBeanDefinition();
  138. boolean primary = (Boolean)attributes.get("primary"); // has a default, won't be null
  139. beanDefinition.setPrimary(primary);
  140. String qualifier = getQualifier(attributes);
  141. if (StringUtils.hasText(qualifier)) {
  142. alias = qualifier;
  143. }
  144. // 5.定义BeanDefinitionHolder,
  145. // 在本例中 名称为FeignService,类为FeignClientFactoryBean
  146. BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className, new String[] { alias });
  147. BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
  148. }
  149. private void validate(Map<String, Object> attributes) {
  150. AnnotationAttributes annotation = AnnotationAttributes.fromMap(attributes);
  151. // This blows up if an aliased property is overspecified
  152. // FIXME annotation.getAliasedString("name", FeignClient.class, null);
  153. Assert.isTrue(
  154. !annotation.getClass("fallback").isInterface(),
  155. "Fallback class must implement the interface annotated by @FeignClient"
  156. );
  157. Assert.isTrue(
  158. !annotation.getClass("fallbackFactory").isInterface(),
  159. "Fallback factory must produce instances of fallback classes that implement the interface annotated by @FeignClient"
  160. );
  161. }
  162. ......
  163. ......
  164. ......
  165. }

在这里做了两件事情:

  1. 将EnableFeignClients注解对应的配置属性注入;
  2. 将FeignClient注解对应的属性注入。

生成FeignClient对应的bean,注入到Spring 的IOC容器。

在registerFeignClient方法中构造了一个BeanDefinitionBuilder对象,BeanDefinitionBuilder的主要作用就是构建一个AbstractBeanDefinition,AbstractBeanDefinition类最终被构建成一个BeanDefinitionHolder 然后注册到Spring中。

beanDefinition类为FeignClientFactoryBean,故在Spring获取类的时候实际返回的是FeignClientFactoryBean类。

FeignClientFactoryBean作为一个实现了FactoryBean的工厂类,那么每次在Spring Context 创建实体类的时候会调用它的getObject()方法。

  1. public Object getObject() throws Exception {
  2. FeignContext context = applicationContext.getBean(FeignContext.class);
  3. Feign.Builder builder = feign(context);
  4. if (!StringUtils.hasText(this.url)) {
  5. String url;
  6. if (!this.name.startsWith("http")) {
  7. url = "http://" + this.name;
  8. }
  9. else {
  10. url = this.name;
  11. }
  12. url += cleanPath();
  13. return loadBalance(builder, context, new HardCodedTarget<>(this.type, this.name, url));
  14. }
  15. if (StringUtils.hasText(this.url) && !this.url.startsWith("http")) {
  16. this.url = "http://" + this.url;
  17. }
  18. String url = this.url + cleanPath();
  19. Client client = getOptional(context, Client.class);
  20. if (client != null) {
  21. if (client instanceof LoadBalancerFeignClient) {
  22. // not lod balancing because we have a url,
  23. // but ribbon is on the classpath, so unwrap
  24. client = ((LoadBalancerFeignClient)client).getDelegate();
  25. }
  26. builder.client(client);
  27. }
  28. Targeter targeter = get(context, Targeter.class);
  29. return targeter.target(this, builder, context, new HardCodedTarget<>(
  30. this.type, this.name, url));
  31. }

这里的getObject()其实就是将@FeinClient中设置value值进行组装起来,此时或许会有疑问,因为在配置FeignClientFactoryBean类时特意说过并没有将Configuration传过来,那么Configuration中的属性是如何配置的呢?看其第一句是:

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

从Spring容器中获取FeignContext.class的类,我们可以看下这个类是从哪加载的。点击该类查看被引用的地方,可以找到在FeignAutoConfiguration类中有声明bean:

  1. @Configuration
  2. @ConditionalOnClass(Feign.class)
  3. @EnableConfigurationProperties({FeignClientProperties.class, FeignHttpClientProperties.class})
  4. public class FeignAutoConfiguration {
  5. @Autowired(required = false)
  6. private List<FeignClientSpecification> configurations = new ArrayList<>();
  7. @Bean
  8. public HasFeatures feignFeature() {
  9. return HasFeatures.namedFeature("Feign", Feign.class);
  10. }
  11. @Bean
  12. public FeignContext feignContext() {
  13. FeignContext context = new FeignContext();
  14. context.setConfigurations(this.configurations);
  15. return context;
  16. }
  17. ......
  18. ......
  19. ......
  20. }

从上面的代码中可以看到在set属性的时候将 FeignClientSpecification 类型的类全部加入此类的属性中。还记得在上面分析registerFeignClients方法的时候里面有一行代码调用:registerClientConfiguration()方法:

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

在注册BeanDefinition的时候, configuration 其实也被作为参数,传给了 FeignClientSpecification。 所以这时候在FeignContext中是带着configuration配置信息的。

至此我们已经完成了配置属性的装配工作,那么是如何执行的呢?我们可以看getObject()最后一句可以看到返回了Targeter.target的方法。

  1. 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为例介绍一下接下来是如何通过创建代理对象的:

  1. class DefaultTargeter implements Targeter {
  2. @Override
  3. public <T> T target(FeignClientFactoryBean factory, Feign.Builder feign, FeignContext context,
  4. Target.HardCodedTarget<T> target) {
  5. return feign.target(target);
  6. }
  7. }
  8. public static class Builder {
  9. public <T> T target(Target<T> target) {
  10. return build().newInstance(target);
  11. }
  12. public Feign build() {
  13. SynchronousMethodHandler.Factory synchronousMethodHandlerFactory =
  14. new SynchronousMethodHandler.Factory(client, retryer, requestInterceptors, logger,
  15. logLevel, decode404);
  16. ParseHandlersByName handlersByName =
  17. new ParseHandlersByName(contract, options, encoder, decoder,
  18. errorDecoder, synchronousMethodHandlerFactory);
  19. return new ReflectiveFeign(handlersByName, invocationHandlerFactory);
  20. }
  21. }

在target方法中有个参数:Feign.Builder:

  1. public static class Builder {
  2. private final List<RequestInterceptor> requestInterceptors =
  3. new ArrayList<RequestInterceptor>();
  4. private Logger.Level logLevel = Logger.Level.NONE;
  5. private Contract contract = new Contract.Default();
  6. private Client client = new Client.Default(null, null);
  7. private Retryer retryer = new Retryer.Default();
  8. private Logger logger = new NoOpLogger();
  9. private Encoder encoder = new Encoder.Default();
  10. private Decoder decoder = new Decoder.Default();
  11. private ErrorDecoder errorDecoder = new ErrorDecoder.Default();
  12. private Options options = new Options();
  13. private InvocationHandlerFactory invocationHandlerFactory =
  14. new InvocationHandlerFactory.Default();
  15. private boolean decode404;
  16. ......
  17. ......
  18. ......
  19. }

构建feign.builder时会向FeignContext获取配置的Encoder,Decoder等各种信息 。Builder中的参数来自于配置文件的 feign.client.config里面的属性。

查看ReflectiveFeign类中newInstance方法是返回一个代理对象:

  1. public <T> T newInstance(Target<T> target) {
  2. //为每个方法创建一个SynchronousMethodHandler对象,并放在 Map 里面
  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. } else if(Util.isDefault(method)) {
  10. //如果是 default 方法,说明已经有实现了,用 DefaultHandler
  11. DefaultMethodHandler handler = new DefaultMethodHandler(method);
  12. defaultMethodHandlers.add(handler);
  13. methodToHandler.put(method, handler);
  14. } else {
  15. //否则就用上面的 SynchronousMethodHandler
  16. methodToHandler.put(method, nameToHandler.get(Feign.configKey(target.type(), method)));
  17. }
  18. }
  19. // 设置拦截器
  20. // 创建动态代理,factory 是 InvocationHandlerFactory.Default,创建出来的是
  21. // ReflectiveFeign.FeignInvocationHanlder,也就是说后续对方法的调用都会进入到该对象的 inovke 方法
  22. InvocationHandler handler = factory.create(target, methodToHandler);
  23. // 创建动态代理对象
  24. T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(), new Class<?>[]{target.type()}, handler);
  25. for(DefaultMethodHandler defaultMethodHandler : defaultMethodHandlers) {
  26. defaultMethodHandler.bindTo(proxy);
  27. }
  28. return proxy;
  29. }

这个方法大概的逻辑是:

  1. 根据target,解析生成MethodHandler对象;
  2. MethodHandler对象进行分类整理,整理成两类:default 方法和 SynchronousMethodHandler 方法;
  3. 通过jdk动态代理生成代理对象,这里是最关键的地方;
  4. DefaultMethodHandler绑定到代理对象。

最终都是执行了SynchronousMethodHandler拦截器中的invoke方法:

  1. @Override
  2. public Object invoke(Object[] argv) throws Throwable {
  3. RequestTemplate template = buildTemplateFromArgs.create(argv);
  4. Retryer retryer = this.retryer.clone();
  5. while (true) {
  6. try {
  7. return executeAndDecode(template);
  8. } catch (RetryableException e) {
  9. retryer.continueOrPropagate(e);
  10. if (logLevel != Logger.Level.NONE) {
  11. logger.logRetry(metadata.configKey(), logLevel);
  12. }
  13. continue;
  14. }
  15. }
  16. }

invoke方法方法首先生成 RequestTemplate 对象,应用 encoder,decoder 以及 retry 等配置,下面有一个死循环调用:executeAndDecode,从名字上看就是执行调用逻辑并对返回结果解析。

  1. Object executeAndDecode(RequestTemplate template) throws Throwable {
  2. //根据 RequestTemplate生成Request对象
  3. Request request = targetRequest(template);
  4. if (logLevel != Logger.Level.NONE) {
  5. logger.logRequest(metadata.configKey(), logLevel, request);
  6. }
  7. Response response;
  8. long start = System.nanoTime();
  9. try {
  10. // 调用client对象的execute()方法执行http调用逻辑,
  11. //execute()内部可能设置request对象,也可能不设置,所以需要response.toBuilder().request(request).build();这一行代码
  12. response = client.execute(request, options);
  13. // ensure the request is set. TODO: remove in Feign 10
  14. response.toBuilder().request(request).build();
  15. } catch (IOException e) {
  16. if (logLevel != Logger.Level.NONE) {
  17. logger.logIOException(metadata.configKey(), logLevel, e, elapsedTime(start));
  18. }
  19. // IOException的时候,包装成 RetryableException异常,上面的while循环 catch里捕捉的就是这个异常
  20. throw errorExecuting(request, e);
  21. }
  22. //统计 执行调用花费的时间
  23. long elapsedTime = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start);
  24. boolean shouldClose = true;
  25. try {
  26. if (logLevel != Logger.Level.NONE) {
  27. response =
  28. logger.logAndRebufferResponse(metadata.configKey(), logLevel, response, elapsedTime);
  29. // ensure the request is set. TODO: remove in Feign 10
  30. response.toBuilder().request(request).build();
  31. }
  32. //如果元数据返回类型是 Response,直接返回回去即可,不需要decode()解码
  33. if (Response.class == metadata.returnType()) {
  34. if (response.body() == null) {
  35. return response;
  36. }
  37. if (response.body().length() == null ||
  38. response.body().length() > MAX_RESPONSE_BUFFER_SIZE) {
  39. shouldClose = false;
  40. return response;
  41. }
  42. // Ensure the response body is disconnected
  43. byte[] bodyData = Util.toByteArray(response.body().asInputStream());
  44. return response.toBuilder().body(bodyData).build();
  45. }
  46. //主要对2xx和404等进行解码,404需要特别的开关控制。其他情况,使用errorDecoder进行解码,以异常的方式返回
  47. if (response.status() >= 200 && response.status() < 300) {
  48. if (void.class == metadata.returnType()) {
  49. return null;
  50. } else {
  51. return decode(response);
  52. }
  53. } else if (decode404 && response.status() == 404 && void.class != metadata.returnType()) {
  54. return decode(response);
  55. } else {
  56. throw errorDecoder.decode(metadata.configKey(), response);
  57. }
  58. } catch (IOException e) {
  59. if (logLevel != Logger.Level.NONE) {
  60. logger.logIOException(metadata.configKey(), logLevel, e, elapsedTime);
  61. }
  62. throw errorReading(request, response, e);
  63. } finally {
  64. if (shouldClose) {
  65. ensureClosed(response.body());
  66. }
  67. }
  68. }

这里主要就是使用: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将每个方法都封装成为代理对象,那么当该方法被调用时,真正执行逻辑的是封装好的代理对象进行处理,执行对应的服务调用逻辑。

Spring Cloud Feign 调用过程分析的更多相关文章

  1. Bug集锦-Spring Cloud Feign调用其它接口报错

    问题描述 Spring Cloud Feign调用其它服务报错,错误提示如下:Failed to instantiate [java.util.List]: Specified class is an ...

  2. Spring Boot 和 Spring Cloud Feign调用服务及传递参数踩坑记录

    背景 :在Spring Cloud Netflix栈中,各个微服务都是以HTTP接口的形式暴露自身服务的,因此在调用远程服务时就必须使用HTTP客户端.我们可以使用JDK原生的URLConnectio ...

  3. spring cloud feign 调用接口报错"No message available

    There was an unexpected error (type=Internal Server Error, status=500). status 404 reading HelloServ ...

  4. Spring cloud Feign 调用端不生效

    如果提供方的接口经过测试是没问题的话. 消费方启动类加上@EnableFeignClients 注意定义的接口如果不和启动类在同一个包路径下,需要加basePackages 即:@EnableFeig ...

  5. spring cloud feign 调用服务注意问题

    服务端 rest api @RequestMapping(value = "/phone") public ResponsePhone getPhone(@RequestParam ...

  6. Spring Boot 和 Spring Cloud Feign调用服务及传递参数踩坑记录(转)

    https://blog.csdn.net/uotail/article/details/84673347

  7. Spring cloud Feign 深度学习与应用

    简介 Spring Cloud Feign是一个声明式的Web Service客户端,它的目的就是让Web Service调用更加简单.Feign提供了HTTP请求的模板,通过编写简单的接口和插入注解 ...

  8. 笔记:Spring Cloud Feign 声明式服务调用

    在实际开发中,对于服务依赖的调用可能不止一处,往往一个接口会被多处调用,所以我们通常会针对各个微服务自行封装一些客户端类来包装这些依赖服务的调用,Spring Cloud Feign 在此基础上做了进 ...

  9. 第六章:声明式服务调用:Spring Cloud Feign

    Spring Cloud Feign 是基于 Netflix Feign 实现的,整合了 Spring Cloud Ribbon 和 Spring Cloud Hystrix,除了提供这两者的强大功能 ...

随机推荐

  1. [摘抄] 2. module对象

    2. module对象 Node内部提供一个Module 构建函数,所有函数都是Module的实例. function Moudle(id,parent) { this.id = id; this.e ...

  2. const关键字总结

    在C语言中 const是C语言中总结 1.修饰的变量,使其具有常属性,使变量的值不能直接被改变.但是可以通过指针来间接的修改变量的值. 2.便于进行类型检查(在编译时进行类型检查),使编译对处理内容有 ...

  3. Android源码分析(四)-----Android源码编译及刷机步骤

    一 : 获取源码: 每个公司服务器地址不同,以如下源码地址为例: http://10.1.14.6/android/Qualcomm/msm89xx/branches/msm89xx svn环境执行: ...

  4. NumPy 之 存储文件和线性代数

    import numpy as np File Input and Output NumPy is able to save and load data to and from disk either ...

  5. Underscore——JS函数库

    转载请注明原文地址:https://www.cnblogs.com/ygj0930/p/10826065.html underscore是什么——它是一个js函数库 jQuery统一了不同浏览器之间的 ...

  6. DOM4j XML 工具类

    之前项目有跟客户系统对接一个webservice系统,该接口有传参和返回都是xml,所以找时间百度研究了一下dom4j,dom4j是一个Java的XML API,是jdom的升级品,用来读写XML文件 ...

  7. springboot集成mail实现邮件服务

    1,新建mailconfig类,对邮件服务固定配置进行实体封装: import java.util.Properties; import org.springframework.beans.facto ...

  8. java语言评价--java帝国

    “陛下您想想,我们有很多宝贝,” IO大臣根本不理线程大臣, 继续侃侃而谈:“ 比如IoC, AOP,反射.动态代理.泛型.注解.JDBC.JMS...... 还有我们引以为豪的JVM.这些东西,那些 ...

  9. oracle封装OracleHelper

    1.Oracle 封装类 using System; using System.Collections.Generic; using System.Linq; using System.Text; u ...

  10. BZOJ 3561: DZY Loves Math VI 莫比乌斯反演+复杂度分析

    推到了一个推不下去的形式,然后就不会了 ~ 看题解后傻了:我推的是对的,推不下去是因为不需要再推了. 复杂度看似很大,但其实是均摊 $O(n)$ 的,看来分析复杂度也是一个能力啊 ~ code: #i ...