1 Star 0 Fork 0

懒虫虫/springboot-openfeign

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
克隆/下载
贡献代码
同步代码
取消
提示: 由于 Git 不支持空文件夾,创建文件夹后会生成空的 .keep 文件
Loading...
README
MulanPSL-2.0

openfeign源码解析

1.项目搭建

1.1 项目结构

registry -- 注册中心(当前采用eureka)
person -- 人员服务
  person-api -- 人员相关api提供, 包括 req, resp, service 等
  person-biz -- 人员相关服务接口具体实现, 依赖person-api
  person-provider -- 人员相关微服务启动类, 依赖person-biz
equipment -- 设备服务
  equipment-api -- 设备相关api提供, 包括 req, resp, service 等
  equipment-biz -- 设备相关服务接口具体实现, 依赖equipment-api
  equipment-provider -- 设备相关微服务启动类, 依赖equipment-biz

1.2 服务调用

这里使用eureka作为注册中心,,person和equipment两个web服务作为业务中台,本例中会使用postman调用person服务,person服务中调用equipment服务

1.3 pom

person-api

<parent>
    <artifactId>person</artifactId>
    <groupId>tca</groupId>
    <version>1.0.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>

<artifactId>cloud-person-api</artifactId>

<dependencies>
    <!-- lombok -->
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
    </dependency>

    <!-- 参数校验 -->
    <dependency>
        <groupId>javax.validation</groupId>
        <artifactId>validation-api</artifactId>
    </dependency>

    <!-- openFeign -->
    <!-- 注意: 这里仅仅是demo, 所以没有再引入feign-httpclient相关 -->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-openfeign</artifactId>
    </dependency>

</dependencies>
spring-cloud-starter-openfeign
<dependencies>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-openfeign-core</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-commons</artifactId>
    </dependency>
    <dependency>
        <groupId>io.github.openfeign</groupId>
        <artifactId>feign-core</artifactId>
    </dependency>
    <dependency>
        <groupId>io.github.openfeign</groupId>
        <artifactId>feign-slf4j</artifactId>
    </dependency>
    <!-- hystrix -->
    <dependency>
        <groupId>io.github.openfeign</groupId>
        <artifactId>feign-hystrix</artifactId>
    </dependency>
    <dependency>
        <groupId>io.github.openfeign</groupId>
        <artifactId>feign-java8</artifactId>
    </dependency>
    <!-- ribbon -->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
        <optional>true</optional>
    </dependency>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-archaius</artifactId>
        <optional>true</optional>
    </dependency>
</dependencies>

可以看到,openfeign中已经引入了ribbon,hystrix相关依赖,即openfeign默认集成了 ribbon 和 hystrix 相关

person-biz

<dependencies>
    <!-- 使用web -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <!-- person-api -->
    <dependency>
        <groupId>tca</groupId>
        <artifactId>cloud-person-api</artifactId>
        <version>1.0.0</version>
    </dependency>

    <!-- equipment-api -->
    <dependency>
        <groupId>tca</groupId>
        <artifactId>cloud-equipment-api</artifactId>
        <version>1.0.0</version>
    </dependency>

    <!-- log -->
    <dependency>
        <groupId>tca</groupId>
        <artifactId>common-log-springboot-logback</artifactId>
        <version>1.0.0</version>
    </dependency>
</dependencies>

person-provider

<dependencies>
    <dependency>
        <groupId>tca</groupId>
        <artifactId>cloud-person-biz</artifactId>
        <version>1.0.0</version>
    </dependency>

    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    </dependency>
</dependencies>

equipment与person类似,这里省略

1.4 java代码

启动类

/**
 * @author zhouan
 * @Date 2021/7/29
 */
@EnableEurekaClient
@ComponentScan(basePackages = "com.tca")
@SpringBootApplication
@EnableFeignClients(basePackages = "com.tca")
public class PersonApplication {

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

api

personFeign

package com.tca.cloud.standalone.person.api.service;

import com.tca.cloud.standalone.person.api.req.PersonReq;
import com.tca.cloud.standalone.person.api.resp.PersonResp;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;

/**
 * @author zhouan
 * @Date 2021/7/29
 */
@FeignClient(value = "person-provider")
public interface PersonFeign {

    /**
     * 根据id获取人员
     * @param personReq
     * @return
     */
    @PostMapping("/person/get")
    PersonResp get(PersonReq personReq);
}

equipmentFeign

package com.tca.cloud.standalone.equipment.api.service;

import com.tca.cloud.standalone.equipment.api.req.EquipmentReq;
import com.tca.cloud.standalone.equipment.api.resp.EquipmentResp;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.Mapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;

/**
 * @author zhouan
 * @Date 2021/7/29
 */
@FeignClient(value = "equipment-provider")
public interface EquipmentFeign {

    /**
     * 根据id获取人员
     * @param equipmentReq
     * @return
     */
    @PostMapping("/equipment/get")
    EquipmentResp get(EquipmentReq equipmentReq);
}

controller

package com.tca.cloud.standalone.person.biz.controller;

import com.tca.cloud.standalone.equipment.api.req.EquipmentReq;
import com.tca.cloud.standalone.equipment.api.resp.EquipmentResp;
import com.tca.cloud.standalone.equipment.api.service.EquipmentFeign;
import com.tca.cloud.standalone.person.api.req.PersonReq;
import com.tca.cloud.standalone.person.api.resp.PersonResp;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;

/**
 * @author zhouan
 * @Date 2021/7/29
 */
@RestController
@RequestMapping(value = "/person")
@Slf4j
public class PersonController {

    @Autowired
    private EquipmentFeign equipmentFeign;

    /**
     * 获取人员
     * @param personReq
     * @return
     */
    @GetMapping("/get")
    public PersonResp get(@Validated @RequestBody PersonReq personReq) {
        PersonResp personResp = new PersonResp();
        personResp.setId(personReq.getId());
        personResp.setAge(30);
        personResp.setName("Messi");

        EquipmentReq equipmentReq = new EquipmentReq();
        equipmentReq.setId(personReq.getId());
        EquipmentResp equipmentResp = equipmentFeign.get(equipmentReq);
        log.info("equipmentResp = {}", equipmentResp);
        return personResp;
    }
}

2.常用注解解析

openfeign中最常用的注解是:启动类上的 @EnableFeignClients 和 接口声明上的@FeignClient

2.1 EnableFeignClients

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(FeignClientsRegistrar.class) // 引入FeignClientsRegistrar, @Import作用在springspringboot源码中已经描述过, 这里不作赘述
public @interface EnableFeignClients {

    // 等用于 basePackages, 即扫描的包
	String[] value() default {};

	String[] basePackages() default {};

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

    // FeignClients的全局配置类, 可以定义一些bean提供给FeignClient使用, 比如: Decoder, Encoder, Contract
	Class<?>[] defaultConfiguration() default {};

