程序项目代做,有需求私信(小程序、网站、爬虫、电路板设计、驱动、应用程序开发、毕设疑难问题处理等)

Spring Cloud -- OpenFeign 核心原理

当今是微服务横行的时代,各个微服务之间相互调用是一件再平常不过的时候。在采用HTTP协议进行通信的微服务中,我们自己可能去封装一个HttpClient工具类去进行服务间的调用,封装一个HttpClient工具,我们就需要考虑一下这些事情:

  • 我们在发送一个HTTP请求时,我们需要选择请求方式GET、POST、DELETE等,我们需要构建请求参数、构建请求头信息等,那么作为一个工具类我们是不是也要提供各种参数的灵活配置;
  • 因为采用restful API 风格的HTTP请求参数和返回数据都是字符串的格式,那我们是否需要考虑序列化和反序列化问题;
  • 当同一个服务部署到多台服务器的时候,我们是不是应该采用轮询或者随机的方式去选择服务器,这也就是我们常说的负载均衡。从另一方面来说我们的核心是解决服务间的调用,但是我们在设计一个通用HttpClient工具的时候是否也应该支持负载均衡,以及如何和负载均衡高度解耦。

为此,大名鼎鼎的Feign应时而生,我们在学习Feign的实现的时候,我们应该带着这些问题去学习Feign的实现原理。

一、什么是Feign

Feign 是声明式 Web 服务客户端,它使编写 Web 服务客户端更加容易 Feign 不做任何请求处理,通过处理注解相关信息生成 Request,并对调用返回的数据进行解码,从而实现简化HTTP API 的开发。当然你也可以直接使用 Apache HttpClient 来实现Web服务器客户端,但是 Feign 的目的是尽量的减少资源和代码来实现和 HTTP API 的连接。通过自定义的编码解码器以及错误处理,你可以编写任何基于文本的 HTTP API。

如果要使用 Feign,需要创建一个接口并对其添加 Feign 相关注解,另外 Feign 还支持可插拔编码器和解码器,致力于打造一个轻量级 HTTP 客户端。

如果你想直接使用原生的Feign的话,你可以去参考Feign配置使用,下面就是Feign针对一个HTTP API的接口定义:

interface GitHub {
  // RequestLine注解声明请求方法和请求地址,可以允许有查询参数
  @RequestLine("GET /user/list")
  List<User> list();
}

目前由于Spring Cloud微服务的广泛使用,广大开发者更倾向于使用spring-cloud-starter-openfeign,Spring Cloud 添加了对 Spring MVC 注解的支持,在微服务中我们的接口定义有所变化:

@FeignClient(name="服务名",contextId="唯一标识")
interface GitHub {
  @GetMapping("/user/list")
  List<User> list();
}

二、Feign 和 Openfeign 的区别

Feign 最早是由 Netflix 公司进行维护的,后来 Netflix 不再对其进行维护,最终 Feign 由社区进行维护,更名为 Openfeign。

2.1 Starter OpenFeign

当然了,基于 SpringCloud 团队对 Netflix 的情有独钟,你出了这么好用的轻量级 HTTP 客户端,我这老大哥不得支持一下,所以就有了基于 Feign 封装的 Starter。

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

这个包引入了如下依赖:

复制代码
<dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-starter</artifactId>
      <version>2.2.0.RELEASE</version>
      <scope>compile</scope>
    </dependency>
    <dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-openfeign-core</artifactId>
      <version>2.2.0.RELEASE</version>
      <scope>compile</scope>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-web</artifactId>
      <version>5.2.1.RELEASE</version>
      <scope>compile</scope>
    </dependency>
    <dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-commons</artifactId>
      <version>2.2.0.RELEASE</version>
      <scope>compile</scope>
    </dependency>
    <dependency>
      <groupId>io.github.openfeign</groupId>
      <artifactId>feign-core</artifactId>
      <version>10.4.0</version>
      <scope>compile</scope>
    </dependency>
    <dependency>
      <groupId>io.github.openfeign</groupId>
      <artifactId>feign-slf4j</artifactId>
      <version>10.4.0</version>
      <scope>compile</scope>
    </dependency>
    <dependency>
      <groupId>io.github.openfeign</groupId>
      <artifactId>feign-hystrix</artifactId>
      <version>10.4.0</version>
      <scope>compile</scope>
    </dependency>
    <dependency>
      <groupId>io.github.openfeign</groupId>
      <artifactId>feign-java8</artifactId>
      <version>10.4.0</version>
      <scope>compile</scope>
    </dependency>
    <dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
      <version>2.2.0.RELEASE</version>
      <scope>compile</scope>
      <optional>true</optional>
    </dependency>
    <dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-starter-netflix-archaius</artifactId>
      <version>2.2.0.RELEASE</version>
      <scope>compile</scope>
      <optional>true</optional>
    </dependency>
    <dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-starter-loadbalancer</artifactId>
      <version>2.2.0.RELEASE</version>
      <scope>compile</scope>
      <optional>true</optional>
    </dependency>
