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
这里使用eureka作为注册中心,,person和equipment两个web服务作为业务中台,本例中会使用postman调用person服务,person服务中调用equipment服务
<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>
<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 相关
<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>
<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类似,这里省略
/**
* @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);
}
}
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);
}
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;
}
}
openfeign中最常用的注解是:启动类上的 @EnableFeignClients 和 接口声明上的@FeignClient
@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的全局配置类
/*
* 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代理对象
// 在类声明中可以看到 它 实现了 ImportBeanDefinitionRegistrar接口,这个在spring源码中有介绍,在ConfigurationClassPostProcessor中会委托ConfigurationClassParser解析 @Import 注解获取到 FeignClientsRegistrar, 判断其实现了 ImportBeanDefinitionRegistrar 接口, 此时会使用反射创建FeignClientsRegistrar实例, 回调其Aware接口, 之后, 会统一回调 FeignClientsRegistrar#registerBeanDefinitions方法
class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
// 注册默认配置 见@3.2.1
registerDefaultConfiguration(metadata, registry);
// 注册FeignClient 见@3.2.2
registerFeignClients(metadata, registry);
}
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, 默认为 {}
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
在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
/**
* @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;
}
// ......
}
// 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子容器, 实现客户端之间的配置隔离!!!
前期我们向spring容器中注册了feignClient相关bd, 对应的bd为FactoryBean的实现类 --> FeignClientFactoryBean, 因此代理对象是通过 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));
}
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对象!!!
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对象
// 这里会创建代理对象
@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;
}
从上述可以看到, 通过jdk动态代理创建 clientfeign 代理对象, jdk的动态代理有一个核心类 -- InvocationHandler, 这里默认实现是: 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);
}
@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;
}
}
}
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());
}
}
}
@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);
}
}
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);
}
这里是负载均衡的执行流程!!!调用的是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;
}
});
// ......
}
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;
}
}
}
这块可以参考common-learning-openfeign文档:
Feign的构建涉及到一些核心组件: contract, encoder, decoder, interceptor、logger、httpclient、retryer。
使用springcloud-openfeign时, 我们可以将组件通过@EnableFeignClients或@FeignClient的configuration配置, 将自定义组件注册到spring容器中, 在构建Feign时, 可以使用spring容器中的相关组件!
见@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
@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
@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.2的描述可以看到, 自动配置类FeignRibbonClientAutoConfiguration中使用@Import注解导入了三个客户端配置bean:
HttpClientFeignLoadBalancedConfiguration、OkHttpFeignLoadBalancedConfiguration、DefaultFeignLoadBalancedConfiguration。这三个类根据配置和Condition条件, 都会向spring容器中注入feign的client组件 - LoadBalancerFeignClient
@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的集成!
@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失效原因及解决办法参考
@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客户端实现)
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。