    // 通过该属性直接指定要加载哪些@FeignClient接口, 使用这个属性之后, basePackages就不再起作用了
	Class<?>[] clients() default {};

}

总结

@EnableFeignClients 注解用于向 spring 容器中导入 FeignClientsRegistrar, 他有两个核心属性: 1.需要扫描的包或者指定的Class 2.提供FeignClients的全局配置类

2.2 FeignClient

/*
 * Copyright 2013-2019 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

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;

/**
 * Annotation for interfaces declaring that a REST client with that interface should be
 * created (e.g. for autowiring into another component). If ribbon is available it will be
 * used to load balance the backend requests, and the load balancer can be configured
 * using a <code>@RibbonClient</code> with the same name (i.e. value) as the feign client.
 *
 * @author Spencer Gibb
 * @author Venil Noronha
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface FeignClient {

	// 指定服务名称, 一般与 ${spring.application.name} 相同
	@AliasFor("name")
	String value() default "";

	// 跟value相同
	@Deprecated
	String serviceId() default "";

	// 如果存在,它将被用作bean的名称,代替name属性,但不会用作服务id
	String contextId() default "";

	// 跟value相同
	@AliasFor("value")
	String name() default "";

	// 根据Class Type注入,如果存在多个会报错,就需要根据名称注入@Qualifier可以指定名称
	String qualifier() default "";

	// 配置这个相当于直连方式,例如url="localhost:8080", 这样就不会走负载均衡了
	String url() default "";

	// 如果发生FeignExceptions是否需要返回404
	boolean decode404() default false;

	// 这个配置相当于是局部配置,某一个feign client专门的配置
	Class<?>[] configuration() default {};

	// 指定Feign client接口的服务降级类。服务降级类必须实现由该注解注释的接口, 并且必须是一个有效的spring bean
	Class<?> fallback() default void.class;

	// 为指定的Feign client接口定义一个服务降级工厂。服务降级工厂必须生成服务降级类的实例, 这些实例实现了由{@link FeignClient}注释的接口。服务降级工厂必须是一个有效的spring bean
	Class<?> fallbackFactory() default void.class;

	// 所有方法级映射使用的路径前缀。可以@RibbonClient一起使用。
	String path() default "";

    // 是否将feign代理标记为主bean。默认值为true。和@Primary注解类似,同一个接口多个实现类中根据类型注入时候会首选被@Primary标记的实现类
    // 每个feign client接口都会为其生成一个代理类,这里是为这个代理类标记为Primary
	boolean primary() default true;

}

总结

@FeignClient提供了一些属性, 用于FeignClientFactoryBean创建FeignClient代理对象

3.FeignClientsRegistrar详解

3.1 接口

// 在类声明中可以看到 它 实现了 ImportBeanDefinitionRegistrar接口,这个在spring源码中有介绍,在ConfigurationClassPostProcessor中会委托ConfigurationClassParser解析 @Import 注解获取到 FeignClientsRegistrar, 判断其实现了 ImportBeanDefinitionRegistrar 接口, 此时会使用反射创建FeignClientsRegistrar实例, 回调其Aware接口, 之后, 会统一回调 FeignClientsRegistrar#registerBeanDefinitions方法
class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware

3.2 FeignClientsRegistrar#registerBeanDefinitions

@Override
public void registerBeanDefinitions(AnnotationMetadata metadata,
                                    BeanDefinitionRegistry registry) {
    // 注册默认配置 见@3.2.1
    registerDefaultConfiguration(metadata, registry);
    // 注册FeignClient 见@3.2.2
    registerFeignClients(metadata, registry);
}

3.2.1 FeignClientsRegistrar#registerDefaultConfiguration

private void registerDefaultConfiguration(AnnotationMetadata metadata,
                                          BeanDefinitionRegistry registry) {
    // 1.获取 EnableFeignClients 的属性
    // 该方法第二个参数true,表示将注解中class类型的属性转换为字符串类名暴露到返回到map中
    Map<String, Object> defaultAttrs = metadata
        .getAnnotationAttributes(EnableFeignClients.class.getName(), true);
    // 2.处理defaultConfiguration属性
    if (defaultAttrs != null && defaultAttrs.containsKey("defaultConfiguration")) {
        String name;
        if (metadata.hasEnclosingClass()) {
            name = "default." + metadata.getEnclosingClassName();
        }
        else {
            // 3.name == default+类名, 这里 name == default.PersonApplication
            name = "default." + metadata.getClassName();
        }
        // 4.向spring容器中注册FeignClient相关配置类, 见下
        registerClientConfiguration(registry, name,
                                    defaultAttrs.get("defaultConfiguration"));
    }
}

FeignClientsRegistrar#registerClientConfiguration

private void registerClientConfiguration(BeanDefinitionRegistry registry, Object name,
                                         Object configuration) {
    // 1.创建 BeanDefinitionBuilder, beanClass == FeignClientSpecification, 用于创建 FeignClientSpecification 对应的 BeanDefinition, 
    BeanDefinitionBuilder builder = BeanDefinitionBuilder
        .genericBeanDefinition(FeignClientSpecification.class);
    // 2.为FeignClientSpecification添加两个构造器参数:
    // name == default.PersonApplication
    // configuration == {}, 因为EnableFeignClients中的 defaultConfiguration 属性为 {}
    builder.addConstructorArgValue(name);
    builder.addConstructorArgValue(configuration);
    // 3.向spring容器中注册bd
    // name == default.PersonApplication.FeignClientSpecification
    // value == FeignClientSpecification对应的bd
    registry.registerBeanDefinition(
        name + "." + FeignClientSpecification.class.getSimpleName(),
        builder.getBeanDefinition());
}
总结
FeignClientsRegistrar#registerDefaultConfiguration的作用:
1.解析 EnableFeignClients注解, 获取 defaultConfiguration 属性
2.创建 主配置类 PersonApplication 对应的 FeignClient全局配置类FeignClientSpecification的bd, 并注册到spring容器中
3.FeignClientSpecification 有两个重要的属性, name 以及 configuration, 这里的configuration == defaultConfiguration, 默认为 {}

3.2.2 FeignClientsRegistrar#registerFeignClients

public void registerFeignClients(AnnotationMetadata metadata,
                                 BeanDefinitionRegistry registry) {
    // 1.获取包扫描器, 这里跟 mybatis-spring 集成不同, mybatis-spring 是定义一个扫描器继承 ClasspathBeanDefinitionScanner, 重写isCandidateComponent方法
    // 在这里, 它重新定义了一个scanner --> ClassPathScanningCandidateComponentProvider, 原理差不多
    ClassPathScanningCandidateComponentProvider scanner = getScanner();
    // 设置资源加载器
    scanner.setResourceLoader(this.resourceLoader);

    Set<String> basePackages;
	// 2.获取@FeignClient注解属性
    Map<String, Object> attrs = metadata
        .getAnnotationAttributes(EnableFeignClients.class.getName());
    AnnotationTypeFilter annotationTypeFilter = new AnnotationTypeFilter(
        FeignClient.class);
    // 获取 clients 属性, 默认为{}, 这里可以看出 如果配置了 clients 属性就不会走basePackage了
    final Class<?>[] clients = attrs == null ? null
        : (Class<?>[]) attrs.get("clients");
    if (clients == null || clients.length == 0) {
        // 3.添加扫描过滤器, 即只有@FeignClient注解的才会被扫描
        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)));
    }
	// 4.扫描包中的有@FeignClient注解的接口
    for (String basePackage : basePackages) {
        // 5.扫描出对应的接口对应并创建bd
        // 这里可以获取 equipmentFeign 和 personFeign 对应的bd
        Set<BeanDefinition> candidateComponents = scanner
            .findCandidateComponents(basePackage);
        for (BeanDefinition candidateComponent : candidateComponents) {
            // 6.处理所有的bd
            if (candidateComponent instanceof AnnotatedBeanDefinition) {
                // verify annotated class is an interface
                // 7.获取bd
                AnnotatedBeanDefinition beanDefinition = (AnnotatedBeanDefinition) candidateComponent;
                AnnotationMetadata annotationMetadata = beanDefinition.getMetadata();
                Assert.isTrue(annotationMetadata.isInterface(),
                              "@FeignClient can only be specified on an interface");
			   // 8.获取bd上@FeignClient注解的属性
                Map<String, Object> attributes = annotationMetadata
                    .getAnnotationAttributes(
                    FeignClient.class.getCanonicalName());
			   // 9.获取name属性
                String name = getClientName(attributes);
                // 10.这里跟@3.2.1相同, 都是注册FeignClientSpecification的bd
                // 但是这里注册的是 每一个 FeignClient 的私有配置 FeignClientSpecification的bd, 并注册到spring容器中
                // name == person-provider.FeignClientSpecification
                // value == FeignClientSpecification对应bd
                registerClientConfiguration(registry, name,
                                            attributes.get("configuration"));
			   // 11.【重点】注册FeignClient, 见下方
                registerFeignClient(registry, annotationMetadata, attributes);
            }
        }
    }
}

FeignClientsRegistrar#registerFeignClient

// 这里就是注册FeignClient对应bd, 这里以 personFeign 为例
private void registerFeignClient(BeanDefinitionRegistry registry,
                                 AnnotationMetadata annotationMetadata, Map<String, Object> attributes) {
    // 1.className == com.tca.cloud.standalone.person.api.service.PersonFeign
    String className = annotationMetadata.getClassName();
    // 2.创建BeanDefinitionBuilder, 可以看到, 这里的beanClass == FeignClientFactoryBean, FeignClientFactoryBean是FactoryBean, 这里的原理和mybatis-spring整合一致, 使用FactoryBean来创建每一个代理对象!!!
    BeanDefinitionBuilder definition = BeanDefinitionBuilder
        .genericBeanDefinition(FeignClientFactoryBean.class);
    validate(attributes);
    // 3.给bd中的beanClass添加相关属性: url, path, name, contextId 等
    // 查看 FeignClientFactoryBean 代码可以看到, 其确实有这些对应的属性 
    definition.addPropertyValue("url", getUrl(attributes));
    definition.addPropertyValue("path", getPath(attributes));
    String name = getName(attributes);
    definition.addPropertyValue("name", name);
    String contextId = getContextId(attributes);
    definition.addPropertyValue("contextId", contextId);
    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 = contextId + "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 });
    // 4.注册到spring容器中
    // name == com.tca.cloud.standalone.person.api.service.PersonFeign
    // value == FeignClientFactoryBean对应的bd
    BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
}

总结
FeignClientsRegistrar#registerFeignClients的作用:
1.扫描@FeignClient注解的接口
2.对于每一个 FeignClient, 创建两个bd, 并分别注册到spring容器中:
  a. FeignClient对应的FeignClientSpecification, name == ${value}.FeignClientSpecification, value == FeignClientSpecification, 这里是当前FeignClient的局部配置类
  b. FeignClient对应的FeignClientFactoryBean, name == 接口全限定名, value == FeignClientFactoryBean

4.自动配置

4.1 原理

在springboot源码解析中, 我们知道自动配置的原理, 其核心在于spring会读取所有jar包中的META-INF/spring.factories配置文件, 获取配置文件中的所有配置类, 再根据配置类中的@ConditionOnxxx等方式判断是否需要将相关配置类注册到spring容器中!
可以看到spring-cloud-starter-openfeign中依赖了spring-cloud-openfeign-core,spring-cloud-openfeign-core的jar包中包含了spring.factories配置文件!其中有一个重要的配置类:FeignAutoConfiguration
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.cloud.openfeign.ribbon.FeignRibbonClientAutoConfiguration,\
org.springframework.cloud.openfeign.FeignAutoConfiguration,\
org.springframework.cloud.openfeign.encoding.FeignAcceptGzipEncodingAutoConfiguration,\
org.springframework.cloud.openfeign.encoding.FeignContentGzipEncodingAutoConfiguration

4.2 FeignAutoConfiguration

/**
 * @author Spencer Gibb
 * @author Julien Roy
 */
@Configuration
@ConditionalOnClass(Feign.class)
@EnableConfigurationProperties({ FeignClientProperties.class,
                                FeignHttpClientProperties.class })
public class FeignAutoConfiguration {
	