View Code
复制代码

这里面有两个非常重要的包:

  • 一个是spring-cloud-openfeign-core,这个包是SpringCloud支持Feign的核心包,Spring Cloud 添加了对 Spring MVC 注解的支持(通过SpringMvcContract实现),并支持使用 Spring Web 中默认使用的相同 HttpMessageConverters。另外,Spring Cloud同时集成了Ribbon和Eureka以及Spring Cloud LoadBalancer,以在使用 Feign 时提供负载均衡的 HTTP客户端。针对于注册中心的支持,包含但不限于 Eureka,比如 Consul、Naocs 等注册中心均支持。
  • 另一个包是feign-core,也就是feign的原生包,具体使用细节可以参考Feign配置使用。通俗点说,spring-cloud-openfeign-core就是通过一系列的配置创建Feign.builder()实例的过程。

在我们 SpringCloud 项目开发过程中,使用的大多都是这个 Starter Feign。

2.2 demo

为了方便大家理解,这里写出对应的生产方、消费方 Demo 代码,以及使用的注册中心。

生产者服务:添加 Nacos 服务注册发现注解以及发布出 HTTP 接口服务

复制代码
@EnableDiscoveryClient 
@SpringBootApplication
public class NacosProduceApplication {
    public static void main(String[] args) {
        SpringApplication.run(NacosProduceApplication.class, args);
    }
    @RestController
    static class TestController {
        @GetMapping("/hello")
        public String hello(@RequestParam("name") String name) {
            return "hello " + name;
        }
    }
}
复制代码

消费者服务:

定义 FeignClient 消费服务接口

@FeignClient(name= "nacos-produce",contextId="DemoFeignClient")
public interface DemoFeignClient {
    @GetMapping("/hello")
    String sayHello(@RequestParam("name") String name);
}

因为生产者使用 Nacos,所以消费者除了开启 Feign 注解,同时也要开启 Naocs 服务注册发现。

复制代码
@RestController 
@EnableFeignClients
@EnableDiscoveryClient 
@SpringBootApplication
public class NacosConsumeApplication {
    public static void main(String[] args) {
        SpringApplication.run(NacosConsumeApplication.class, args);
    }

    @Autowired private DemoFeignClient demoFeignClient;

    @GetMapping("/test")
    public String test() {
        String result = demoFeignClient.sayHello("xxxxx");
        return result;
    }
}
复制代码

三、Feign 的启动原理

下文中调试中使用的代码并不是demo中的代码,不过和demo使用的类似,只是业务系统更加复杂而已。

我们在 SpringCloud 的使用过程中,如果想要启动某个组件,一般都是 @Enable... 这种方式注入,Feign 也不例外,我们需要在类上标记此注解 @EnableFeignClients。

3.1 @EnableFeignClients

EnableFeignClients注解,用于扫描使用@FeignClient注解标注的接口, 而该功能是通过@Import(FeignClientsRegistrar.class)完成。

复制代码
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(FeignClientsRegistrar.class)
public @interface EnableFeignClients {

    String[] value() default {};

    String[] basePackages() default {};

    Class<?>[] basePackageClasses() default {};

    Class<?>[] defaultConfiguration() default {};

    Class<?>[] clients() default {};

}
复制代码

FeignClientsRegistrar实现了ImportBeanDefinitionRegistrar, 用以完成相关Bean注册。

ImportBeanDefinitionRegistrar 负责动态注入 IOC Bean,分别注入 Feign 配置类、FeignClient。

