# springcloud-openfeign **Repository Path**: tca/springcloud-openfeign ## Basic Information - **Project Name**: springcloud-openfeign - **Description**: spring-cloud-openfeign源码学习 - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 1 - **Forks**: 2 - **Created**: 2021-11-22 - **Last Updated**: 2024-11-27 ## Categories & Tags **Categories**: Uncategorized **Tags**: spring-cloud-openfeign源码学习 ## README # 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 ```xml person tca 1.0.0 4.0.0 cloud-person-api org.projectlombok lombok javax.validation validation-api org.springframework.cloud spring-cloud-starter-openfeign ``` ##### spring-cloud-starter-openfeign ```xml org.springframework.cloud spring-cloud-starter org.springframework.cloud spring-cloud-openfeign-core org.springframework spring-web org.springframework.cloud spring-cloud-commons io.github.openfeign feign-core io.github.openfeign feign-slf4j io.github.openfeign feign-hystrix io.github.openfeign feign-java8 org.springframework.cloud spring-cloud-starter-netflix-ribbon true org.springframework.cloud spring-cloud-starter-netflix-archaius true ``` 可以看到,openfeign中已经引入了ribbon,hystrix相关依赖,即openfeign默认集成了 ribbon 和 hystrix 相关 #### person-biz ```xml org.springframework.boot spring-boot-starter-web tca cloud-person-api 1.0.0 tca cloud-equipment-api 1.0.0 tca common-log-springboot-logback 1.0.0 ``` #### person-provider ```xml tca cloud-person-biz 1.0.0 org.springframework.cloud spring-cloud-starter-netflix-eureka-client ``` equipment与person类似,这里省略 ### 1.4 java代码 #### 启动类 ```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 ```java 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 ```java 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 ```java 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 ```java @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) @Documented @Import(FeignClientsRegistrar.class) // 引入FeignClientsRegistrar, @Import作用在spring和springboot源码中已经描述过, 这里不作赘述 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 ```java /* * 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 @RibbonClient 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 接口 ```java // 在类声明中可以看到 它 实现了 ImportBeanDefinitionRegistrar接口,这个在spring源码中有介绍,在ConfigurationClassPostProcessor中会委托ConfigurationClassParser解析 @Import 注解获取到 FeignClientsRegistrar, 判断其实现了 ImportBeanDefinitionRegistrar 接口, 此时会使用反射创建FeignClientsRegistrar实例, 回调其Aware接口, 之后, 会统一回调 FeignClientsRegistrar#registerBeanDefinitions方法 class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware ``` ### 3.2 FeignClientsRegistrar#registerBeanDefinitions ```java @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 ```java private void registerDefaultConfiguration(AnnotationMetadata metadata, BeanDefinitionRegistry registry) { // 1.获取 EnableFeignClients 的属性 // 该方法第二个参数true,表示将注解中class类型的属性转换为字符串类名暴露到返回到map中 Map 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.com.tca.cloud.standalone.person.provider.PersonApplication name = "default." + metadata.getClassName(); } // 4.向spring容器中注册FeignClient相关配置类, 见下 registerClientConfiguration(registry, name, defaultAttrs.get("defaultConfiguration")); } } ``` FeignClientsRegistrar#registerClientConfiguration ```java 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.com.tca.cloud.standalone.person.provider.PersonApplication // configuration == {}, 因为EnableFeignClients中的 defaultConfiguration 属性为 {} builder.addConstructorArgValue(name); builder.addConstructorArgValue(configuration); // 3.向spring容器中注册bd // name == default.com.tca.cloud.standalone.person.provider.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 ```java public void registerFeignClients(AnnotationMetadata metadata, BeanDefinitionRegistry registry) { // 1.获取包扫描器, 这里跟 mybatis-spring 集成不同, mybatis-spring 是定义一个扫描器继承 ClasspathBeanDefinitionScanner, 重写isCandidateComponent方法 // 在这里, 它重新定义了一个scanner --> ClassPathScanningCandidateComponentProvider, 原理差不多 ClassPathScanningCandidateComponentProvider scanner = getScanner(); // 设置资源加载器 scanner.setResourceLoader(this.resourceLoader); Set basePackages; // 2.获取@FeignClient注解属性 Map 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 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 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 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 ```java // 这里就是注册FeignClient对应bd, 这里以 personFeign 为例 private void registerFeignClient(BeanDefinitionRegistry registry, AnnotationMetadata annotationMetadata, Map 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 ```java /** * @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 configurations = new ArrayList<>(); // 向容器中注册FeignContext【核心组件】 @Bean public FeignContext feignContext() { FeignContext context = new FeignContext(); context.setConfigurations(this.configurations); return context; } // ...... } ``` ### 4.3 FeignContext ```java // FeignContext 是feign的全局上下文, 它组合了所有 FeignClientSpecification public class FeignContext extends NamedContextFactory { public FeignContext() { super(FeignClientsConfiguration.class, "feign", "feign.client.name"); } } ``` ```java public abstract class NamedContextFactory implements DisposableBean, ApplicationContextAware { private final String propertySourceName; private final String propertyName; // 【重要】 // key == 微服务的名称, 即@FeignClient的value属性值 // value == spring容器, 它是服务容器的子容器, 即创建FeignClient代理对象时的组件都放在子容器中 private Map contexts = new ConcurrentHashMap<>(); // configurations即为之前封装的所有FeignClientSpecification private Map 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 configurations = new ConcurrentHashMap<>(); // 这个用来存储上述说的默认配置和全局配置类 private Map 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 ```java @Override public Object getObject() throws Exception { // 见下 return getTarget(); } ``` ```java 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 ```java protected Feign.Builder feign(FeignContext context) { // 1.从FeignContext中获取FeignLoggerFactory // 由 @4.3 可以看到, FeignContext的父类中有一个重要的属性: // private Map 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 ```java protected T loadBalance(Feign.Builder builder, FeignContext context, HardCodedTarget 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 ```java // 这里会创建代理对象 @Override public T target(FeignClientFactoryBean factory, Feign.Builder feign, FeignContext context, Target.HardCodedTarget 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 ```java // target == Target.HardCodedTarget public T target(Target target) { // 1.build --> 创建 Feign 对象, 这里的 Feign == ReflectiveFeign // 2.newInstance --> 创建代理对象 return build().newInstance(target); } ``` Feign#build ```java 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 ```java @Override public T newInstance(Target target) { // 1.生成 name -- methodHandler , 以EquipmentFeign为例 // name == EquipmentFeign#get(EquipmentReq) // value == SynchronousMethodHandler Map nameToHandler = targetToHandlersByName.apply(target); // 2.声明 method - MethodHandler Map methodToHandler = new LinkedHashMap(); List defaultMethodHandlers = new LinkedList(); // 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 ```java @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 ```java @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 ```java 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 ```java @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 ```java private FeignLoadBalancer lbClient(String clientName) { // lbClientFactory --> CachingSpringLoadBalancerFactory return this.lbClientFactory.create(clientName); } ``` CachingSpringLoadBalancerFactory#create ```java 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 ```java public ILoadBalancer getLoadBalancer(String name) { // 根据微服务名, 从子容器中获取ILoadBalancer实例, 取不到就从父容器中获取 // 这里获取的是 ZoneAwareLoadBalancer return getInstance(name, ILoadBalancer.class); } ``` ```java @Override public C getInstance(String name, Class 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方法 ```java /** * 当调用者希望将请求分派到负载平衡器选择的服务器, 而不是在请求的URI中指定服务器时, 应该使用此方法。 * 它通过调用reconstructURIWithServer来计算最终的URI */ public T executeWithLoadBalancer(final S request, final IClientConfig requestConfig) throws ClientException { LoadBalancerCommand command = buildLoadBalancerCommand(request, requestConfig); try { // 1.command提交了一个服务器操作, 当负载均衡选到了要访问的服务器后, 就会执行这个ServerOperation, 所以负载均衡触发点主要在sumbit中 return command.submit( new ServerOperation() { @Override public Observable 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 方法,这个方法涉及服务器的选择 ```java public Observable submit(final ServerOperation operation) { // ...... // 通过 selectServer 选择服务器 Observable o = (server == null ? selectServer() : Observable.just(server)) .concatMap(new Func1>() { }); if (maxRetrysSame > 0) o = o.retry(retryPolicy(maxRetrysSame, true)); return o; } }); // ...... } ``` ### 6.7 LoadBalancerCommand#selectServer ```java private Observable selectServer() { return Observable.create(new OnSubscribe() { @Override public void call(Subscriber next) { try { // 选择服务器 Server server = loadBalancerContext.getServerFromLoadBalancer(loadBalancerURI, loadBalancerKey); next.onNext(server); next.onCompleted(); } catch (Exception e) { next.onError(e); } } }); } ``` LoadBalancerContext#getServerFromLoadBalancer ```java 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 ```java 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文档: [参考](https://gitee.com/tca/common/blob/master/common-learning/common-learning-openfeign/readme.md) ``` 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 ```properties 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 ```java @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 ```java @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 ```java @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 ```java @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失效原因及解决办法参考](https://blog.csdn.net/esunlang/article/details/113772887) #### openfeign默认集成jdk-httpclient ###### DefaultFeignLoadBalancedConfiguration ```java @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客户端实现)