    // 这里会获取容器中所有的FeignClientSpecification, 包括当前应用的全局FeignClientSpecification 以及每个 FeignClient 对应的局部的FeignClientSpecification 
    @Autowired(required = false)
    private List<FeignClientSpecification> configurations = new ArrayList<>();

	// 向容器中注册FeignContext【核心组件】
    @Bean
    public FeignContext feignContext() {
        FeignContext context = new FeignContext();
        context.setConfigurations(this.configurations);
        return context;
    }

   // ......
}

4.3 FeignContext

// FeignContext 是feign的全局上下文, 它组合了所有 FeignClientSpecification
public class FeignContext extends NamedContextFactory<FeignClientSpecification> {

    public FeignContext() {
        super(FeignClientsConfiguration.class, "feign", "feign.client.name");
    }

}
public abstract class NamedContextFactory<C extends NamedContextFactory.Specification>
		implements DisposableBean, ApplicationContextAware {

	private final String propertySourceName;

	private final String propertyName;
    
	// 【重要】
    // key == 微服务的名称, 即@FeignClient的value属性值
    // value == spring容器, 它是服务容器的子容器, 即创建FeignClient代理对象时的组件都放在子容器中
	private Map<String, AnnotationConfigApplicationContext> contexts = new ConcurrentHashMap<>();
    
    // configurations即为之前封装的所有FeignClientSpecification
	private Map<String, C> configurations = new ConcurrentHashMap<>();
    
    // 父容器
	private ApplicationContext parent;

	private Class<?> defaultConfigType;
    
    //......
}

总结

1.根据上面学习知道, 使用 @EnableFeignClients 和 @FeignClient 注解, springboot容器启动时会解析这两个核心注解, 根据 @EnableFeignClients 的属性创建全局 FeignClientSpecification (这个是全局默认配置类), 根据每一个 @FeignClient 的属性创建每一个 @FeignClient 的 FeignClientSpecification 配置类, 全部都注册到spring容器中, 对于全局配置, key == default.***, 对于局部配置, key == FeignClient的value属性, 一般为微服务的name!!!
2.根据自动配置, 在容器中创建了 FeignContext, 它继承了 NamedContextFactory, NamedContextFactory很重要, 它有两个重要的属性:
private Map<String, C> configurations = new ConcurrentHashMap<>(); // 这个用来存储上述说的默认配置和全局配置类
private Map<String, AnnotationConfigApplicationContext> contexts = new ConcurrentHashMap<>(); // 这个会为每个FeignClient创建一个对应的spring子容器, 实现FeignClient的隔离!!!
3.通过阅读@ribbon源码, ribbon中和openfeign的设计如出一辙, ribbon中有@RibbonClients 和 @RibbonClient, 对应 EnableFeignClients 和 FeignClient, 也会创建全局配置类 RibbonClientSpecification, ribbon中有 SpringClientFactory 对应 openfeign中的 FeignContext, 同样继承了 NamedContextFactory, 也存储了每个RibbonClient的配置以及为每个 RibbonClient 创建 spring子容器, 实现客户端之间的配置隔离!!!

5.FeignClient创建

前期我们向spring容器中注册了feignClient相关bd, 对应的bd为FactoryBean的实现类 --> FeignClientFactoryBean, 因此代理对象是通过 FeignClientFactoryBean#getObject方法创建的

5.1 FeignClientFactoryBean#getObject

@Override
public Object getObject() throws Exception {
    // 见下
    return getTarget();
}
<T> T getTarget() {
    // 1.由@4自动配置可以看到, 通过自动配置已经向spring容器中注册了FeignContext
    FeignContext context = this.applicationContext.getBean(FeignContext.class);
    // 2.通过 FeignContext 创建 Feign.Builder, 见@5.2
    Feign.Builder builder = feign(context);
    // 3.判断@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();
        // 3.负载均衡策略, 见@5.3
        return (T) loadBalance(builder, context,
                               new HardCodedTarget<>(this.type, this.name, this.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 load 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 (T) targeter.target(this, builder, context,
                               new HardCodedTarget<>(this.type, this.name, url));
}

5.2 FeignClientFactoryBean#feign

protected Feign.Builder feign(FeignContext context) {
    // 1.从FeignContext中获取FeignLoggerFactory
    // 由 @4.3 可以看到, FeignContext的父类中有一个重要的属性: 
    // private Map<String, AnnotationConfigApplicationContext> contexts = new ConcurrentHashMap<>();
    // key == 微服务的名称, 即@FeignClient的value属性值
    // value == spring容器, 它是服务容器的子容器, 即创建FeignClient代理对象时的组件都放在子容器中
    // 实例化FeignContext时, 当前属性为空, 当我们调用 get(context, FeignLoggerFactory.class) 时, 会根据当前FeignClient的value, 即微服务的名称(person-provider/equipment-provider)从 contexts 属性中获取对应的spring子容器, 第一次获取时, 会创建子容器, 并存储到context属性中!!!
    // 创建子容器时, 会从局部配置类和全局配置类 FeignClientSpecification 中读取相关配置bean, 并注册到子容器中!!!
    // 【综上】这里会创建 子容器, 并以key-value格式存储到 FeignContext 的 contexts属性中, 
    FeignLoggerFactory loggerFactory = get(context, FeignLoggerFactory.class);
    Logger logger = loggerFactory.create(this.type);

    // 2.从子容器中获取 Feign.Builder 对象, Encoder对象, Decoder对象, Contract对象, 封装到 Feign.Builder 中
    Feign.Builder builder = get(context, Feign.Builder.class)
        .logger(logger)
        // encoder --> SpringEncoder
        .encoder(get(context, Encoder.class))
        // decoder --> OptionalDecoder
        .decoder(get(context, Decoder.class))
        // contract --> SpringMVCContract
        .contract(get(context, Contract.class));
	// 3.根据 FeignClientProperties 继续配置 Feign.Builder
    configureFeign(context, builder);

    return builder;
}

总结

这里主要用于构建Feign.Builder对象, 其中一个核心的步骤是创建每个FeignClient对应的spring子容器, 将FeignClient对应的局部配置类和全局配置类中的相关bean注册到子容器中, 并将其存储在 FeignContext 中!!!并用于构建Feign.Builder对象!!!

5.3 FeignClientFactoryBean#loadBalance

protected <T> T loadBalance(Feign.Builder builder, FeignContext context,
                            HardCodedTarget<T> target) {
    // 1.从 FeignContext 中获取 Client 对象, 这里的client == LoadBalanceFeignClient
  	// 这里的Client是哪个注入的, 又是如何集成ribbon的, 见@7.1 
    Client client = getOptional(context, Client.class);
    if (client != null) {
        // 2.继续配置 Feign.Builder 
        builder.client(client);
        // 3.从 FeignContext 中获取 Targeter 对象, 这里的targeter == HystrixTargeter
        Targeter targeter = get(context, Targeter.class);
        // 4.创建代理对象 见@5.4
        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?");
}

总结

这里从 FeignContext 中的子容器中继续获取 Client, Targeter对象, 用于构建Feign.Builder对象

5.4 HystrixTargeter#target

// 这里会创建代理对象
@Override
public <T> T target(FeignClientFactoryBean factory, Feign.Builder feign,
                    FeignContext context, Target.HardCodedTarget<T> target) {
    // 默认进入 feign instanceof feign.hystrix.HystrixFeign.Builder == false
    // 没有开启熔断功能话就不是熔断的Builder
    if (!(feign instanceof feign.hystrix.HystrixFeign.Builder)) {
        // 见下
        return feign.target(target);
    }
    //如果开启了熔断,就会处理一些服务降级的配置
    feign.hystrix.HystrixFeign.Builder builder = (feign.hystrix.HystrixFeign.Builder) feign;
    SetterFactory setterFactory = getOptional(factory.getName(), context,
                                              SetterFactory.class);
    if (setterFactory != null) {
        builder.setterFactory(setterFactory);
    }
    Class<?> fallback = factory.getFallback();
    if (fallback != void.class) {
        return targetWithFallback(factory.getName(), context, target, builder,
                                  fallback);
    }
    Class<?> fallbackFactory = factory.getFallbackFactory();
    if (fallbackFactory != void.class) {
        return targetWithFallbackFactory(factory.getName(), context, target, builder,
                                         fallbackFactory);
    }

    return feign.target(target);
}

Feign#target

// target == Target.HardCodedTarget
public <T> T target(Target<T> target) {
    // 1.build --> 创建 Feign 对象, 这里的 Feign == ReflectiveFeign
    // 2.newInstance --> 创建代理对象
    return build().newInstance(target);
}

Feign#build

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

ReflectiveFeign#newInstance

@Override
public <T> T newInstance(Target<T> target) {
    // 1.生成 name -- methodHandler , 以EquipmentFeign为例
    // name == EquipmentFeign#get(EquipmentReq)
    // value == SynchronousMethodHandler
    Map<String, MethodHandler> nameToHandler = targetToHandlersByName.apply(target);
    // 2.声明 method - MethodHandler
    Map<Method, MethodHandler> methodToHandler = new LinkedHashMap<Method, MethodHandler>();
    List<DefaultMethodHandler> defaultMethodHandlers = new LinkedList<DefaultMethodHandler>();
	// 3.获取 EquipmentFeign 的所有方法 
    for (Method method : target.type().getMethods()) {
        if (method.getDeclaringClass() == Object.class) {
            continue;
        } else if (Util.isDefault(method)) {
            DefaultMethodHandler handler = new DefaultMethodHandler(method);
            defaultMethodHandlers.add(handler);
            methodToHandler.put(method, handler);
        } else {
            // 添加 method - MethodHandler 到 methodToHandler中
            methodToHandler.put(method, nameToHandler.get(Feign.configKey(target.type(), method)));
        }
    }
    // 4.创建 InvocationHandler, 这里为 ReflectiveFeign.FeignInvocationHandler
    InvocationHandler handler = factory.create(target, methodToHandler);
    // 5.创建代理对象
    T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(),
                                         new Class<?>[] {target.type()}, handler);

    for (DefaultMethodHandler defaultMethodHandler : defaultMethodHandlers) {
        defaultMethodHandler.bindTo(proxy);
    }
    return proxy;
}

6.远程调用&Ribbon负载均衡

从上述可以看到, 通过jdk动态代理创建 clientfeign 代理对象, jdk的动态代理有一个核心类 -- InvocationHandler, 这里默认实现是: ReflectiveFeign.FeignInvocationHandler, 因此方法调用时, 调用其invoke方法

6.1 ReflectiveFeign.FeignInvocationHandler#invoke

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    if ("equals".equals(method.getName())) {
        try {
            Object otherHandler =
                args.length > 0 && args[0] != null ? Proxy.getInvocationHandler(args[0]) : null;
            return equals(otherHandler);
        } catch (IllegalArgumentException e) {
            return false;
        }
    } else if ("hashCode".equals(method.getName())) {
        return hashCode();
    } else if ("toString".equals(method.getName())) {
        return toString();
    }
    // dispatch存储的是 method -- MethodHandler 关联关系, 调用某个method 就是会调用对应的 MethodHandler#invoke方法
    //如果执行的方法是接口中的抽象方法,方法处理器实现类是SynchronousMethodHandler【重点】, 见 @6.2
    //如果执行的方法是接口中的默认方法,方法处理器实现类是DefaultMethodHandler
    return dispatch.get(method).invoke(args);
}

6.2 SynchronousMethodHandler#invoke

@Override
public Object invoke(Object[] argv) throws Throwable {
    // 1.构建请求模板, 包括: 请求头, 请求体, 请求方法, 请求url等
    RequestTemplate template = buildTemplateFromArgs.create(argv);
    Retryer retryer = this.retryer.clone();
    while (true) {
        try {
            // 2.执行并解码, 见@6.3
            return executeAndDecode(template);
        } catch (RetryableException e) {
            try {
                retryer.continueOrPropagate(e);
            } catch (RetryableException th) {
                Throwable cause = th.getCause();
                if (propagationPolicy == UNWRAP && cause != null) {
                    throw cause;
                } else {
                    throw th;
                }
            }
            if (logLevel != Logger.Level.NONE) {
                logger.logRetry(metadata.configKey(), logLevel);
            }
            continue;
        }
    }
}

6.3 SynchronousMethodHandler#executeAndDecode

Object executeAndDecode(RequestTemplate template) throws Throwable {
    // 1.target中包含请求url的前段 -- http://equipment-provider/ , RequestTemplate中包含 /equipment/get , 拼在一起: http://equipment-provider/equipment/get
    Request request = targetRequest(template);

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

    Response response;
    long start = System.nanoTime();
    try {
        // 这个Client就是发起远程调用的客户端
        // 如果导入了Ribbon负载均衡依赖,这个client就是 LoadBalancerFeignClient, openfeign的包中默认集成了ribbon!
        // client默认为负载均衡client --> LoadBalancerFeignClient
        // 见@6.4
        response = client.execute(request, options);
    } catch (IOException e) {
        if (logLevel != Logger.Level.NONE) {
            logger.logIOException(metadata.configKey(), logLevel, e, elapsedTime(start));
        }
        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);
        }
        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();
        }
        if (response.status() >= 200 && response.status() < 300) {
            if (void.class == metadata.returnType()) {
                return null;
            } else {
                Object result = decode(response);
                shouldClose = closeAfterDecode;
                return result;
            }
        } else if (decode404 && response.status() == 404 && void.class != metadata.returnType()) {
            Object result = decode(response);
            shouldClose = closeAfterDecode;
            return result;
        } 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());
        }
    }
}