复制代码
class FeignClientsRegistrarimplements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware{
        @Override
    public void registerBeanDefinitions(AnnotationMetadata metadata,
            BeanDefinitionRegistry registry) {
        registerDefaultConfiguration(metadata, registry);
        registerFeignClients(metadata, registry);
    }
       ...
}
复制代码

3.2 registerDefaultConfiguration(metadata, registry)

复制代码
    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"));
        }
    }

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());
    }
View Code
复制代码

1. 获取 @EnableFeignClients 注解上的属性以及对应 value。

2.使用BeanDefinitionBuilder构造器为FeignClientSpecification类生成BeanDefinition,这个BeanDefinition是对FeignClientSpecification Bean的定义,保存了FeignClientSpecification   Bean 的各种信息,如属性、构造方法参数等。其中@EnableFeignClients 注解上的defaultConfiguration属性就是作为构造方法参数传入的。而bean名称为 default. + @EnableFeignClients 修饰类(一般是启动类)全限定名称 + FeignClientSpecification

3.@EnableFeignClients defaultConfiguration 默认为 {},如果没有相关配置,默认使用 FeignClientsConfiguration 并结合 name 填充到 FeignClientSpecification,最终会被注册为 IOC Bean

总结下来,就是根据@EnableFeignClients中属性defaultConfiguration,为FeignClientSpecification类型生成BeanDefinition,并注入Spriing容器中。

3.3 registerFeignClients(metadata, registry)

复制代码
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)));
        }

        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"));

                    registerFeignClient(registry, annotationMetadata, attributes);
                }
            }
        }
    }
View Code
复制代码

1. 扫描使用FeignClient注解标注的接口,获取basePackages扫描路径 。

2.根据获取到的扫描路径,然后根据扫描路径,获取该路径将其子路径下,使用FeignClient注解标记的接口。

3.遍历每一个FeignClient注解的类:

  • 收集接口FeignClient注解属性信息,并根据 configuration 属性去创建接口级的 FeignClientSpecification BeanDefinition,然后注入Spring容器。

  • 生成FeignClientFactoryBean 类型的BeanDefinition,并将 @FeignClient 的属性设置到 FeignClientFactoryBean 对象上,然后注入Spring容器。

其中需要注意,在将@FeignClient 的属性设置到 FeignClientFactoryBean 对象,会将@FeignClient的修饰的类的className作为type属性,传递给FeignClientFactoryBean,后续正是通过这个,创建对应的代理类。

总结下来,就是为一个@FeignClient创建一个FeignClientSpecification、FeignClientFactoryBean,FeignClientSpecification保存这个@FeignClient的configuration 属性信息,而FeignClientFactoryBean中收集了这个FeignClient其他的属性。

由于FeignClientFactoryBean 继承自 FactoryBean,也就是说,当我们定义 @FeignClient 修饰接口时,注册到 IOC 容器中 Bean 类型变成了 FeignClientFactoryBean,在 Spring 中,FactoryBean 是一个工厂 Bean,用来创建代理 Bean。工厂 Bean 是一种特殊的 Bean,对于需要获取 Bean 的消费者而言,它是不知道 Bean 是普通 Bean 或是工厂 Bean 的。工厂 Bean 返回的实例不是工厂 Bean 本身,该实例是由工厂 Bean 中 FactoryBean#getObject 逻辑所创建的。更多FactoryBean相关的信息,可以阅读我之前的博客

四、 FeignClient创建过程分析

上面说到 @FeignClient 修饰的接口最终填充到 IOC 容器的类型是 FeignClientFactoryBean,先来看下它是什么。

 4.1 FactoryBean 接口特征

1 .它会在类初始化时执行一段逻辑,依据InitializingBean 接口。

2.如果它被别的类 @Autowired 进行注入,返回的不是它本身,而是 FactoryBean#getObject 返回的类,依据 Spring FactoryBean 接口。

3.它能够获取 Spring 上下文对象,依据 Spring ApplicationContextAware 接口。

先来看它的初始化逻辑都执行了什么:

@Override
public void afterPropertiesSet() {
    Assert.hasText(contextId, "Context id must be set");
    Assert.hasText(name, "Name must be set");
}