6.4 LoadBalancerFeignClient#execute

@Override
public Response execute(Request request, Request.Options options) throws IOException {
    try {
        URI asUri = URI.create(request.url());
        String clientName = asUri.getHost();
        URI uriWithoutHost = cleanUrl(request.url(), clientName);
        // 1.Ribbon整合就在这!!!
		// this.delegate --> ApacheHttpClient
		// 包装了一下先进行负载均衡,再进行远程调用,最终远程调用还是依靠this.delegate
        FeignLoadBalancer.RibbonRequest ribbonRequest = new FeignLoadBalancer.RibbonRequest(
            this.delegate, request, uriWithoutHost);
        IClientConfig requestConfig = getClientConfig(options, clientName);
        // 2.lbClient:获取一个负载均衡器, 见@6.5
        // 这里的clientName 即为微服务名称, 如 equipment-provider
        // executeWithLoadBalancer: 负载均衡执行
        return lbClient(clientName)
            .executeWithLoadBalancer(ribbonRequest, requestConfig).toResponse();
    }
    catch (ClientException e) {
        IOException io = findIOException(e);
        if (io != null) {
            throw io;
        }
        throw new RuntimeException(e);
    }
}

6.5 LoadBalancerFeignClient#lbClient

private FeignLoadBalancer lbClient(String clientName) {
    // lbClientFactory --> CachingSpringLoadBalancerFactory
    return this.lbClientFactory.create(clientName);
}

CachingSpringLoadBalancerFactory#create

public FeignLoadBalancer create(String clientName) {
    // 1.先从缓存中获取
    FeignLoadBalancer client = this.cache.get(clientName);
    if (client != null) {
        return client;
    }
    // 2.获取客户端配置
    IClientConfig config = this.factory.getClientConfig(clientName);
    // 3.获取负载均衡器【重点】
    // this.factory == SpringClientFactory
    ILoadBalancer lb = this.factory.getLoadBalancer(clientName);
    ServerIntrospector serverIntrospector = this.factory.getInstance(clientName,
    // 包装成具有重试功能的负载均衡器
	// 同时注意ILoadBalancer是Ribbon的接口
	// RetryableFeignLoadBalancer和FeignLoadBalancer都是OpenFeign的
	// 也可以认为是适配了一下                                                              
    client = this.loadBalancedRetryFactory != null
        ? new RetryableFeignLoadBalancer(lb, config, serverIntrospector,
                                         this.loadBalancedRetryFactory)
        : new FeignLoadBalancer(lb, config, serverIntrospector);
    this.cache.put(clientName, client);
    return client;
}

SpringClientFactory#getLoadBalancer

public ILoadBalancer getLoadBalancer(String name) {
    // 根据微服务名, 从子容器中获取ILoadBalancer实例, 取不到就从父容器中获取
    // 这里获取的是 ZoneAwareLoadBalancer
    return getInstance(name, ILoadBalancer.class);
}
@Override
public <C> C getInstance(String name, Class<C> type) {
    C instance = super.getInstance(name, type);
    if (instance != null) {
        return instance;
    }
    IClientConfig config = getInstance(name, IClientConfig.class);
    return instantiateWithConfig(getContext(name), type, config);
}

6.6 ZoneAwareLoadBalancer#executeWithLoadBalancer

这里是负载均衡的执行流程!!!调用的是ZoneAwareLoadBalancer的父类AbstractLoadBalancerAwareClient#executeWithLoadBalancer方法

/**
 * 当调用者希望将请求分派到负载平衡器选择的服务器, 而不是在请求的URI中指定服务器时, 应该使用此方法。
 * 它通过调用reconstructURIWithServer来计算最终的URI
 */
public T executeWithLoadBalancer(final S request, final IClientConfig requestConfig) throws ClientException {
    LoadBalancerCommand<T> command = buildLoadBalancerCommand(request, requestConfig);

    try {
        // 1.command提交了一个服务器操作, 当负载均衡选到了要访问的服务器后, 就会执行这个ServerOperation, 所以负载均衡触发点主要在sumbit中
        return command.submit(
            new ServerOperation<T>() {
                @Override
                public Observable<T> call(Server server) {
                    // 通过选择的Server计算最终的URI
                    URI finalUri = reconstructURIWithServer(server, request.getUri());
                    S requestForServer = (S) request.replaceUri(finalUri);
                    try {
                        // 调用execute发起请求
                        return Observable.just(AbstractLoadBalancerAwareClient.this.execute(requestForServer, requestConfig));
                    } 
                    catch (Exception e) {
                        return Observable.error(e);
                    }
                }
            })
            .toBlocking()
            .single();
    } catch (Exception e) {
        Throwable t = e.getCause();
        if (t instanceof ClientException) {
            throw (ClientException) t;
        } else {
            throw new ClientException(e);
        }
    }

}

LoadBalancerCommand#submit

注意:这段代码有点长,且涉及webflux响应式编程,这里我们只看 selectServer 方法,这个方法涉及服务器的选择

public Observable<T> submit(final ServerOperation<T> operation) {
    // ......
    // 通过 selectServer 选择服务器
    Observable<T> o = 
        (server == null ? selectServer() : Observable.just(server))
        .concatMap(new Func1<Server, Observable<T>>() {
            
                    });

                if (maxRetrysSame > 0) 
                    o = o.retry(retryPolicy(maxRetrysSame, true));
                return o;
            }
        });
	// ......
    
}