没有特别的操作,只是使用断言工具类判断两个字段不为空。ApplicationContextAware 也没什么说的,获取上下文对象赋值到对象的局部变量里,重点以及关键就是 FactoryBean#getObject 方法。

@Override
public Object getObject() throws Exception {
    return getTarget();
}

4.2 FeignClientFactoryBean#getTarget

getTarget 源码方法还是挺长的,这里采用分段的形式展示:

<T> T getTarget() {
   // 从 IOC 容器获取 FeignContext
    FeignContext context = applicationContext.getBean(FeignContext.class);
   // 通过 context 创建 Feign 构造器
    Feign.Builder builder = feign(context);
  ...
}

这里提出一个疑问?FeignContext 什么时候、在哪里被注入到 Spring 容器里的?

用了 SpringBoot 怎么会不使用自动装配的功能呢,FeignContext 就是在 FeignAutoConfiguration 中被成功创建。

在FeignAutoConfiguration中,向Spring容器注入FeignContext :

并设置其配置为configurations ,而configurations 是通过@Autowired注入,即List<FeignClientSpecification>集合。

复制代码
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(Feign.class)
@EnableConfigurationProperties({ FeignClientProperties.class,
        FeignHttpClientProperties.class })
@Import(DefaultGzipDecoderConfiguration.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;
    }
       ...
}
复制代码

4.3 FeignClientFactoryBean#feign

复制代码
    protected Feign.Builder feign(FeignContext context) {
        FeignLoggerFactory loggerFactory = get(context, FeignLoggerFactory.class);
        Logger logger = loggerFactory.create(this.type);

        // @formatter:off
        Feign.Builder builder = get(context, Feign.Builder.class)
                // required values
                .logger(logger)
                .encoder(get(context, Encoder.class))
                .decoder(get(context, Decoder.class))
                .contract(get(context, Contract.class));
        // @formatter:on

        configureFeign(context, builder);

        return builder;
    }
复制代码

feign 方法里日志工厂、编码、解码等类均是通过FeignClientFactoryBean#get(...) 方法得到。

复制代码
    protected <T> T get(FeignContext context, Class<T> type) {
        T instance = context.getInstance(this.contextId, type);
        if (instance == null) {
            throw new IllegalStateException(
                    "No bean found of type " + type + " for " + this.contextId);
        }
        return instance;
    }

//FeignContext方法
public <T> T getInstance(String name, Class<T> type) { //根据name获取context实例 AnnotationConfigApplicationContext context = getContext(name); //根据type类型从子容器获取Bean实例 if (BeanFactoryUtils.beanNamesForTypeIncludingAncestors(context, type).length > 0) { return context.getBean(type); } return null; }
复制代码

这里涉及到 Spring 父子容器的概念,默认子容器FeignContext#contexts为空,获取不到服务名对应 context 则使用FeignContext#createContext新建。

    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)) {
                    this.contexts.put(name, createContext(name));
                }
            }
        }
        return this.contexts.get(name);
    }
复制代码

子容器context创建完之后,如果@FeignClient中配置有configuration。会向子容器中注入一个 configuration属性指定的类型的 Bean。因此我们可以通过configuration对每个@FeignClient做定制化配置、比如Encoder、Decoder、FeignLoggerFactory等等。

复制代码
//这里的name是@FeignContent中的contentId值
  protected AnnotationConfigApplicationContext createContext(String name) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();

         //@FeignClient没有配置configuration属性 不会执行  this.configurations 保存的是FeignClientConfiguration类型的列表,也就是之前我们介绍到的注入Spring容器中的FeignClient配置
        if (this.configurations.containsKey(name)) {
            for (Class<?> configuration : this.configurations.get(name)
                    .getConfiguration()) {
                context.register(configuration);
            }
        }
        for (Map.Entry<String, C> entry : this.configurations.entrySet()) {
            if (entry.getKey().startsWith("default.")) {
                // @EnableFeignClient没有配置defaultConfiguration属性 不会执行
                for (Class<?> configuration : entry.getValue().getConfiguration()) {
                    context.register(configuration);
                }
            }
        }


         // 注入默认配置类FeignClientsConfiguration,会注入默认的feignEncoder、feignDecoder等
        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);
            // jdk11 issue
            // https://github.com/spring-cloud/spring-cloud-netflix/issues/3101
            context.setClassLoader(this.parent.getClassLoader());
        }
        context.setDisplayName(generateDisplayName(name));
        context.refresh();
        return context;
    }