6.7 LoadBalancerCommand#selectServer

private Observable<Server> selectServer() {
    return Observable.create(new OnSubscribe<Server>() {
        @Override
        public void call(Subscriber<? super Server> next) {
            try {
                // 选择服务器
                Server server = loadBalancerContext.getServerFromLoadBalancer(loadBalancerURI, loadBalancerKey);   
                next.onNext(server);
                next.onCompleted();
            } catch (Exception e) {
                next.onError(e);
            }
        }
    });
}

LoadBalancerContext#getServerFromLoadBalancer

public Server getServerFromLoadBalancer(@Nullable URI original, @Nullable Object loadBalancerKey) throws ClientException {
    // ......
    
    ILoadBalancer lb = getLoadBalancer();
    if (host == null) {
        // Partial URI or no URI Case
        // well we have to just get the right instances from lb - or we fall back
        if (lb != null){
            // 选择服务器!
            Server svc = lb.chooseServer(loadBalancerKey);
            if (svc == null){
                throw new ClientException(ClientException.ErrorType.GENERAL,
                                          "Load balancer does not have available server for client: "
                                          + clientName);
            }
            host = svc.getHost();
            if (host == null){
                throw new ClientException(ClientException.ErrorType.GENERAL,
                                          "Invalid Server for :" + svc);
            }
            logger.debug("{} using LB returned Server: {} for request {}", new Object[]{clientName, svc, original});
            return svc;
        } else {
        }
    }
    
    // ...... 省略很多

    return new Server(host, port);
}

BaseLoadBalancer#chooseServer

public Server chooseServer(Object key) {
    if (counter == null) {
        counter = createCounter();
    }
    counter.increment();
    if (rule == null) {
        return null;
    } else {
        try {
            // 借助 负载均衡策略 选择
            return rule.choose(key);
            logger.warn("LoadBalancer [{}]:  Error choosing server for key {}", name, key, e);
            return null;
        }
    }
}

7.Q&A

7.1 关于Feign的构建

这块可以参考common-learning-openfeign文档:

参考

Feign的构建涉及到一些核心组件: contract, encoder, decoder, interceptor、logger、httpclient、retryer。
使用springcloud-openfeign时, 我们可以将组件通过@EnableFeignClients或@FeignClient的configuration配置, 将自定义组件注册到spring容器中, 在构建Feign时, 可以使用spring容器中的相关组件!

7.2 openfeign如何集成ribbon

见@4自动配置,在spring-cloud-openfeign-core的spring.factories配置文件中有如下自动装配的bean

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.cloud.openfeign.ribbon.FeignRibbonClientAutoConfiguration,\
org.springframework.cloud.openfeign.FeignAutoConfiguration,\
org.springframework.cloud.openfeign.encoding.FeignAcceptGzipEncodingAutoConfiguration,\
org.springframework.cloud.openfeign.encoding.FeignContentGzipEncodingAutoConfiguration

openfeign集成ribbon的核心bean是FeignRibbonClientAutoConfiguration

FeignRibbonClientAutoConfiguration

@ConditionalOnClass({ ILoadBalancer.class, Feign.class })
@ConditionalOnProperty(value = "spring.cloud.loadbalancer.ribbon.enabled",
		matchIfMissing = true)
@Configuration(proxyBeanMethods = false)
@AutoConfigureBefore(FeignAutoConfiguration.class)
@EnableConfigurationProperties({ FeignHttpClientProperties.class })
@Import({ HttpClientFeignLoadBalancedConfiguration.class,
		OkHttpFeignLoadBalancedConfiguration.class,
		DefaultFeignLoadBalancedConfiguration.class })
public class FeignRibbonClientAutoConfiguration {
  
  // ......
}

这里看到通过@Import注解导入了几个核心的LoadBalancedConfiguration:HttpClientFeignLoadBalancedConfiguration、OkHttpFeignLoadBalancedConfiguration、DefaultFeignLoadBalancedConfiguration。 我们知道Feign的构建过程中会使用到很多组件, 其中最核心的组件之一就是 - client, client组件是用于真正发起http请求的组件, 默认采用jdk自带的HttpClient, 但是我们也可以通过导入feign-okhttp或者feign-httpclient 用来使用okhttp或apache的httpclient作为client。这里我们可以看下 OkHttpFeignLoadBalancedConfiguration

OkHttpFeignLoadBalancedConfiguration

@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(OkHttpClient.class)
@ConditionalOnProperty("feign.okhttp.enabled")
@Import(OkHttpFeignConfiguration.class)
class OkHttpFeignLoadBalancedConfiguration {

    @Bean
    @ConditionalOnMissingBean(Client.class)
    public Client feignClient(CachingSpringLoadBalancerFactory cachingFactory,
            SpringClientFactory clientFactory, okhttp3.OkHttpClient okHttpClient) {
        OkHttpClient delegate = new OkHttpClient(okHttpClient);
        return new LoadBalancerFeignClient(delegate, cachingFactory, clientFactory);
    }

}

通过这个配置bean可以看到: 1.首先通过ConditionalOnClass(OkHttpClient.class), 当前bean需要基于OkHttpClient, 如果没有导入相关依赖, 则当前配置bean直接失效 2.通过@Import注解导入了配置bean -> OkHttpFeignConfiguration, 在这个bean中向容器中注入了 okhttp3.OkHttpClient 作为client, 以及连接池对象 3.最后, 在当前bean中注入了核心组件 Client -> feignClient, 可以看到当前feignClient是在okhttp3.OkHttpClient的基础上做了一层封装, 封装成LoadBalancerFeignClient, 在LoadBalancerFeignClient中集成了 ribbon的相关组件 4.这里需要说明的是, 不管我们底层使用的client是okhttp还是apache httpClient或者jdk httpClient, 最终封装成我们feign的Client的组件都会是LoadBalancerFeignClient, 而LoadBalancerFeignClient是集成了 ribbon的相关组件的, 在具体调用的过程中并不是直接使用客户端进行直接调用, 而是通过ribbon相关组件, 从serverList中根据负载均衡策略后再进行http调用

7.3 openfeign集成http客户端(apache-httpclient、okhttp)

openfeign如何集成http客户端

通过@7.2的描述可以看到, 自动配置类FeignRibbonClientAutoConfiguration中使用@Import注解导入了三个客户端配置bean:

HttpClientFeignLoadBalancedConfiguration、OkHttpFeignLoadBalancedConfiguration、DefaultFeignLoadBalancedConfiguration。这三个类根据配置和Condition条件, 都会向spring容器中注入feign的client组件 - LoadBalancerFeignClient

openfeign集成apache-httpclient

HttpClientFeignLoadBalancedConfiguration
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(ApacheHttpClient.class)
@ConditionalOnProperty(value = "feign.httpclient.enabled", matchIfMissing = true)
@Import(HttpClientFeignConfiguration.class)
class HttpClientFeignLoadBalancedConfiguration {

    @Bean
    @ConditionalOnMissingBean(Client.class)
    public Client feignClient(CachingSpringLoadBalancerFactory cachingFactory,
            SpringClientFactory clientFactory, HttpClient httpClient) {
        ApacheHttpClient delegate = new ApacheHttpClient(httpClient);
        return new LoadBalancerFeignClient(delegate, cachingFactory, clientFactory);
    }

}

根据配置类的注解可以看到: 1.通过@ConditionalOnClass(ApacheHttpClient.class)注解, 只有引入apache-httpclient的相关依赖, 才会使当前配置bean生效 2.通过@ConditionalOnProperty(value = "feign.httpclient.enabled", matchIfMissing = true), 说明只要满足上述条件, 即使不配置feign.httpclient.enabled属性, 也可以生效, 除非手动将其改为false

集成使用

根据上面的描述, 理论上只需要导入feign-httpclient依赖, 即可完成openfeign与apache-httpclient的集成!

openfeign集成okhttp

OkHttpFeignLoadBalancedConfiguration
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(OkHttpClient.class)
@ConditionalOnProperty("feign.okhttp.enabled")
@Import(OkHttpFeignConfiguration.class)
class OkHttpFeignLoadBalancedConfiguration {

    @Bean
    @ConditionalOnMissingBean(Client.class)
    public Client feignClient(CachingSpringLoadBalancerFactory cachingFactory,
            SpringClientFactory clientFactory, okhttp3.OkHttpClient okHttpClient) {
        OkHttpClient delegate = new OkHttpClient(okHttpClient);
        return new LoadBalancerFeignClient(delegate, cachingFactory, clientFactory);
    }

}

根据配置类的注解可以看到: 1.通过@ConditionalOnClass(OkHttpClient.class)注解, 只有引入okhttp的相关依赖, 才会使当前配置bean生效 2.通过@ConditionalOnProperty("feign.okhttp.enabled"), 这里跟apache-httpclient的集成配置不同, 需要在配置文件中手动开启feign.okhttp.enabled配置

集成使用

这里有坑, 如果仅仅引入feign-okhttp依赖, 并在配置文件手动开启配置后, 并不会加载OkHttpFeignLoadBalancedConfiguration配置类, 导致集成失败 openfeign集成okhttp失效原因及解决办法参考

openfeign默认集成jdk-httpclient

DefaultFeignLoadBalancedConfiguration
@Configuration(proxyBeanMethods = false)
class DefaultFeignLoadBalancedConfiguration {

    @Bean
    @ConditionalOnMissingBean
    public Client feignClient(CachingSpringLoadBalancerFactory cachingFactory,
            SpringClientFactory clientFactory) {
        return new LoadBalancerFeignClient(new Client.Default(null, null), cachingFactory,
                clientFactory);
    }

}

根据配置类可以看到: 1.使用@ConditionalOnMissingBean表示当spring容器中没有Client对象时, 才会使用Client.Default(默认使用jdk的http客户端实现)