复制代码

关于父子类容器对应关系,以及提供 @FeignClient 服务对应子容器的关系(每一个服务对应一个子容器实例)

需要注意的是上图中的Product1、Product2、Product3并不是说就有三个微服务。而是说有三个@FeignClien服务,三个服务可以对应一个微服务,比如下面这种:

复制代码
Client 1
@FeignClient(name = "optimization-user",contextId="1")
public interface UserRemoteClient {
    @GetMapping("/user/get")
    public User getUser(@RequestParam("id") int id);
}

Client 2
@FeignClient(name = "optimization-user",,contextId="2")
public interface UserRemoteClient2 {
    @GetMapping("/user2/get")
    public User getUser(@RequestParam("id") int id);
}

Client 3
@FeignClient(name = "optimization-user",,contextId="3")
public interface UserRemoteClient2 {
    @GetMapping("/user2/get")
    public User getUser(@RequestParam("id") int id);
}
复制代码

回到FeignContext#getInstance 方法,子容器此时已加载对应 Bean,直接通过 getBean 获取 FeignLoggerFactory。

如法炮制,Feign.Builder、Encoder、Decoder、Contract 都可以通过子容器获取对应 Bean。

复制代码
protected Feign.Builder feign(FeignContext context) {
        FeignLoggerFactory loggerFactory = get(context, FeignLoggerFactory.class);
        Logger logger = loggerFactory.create(this.type);

        // @formatter:off
        Feign.Builder builder = get(context, Feign.Builder.class)
                // required values
                .logger(logger)
                .encoder(get(context, Encoder.class))
                .decoder(get(context, Decoder.class))
                .contract(get(context, Contract.class));
        // @formatter:on

        configureFeign(context, builder);

        return builder;
    }
复制代码

configureFeign 方法主要进行一些配置赋值,比如超时、重试、404 配置等,就不再细说了。

到这里有必要总结一下创建 Spring 代理工厂的前半场代码 :

1. 注入@FeignClient 服务时,其实注入的是 FactoryBean#getObject 返回代理工厂对象。

2.通过 IOC 容器获取 FeignContext 上下文。

3.,创建 Feign.Builder 对象时会创建 Feign 服务对应的子容器。

4.从子容器中获取日志工厂、编码器、解码器等 Bean 为 Feign.Builder 设置配置,比如超时时间、日志级别等属性,每一个服务都可以个性化设置。

4.4 动态生成代理

接下来是最最最重要的地方了,继续FeignClientFactoryBean#getTarget

复制代码
<T> T getTarget() {
        FeignContext context = this.applicationContext.getBean(FeignContext.class);
        Feign.Builder builder = feign(context);
        //判断@FeignClient url属性是否存在
        if (!StringUtils.hasText(this.url)) {
            if (!this.name.startsWith("http")) {
                this.url = "http://" + this.name;
            }
            else {
                this.url = this.name;
            }
            this.url += cleanPath();
           //type就是@FeignClient注解修饰的接口类型
          //name:@FeignClient name属性,ribbon通过这个到注册中心获取服务信息
            return (T) loadBalance(builder, context,
                    new HardCodedTarget<>(this.type, this.name, this.url));
        }
        //存在的话,就不使用负载均衡以及注册中心了
               ...
}
复制代码

因为我们在 @FeignClient 注解是使用 name 而不是 url,所以会执行负载均衡策略的分支。FeignClientFactoryBean#loadBalance:

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

Client: Feign 发送请求以及接收响应等都是由 Client 完成,该类默认 Client.Default,另外支持 HttpClient、OkHttp 等客户端,如:

Feign.builder().client(new OkHttpClient())

代码中的 Client、Targeter 在自动装配时注册,配合上文中的父子容器理论,这两个 Bean 在父容器中存在,所以子容器也可以获取到。FeignClientFactoryBean#getOptional,getOptional和get的区别在于一个是可选,一个是必须的,get中如果从子容器获取不到指定的bean实例,会抛出异常,而getOptional不会:

    protected <T> T getOptional(FeignContext context, Class<T> type) {
        return context.getInstance(this.contextId, type);
    }

因为我们并没有对 Hystix 进行设置,所以Targeter#target走入Feign#target分支:

复制代码
    public <T> T target(FeignClientFactoryBean factory, Feign.Builder feign,
            FeignContext context, Target.HardCodedTarget<T> target) {
        if (!(feign instanceof feign.hystrix.HystrixFeign.Builder)) {
            return feign.target(target);
        }
        feign.hystrix.HystrixFeign.Builder builder = (feign.hystrix.HystrixFeign.Builder) feign;
        String name = StringUtils.isEmpty(factory.getContextId()) ? factory.getName()
                : factory.getContextId();
        SetterFactory setterFactory = getOptional(name, context, SetterFactory.class);
        if (setterFactory != null) {
            builder.setterFactory(setterFactory);
        }
        Class<?> fallback = factory.getFallback();
        if (fallback != void.class) {
            return targetWithFallback(name, context, target, builder, fallback);
        }
        Class<?> fallbackFactory = factory.getFallbackFactory();
        if (fallbackFactory != void.class) {
            return targetWithFallbackFactory(name, context, target, builder,
                    fallbackFactory);
        }

        return feign.target(target);
    }
复制代码

Feign#target中首先会创建反射类 ReflectiveFeign,其中ReflectiveFeign是Feign的实现类:

然后调用ReflectiveFeign#newInstance(target)执行创建实例类:

    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, closeAfterDecode, propagationPolicy);
      ParseHandlersByName handlersByName =
          new ParseHandlersByName(contract, options, encoder, decoder, queryMapEncoder,
              errorDecoder, synchronousMethodHandlerFactory);
      return new ReflectiveFeign(handlersByName, invocationHandlerFactory, queryMapEncoder);
    }
复制代码

亲爱的读者和支持者们,自动博客加入了打赏功能,陆陆续续收到了各位老铁的打赏。在此,我想由衷地感谢每一位对我们博客的支持和打赏。你们的慷慨与支持,是我们前行的动力与源泉。

日期姓名金额
2023-09-06*源19
2023-09-11*朝科88
2023-09-21*号5
2023-09-16*真60
2023-10-26*通9.9
2023-11-04*慎0.66
2023-11-24*恩0.01
2023-12-30I*B1
2024-01-28*兴20
2024-02-01QYing20
2024-02-11*督6
2024-02-18一*x1
2024-02-20c*l18.88
2024-01-01*I5
2024-04-08*程150
2024-04-18*超20
2024-04-26.*V30
2024-05-08D*W5
2024-05-29*辉20
2024-05-30*雄10
2024-06-08*:10
2024-06-23小狮子666
2024-06-28*s6.66
2024-06-29*炼1
2024-06-30*!1
2024-07-08*方20
2024-07-18A*16.66
2024-07-31*北12
2024-08-13*基1
2024-08-23n*s2
2024-09-02*源50
2024-09-04*J2
2024-09-06*强8.8
2024-09-09*波1
2024-09-10*口1
2024-09-10*波1
2024-09-12*波10
2024-09-18*明1.68
2024-09-26B*h10
2024-09-3010
2024-10-02M*i1
2024-10-14*朋10
2024-10-22*海10
2024-10-23*南10
2024-10-26*节6.66
2024-10-27*o5
2024-10-28W*F6.66
2024-10-29R*n6.66
2024-11-02*球6
2024-11-021*鑫6.66
2024-11-25*沙5
2024-11-29C*n2.88
posted @   大奥特曼打小怪兽  阅读(1873)  评论(1编辑  收藏  举报
编辑推荐:
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 没有源码,如何修改代码逻辑?
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
历史上的今天:
2018-04-17 第六节,TensorFlow编程基础案例-保存和恢复模型(中)
2018-04-17 第五节,TensorFlow编程基础案例-session使用(上)
2018-04-17 第四节,线性回归案例
如果有任何技术小问题,欢迎大家交流沟通,共同进步

公告 & 打赏

>>

欢迎打赏支持我 ^_^

最新公告

程序项目代做,有需求私信(小程序、网站、爬虫、电路板设计、驱动、应用程序开发、毕设疑难问题处理等)。

了解更多

点击右上角即可分享
微信分享提示