木兰宽松许可证, 第2版 木兰宽松许可证, 第2版 2020年1月 http://license.coscl.org.cn/MulanPSL2 您对“软件”的复制、使用、修改及分发受木兰宽松许可证,第2版(“本许可证”)的如下条款的约束: 0. 定义 “软件”是指由“贡献”构成的许可在“本许可证”下的程序和相关文档的集合。 “贡献”是指由任一“贡献者”许可在“本许可证”下的受版权法保护的作品。 “贡献者”是指将受版权法保护的作品许可在“本许可证”下的自然人或“法人实体”。 “法人实体”是指提交贡献的机构及其“关联实体”。 “关联实体”是指,对“本许可证”下的行为方而言,控制、受控制或与其共同受控制的机构,此处的控制是指有受控方或共同受控方至少50%直接或间接的投票权、资金或其他有价证券。 1. 授予版权许可 每个“贡献者”根据“本许可证”授予您永久性的、全球性的、免费的、非独占的、不可撤销的版权许可,您可以复制、使用、修改、分发其“贡献”,不论修改与否。 2. 授予专利许可 每个“贡献者”根据“本许可证”授予您永久性的、全球性的、免费的、非独占的、不可撤销的(根据本条规定撤销除外)专利许可,供您制造、委托制造、使用、许诺销售、销售、进口其“贡献”或以其他方式转移其“贡献”。前述专利许可仅限于“贡献者”现在或将来拥有或控制的其“贡献”本身或其“贡献”与许可“贡献”时的“软件”结合而将必然会侵犯的专利权利要求,不包括对“贡献”的修改或包含“贡献”的其他结合。如果您或您的“关联实体”直接或间接地,就“软件”或其中的“贡献”对任何人发起专利侵权诉讼(包括反诉或交叉诉讼)或其他专利维权行动,指控其侵犯专利权,则“本许可证”授予您对“软件”的专利许可自您提起诉讼或发起维权行动之日终止。 3. 无商标许可 “本许可证”不提供对“贡献者”的商品名称、商标、服务标志或产品名称的商标许可,但您为满足第4条规定的声明义务而必须使用除外。 4. 分发限制 您可以在任何媒介中将“软件”以源程序形式或可执行形式重新分发,不论修改与否,但您必须向接收者提供“本许可证”的副本,并保留“软件”中的版权、商标、专利及免责声明。 5. 免责声明与责任限制 “软件”及其中的“贡献”在提供时不带任何明示或默示的担保。在任何情况下,“贡献者”或版权所有者不对任何人因使用“软件”或其中的“贡献”而引发的任何直接或间接损失承担责任,不论因何种原因导致或者基于何种法律理论,即使其曾被建议有此种损失的可能性。 6. 语言 “本许可证”以中英文双语表述,中英文版本具有同等法律效力。如果中英文版本存在任何冲突不一致,以中文版为准。 条款结束 如何将木兰宽松许可证,第2版,应用到您的软件 如果您希望将木兰宽松许可证,第2版,应用到您的新软件,为了方便接收者查阅,建议您完成如下三步: 1, 请您补充如下声明中的空白,包括软件名、软件的首次发表年份以及您作为版权人的名字; 2, 请您在软件包的一级目录下创建以“LICENSE”为名的文件,将整个许可证文本放入该文件中; 3, 请将如下声明文本放入每个源文件的头部注释中。 Copyright (c) [Year] [name of copyright holder] [Software Name] is licensed under Mulan PSL v2. You can use this software according to the terms and conditions of the Mulan PSL v2. You may obtain a copy of Mulan PSL v2 at: http://license.coscl.org.cn/MulanPSL2 THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. See the Mulan PSL v2 for more details. Mulan Permissive Software License,Version 2 Mulan Permissive Software License,Version 2 (Mulan PSL v2) January 2020 http://license.coscl.org.cn/MulanPSL2 Your reproduction, use, modification and distribution of the Software shall be subject to Mulan PSL v2 (this License) with the following terms and conditions: 0. Definition Software means the program and related documents which are licensed under this License and comprise all Contribution(s). Contribution means the copyrightable work licensed by a particular Contributor under this License. Contributor means the Individual or Legal Entity who licenses its copyrightable work under this License. Legal Entity means the entity making a Contribution and all its Affiliates. Affiliates means entities that control, are controlled by, or are under common control with the acting entity under this License, ‘control’ means direct or indirect ownership of at least fifty percent (50%) of the voting power, capital or other securities of controlled or commonly controlled entity. 1. Grant of Copyright License Subject to the terms and conditions of this License, each Contributor hereby grants to you a perpetual, worldwide, royalty-free, non-exclusive, irrevocable copyright license to reproduce, use, modify, or distribute its Contribution, with modification or not. 2. Grant of Patent License Subject to the terms and conditions of this License, each Contributor hereby grants to you a perpetual, worldwide, royalty-free, non-exclusive, irrevocable (except for revocation under this Section) patent license to make, have made, use, offer for sale, sell, import or otherwise transfer its Contribution, where such patent license is only limited to the patent claims owned or controlled by such Contributor now or in future which will be necessarily infringed by its Contribution alone, or by combination of the Contribution with the Software to which the Contribution was contributed. The patent license shall not apply to any modification of the Contribution, and any other combination which includes the Contribution. If you or your Affiliates directly or indirectly institute patent litigation (including a cross claim or counterclaim in a litigation) or other patent enforcement activities against any individual or entity by alleging that the Software or any Contribution in it infringes patents, then any patent license granted to you under this License for the Software shall terminate as of the date such litigation or activity is filed or taken. 3. No Trademark License No trademark license is granted to use the trade names, trademarks, service marks, or product names of Contributor, except as required to fulfill notice requirements in Section 4. 4. Distribution Restriction You may distribute the Software in any medium with or without modification, whether in source or executable forms, provided that you provide recipients with a copy of this License and retain copyright, patent, trademark and disclaimer statements in the Software. 5. Disclaimer of Warranty and Limitation of Liability THE SOFTWARE AND CONTRIBUTION IN IT ARE PROVIDED WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED. IN NO EVENT SHALL ANY CONTRIBUTOR OR COPYRIGHT HOLDER BE LIABLE TO YOU FOR ANY DAMAGES, INCLUDING, BUT NOT LIMITED TO ANY DIRECT, OR INDIRECT, SPECIAL OR CONSEQUENTIAL DAMAGES ARISING FROM YOUR USE OR INABILITY TO USE THE SOFTWARE OR THE CONTRIBUTION IN IT, NO MATTER HOW IT’S CAUSED OR BASED ON WHICH LEGAL THEORY, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 6. Language THIS LICENSE IS WRITTEN IN BOTH CHINESE AND ENGLISH, AND THE CHINESE VERSION AND ENGLISH VERSION SHALL HAVE THE SAME LEGAL EFFECT. IN THE CASE OF DIVERGENCE BETWEEN THE CHINESE AND ENGLISH VERSIONS, THE CHINESE VERSION SHALL PREVAIL. END OF THE TERMS AND CONDITIONS How to Apply the Mulan Permissive Software License,Version 2 (Mulan PSL v2) to Your Software To apply the Mulan PSL v2 to your work, for easy identification by recipients, you are suggested to complete following three steps: i Fill in the blanks in following statement, including insert your software name, the year of the first publication of your software, and your name identified as the copyright owner; ii Create a file named “LICENSE” which contains the whole context of this License in the first directory of your software package; iii Attach the statement to the appropriate annotated syntax at the beginning of each source file. Copyright (c) [Year] [name of copyright holder] [Software Name] is licensed under Mulan PSL v2. You can use this software according to the terms and conditions of the Mulan PSL v2. You may obtain a copy of Mulan PSL v2 at: http://license.coscl.org.cn/MulanPSL2 THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. See the Mulan PSL v2 for more details.

简介

使用eureka作为注册中心,,person和equipment两个web服务作为业务中台,本例中会使用postman调用person服务,person服务中调用equipment服务 展开 收起
README
MulanPSL-2.0
取消

发行版

暂无发行版

贡献者

全部

近期动态

不能加载更多了
马建仓 AI 助手
尝试更多
代码解读
代码找茬
代码优化
Java
1
https://gitee.com/jike11231/springboot-openfeign.git
git@gitee.com:jike11231/springboot-openfeign.git
jike11231
springboot-openfeign
springboot-openfeign
master

搜索帮助