OpenFeign是什么?
Feign is a declarative web service client. It makes writing web service clients easier. To use Feign create an interface and annotate it. It has pluggable annotation support including Feign annotations and JAX-RS annotations. Feign also supports pluggable encoders and decoders. Spring Cloud adds support for Spring MVC annotations and for using the same HttpMessageConverters used by default in Spring Web. Spring Cloud integrates Eureka, Spring Cloud CircuitBreaker, as well as Spring Cloud LoadBalancer to provide a load-balanced http client when using Feign.
feign是一个声明式的web服务客户端。它使得编写web服务客户端更加容易。要使用 feign 则需要创建一个接口并给接口添加上注解即可。
它具有可插入式的注解支持,包含feign注解和JAX-RS注解。feign也支持可插入的编码和解码。Spring Cloud增加了对Spring MVC注释的支持,并支持使用Spring Web中默认使用的HttpMessageConverters。当使用 Feign 时,Spring Cloud 整合了 Eureka,Spring Cloud CircuitBreaker(断路器)以及Spring Cloud LoadBalancer(负载均衡)以提供负载均衡的http client。
说白了, openFeign就是个针对服务端映射(controller)的客户端工具, 主要目的是让 客户端 能够以类似 调用service的方式 调用到 服务端 的 controller
Feign和OpenFeign的区别
| Feign | openFiegn |
|---|---|
| Feign是SpringCloud组件中一个轻量级RESTful的HTTP服务客户端,Feign内置了Ribbon,用来做客户端负载均衡,去调用服务注册中心的服务。Feign的使用方式是:使用Feign的注解定义接口,调用这个接口,就可以调用服务注册中心的服务 | OpenFeign 是SpringCloud在Feign的基础上支持了SpringMVC的注解,如@RequestMapping等。OpenFeign 的@FeignClient可以解析SpringMVC的@RequestMapping注解下的接口,并通过动态代理的方式产生实现类,实现类中做负载均衡并调用其他服务。 |
怎么用?
openFeign 也是针对客户端设计的, 所以代码一般卸载客户端上
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
<dependency>
<groupId>com.zhazha</groupId>
<artifactId>cloud-api-common</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!--一般基础通用配置-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
server:
port: 80
spring:
application:
## 指定服务名称,在nacos中的名字
name: cloud-order-service
cloud:
nacos:
server-addr: 127.0.0.1:8848
discovery:
# register-enabled: true
# watch:
# enabled: true
username: nacos
password: nacos
management:
endpoints:
web:
exposure:
## yml文件中存在特殊字符,必须用单引号包含,否则启动报错
include: '*'
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
@SpringBootApplication
@EnableFeignClients
@EnableDiscoveryClient
public class OrderFeignMain80 {
public static void main(String[] args) throws Exception {
SpringApplication.run(OrderFeignMain80.class, args);
}
}
import com.zhazha.springcloud.entities.Payment;
import com.zhazha.springcloud.utils.CommonResult;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
// 区分大小写
// 这里需要和服务提供方的 spring.application.name 的值相同
@FeignClient(value = "cloud-payment-service")
public interface PaymentFeignService {
@GetMapping("/payment/get/{id}")
CommonResult<Payment> getPaymentById(@PathVariable("id") Long id);
@PostMapping(value = "create")
CommonResult create(@RequestBody Payment payment);
}
import com.zhazha.springcloud.entities.Payment;
import com.zhazha.springcloud.service.PaymentFeignService;
import com.zhazha.springcloud.utils.CommonResult;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
@RestController
@Slf4j
public class OrderFeignController {
@Resource
private PaymentFeignService paymentFeignService;
@GetMapping("/consumer/payment/get/{id}")
public CommonResult<Payment> getPaymentById(@PathVariable("id") Long id) {
return paymentFeignService.getPaymentById(id);
}
}
注意:
CommonResult create(@RequestBody Payment payment);openFeign默认的传参方式就是JSON传参(@RequestBody),因此定义接口的时候可以不用@RequestBody注解标注,不过为了规范,一般都填上。
注意:因为
openFeign默认使用JSON传参方式,如果需要使用表单传参方式则改为@SpringQueryMapCommonResult create(@SpringQueryMap Payment payment);
OpenFeign超时设置
openFeign底层默认超时判定时间是 1秒 , 如果超出 1秒就会抛出异常
所以在有必要的情况下, 可以自行修改超时时间
feign:
client:
config:
## default 设置的全局超时时间,指定服务名称可以设置单个服务的超时时间
default:
connectTimeout: 5000
readTimeout: 5000
loggerLevel: basic
但是如果需要调用涉及到多个openFeign接口的调用呢?
比如下图的情况:
很明显,ServiceA 和 ServiceB 能够通过,但 ServiceC 不能够通过
此时我们可以单独给 serviceC 设置超时时间:
feign:
client:
config:
## default 设置的全局超时时间,指定服务名称可以设置单个服务的超时时间
default:
connectTimeout: 5000
readTimeout: 5000
loggerLevel: basic
cloud-order-service:
connectTimeout: 50000
readTimeout: 50000
loggerLevel: basic
单独设置的优先级大于
default
如何开启日志增强?
openFeign虽然提供了日志增强功能,但是默认是不显示任何日志的,不过开发者在调试阶段可以自己配置日志的级别。openFeign的日志级别如下:
**NONE**:默认的,不显示任何日志;**BASIC**:仅记录请求方法、URL、响应状态码及执行时间;**HEADERS**:除了BASIC中定义的信息之外,还有请求和响应的头信息;**FULL**:除了HEADERS中定义的信息之外,还有请求和响应的正文及元数据。
配置起来也很简单,步骤如下:
import feign.Logger;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class OpenfeignConfig {
@Bean
Logger.Level feignLoggerLevel() {
return Logger.Level.FULL;
}
}
注意:这里的
logger是feign包里的。同时记住把application.yml中的配置loggerLevel: basic注释掉
接着在 application.yml文件中添加:
logging:
level:
com.zhazha.springcloud.service: debug
FULL显示出来的日志:
2022-08-08 20:20:28.650 DEBUG 8752 --- [p-nio-80-exec-2] c.z.s.service.PaymentFeignService : [PaymentFeignService#getPaymentById] ---> GET http://cloud-payment-service/payment/get/1 HTTP/1.1
2022-08-08 20:20:28.651 DEBUG 8752 --- [p-nio-80-exec-2] c.z.s.service.PaymentFeignService : [PaymentFeignService#getPaymentById] ---> END HTTP (0-byte body)
2022-08-08 20:20:28.664 DEBUG 8752 --- [p-nio-80-exec-2] c.z.s.service.PaymentFeignService : [PaymentFeignService#getPaymentById] <--- HTTP/1.1 200 (12ms)
2022-08-08 20:20:28.664 DEBUG 8752 --- [p-nio-80-exec-2] c.z.s.service.PaymentFeignService : [PaymentFeignService#getPaymentById] connection: keep-alive
2022-08-08 20:20:28.664 DEBUG 8752 --- [p-nio-80-exec-2] c.z.s.service.PaymentFeignService : [PaymentFeignService#getPaymentById] content-type: application/json
2022-08-08 20:20:28.665 DEBUG 8752 --- [p-nio-80-exec-2] c.z.s.service.PaymentFeignService : [PaymentFeignService#getPaymentById] date: Mon, 08 Aug 2022 12:20:28 GMT
2022-08-08 20:20:28.665 DEBUG 8752 --- [p-nio-80-exec-2] c.z.s.service.PaymentFeignService : [PaymentFeignService#getPaymentById] keep-alive: timeout=60
2022-08-08 20:20:28.665 DEBUG 8752 --- [p-nio-80-exec-2] c.z.s.service.PaymentFeignService : [PaymentFeignService#getPaymentById] transfer-encoding: chunked
2022-08-08 20:20:28.665 DEBUG 8752 --- [p-nio-80-exec-2] c.z.s.service.PaymentFeignService : [PaymentFeignService#getPaymentById]
2022-08-08 20:20:28.665 DEBUG 8752 --- [p-nio-80-exec-2] c.z.s.service.PaymentFeignService : [PaymentFeignService#getPaymentById] {"code":200,"message":"查询成功 port: 8001","data":{"id":1,"serial":"zhazha01"}}
2022-08-08 20:20:28.665 DEBUG 8752 --- [p-nio-80-exec-2] c.z.s.service.PaymentFeignService : [PaymentFeignService#getPaymentById] <--- END HTTP (84-byte body)
OpenFeign工作原理
大致原理图:
分为初始化和拦截两个部分
初始化
在分析代码前,添加上basePackages,当然不加也行
从@EnableFeignClients注解入手分析
看到@Import注解,第一时间应当想到ImportBeanDefinitionRegistrar接口
还需要想到
ImportSelector
同时就需要了解改接口的作用主要目的就是:利用registerBeanDefinitions函数的AnnotationMetadata importingClassMetadata参数获取@EnableFeignClients(basePackages = "com.zhazha.springcloud.service")注解的属性,然后使用BeanDefinitionRegistry registry去注册 Bean Definition
在本例子中
AnnotationMetadata importingClassMetadata参数解析的是注解EnableFeignClients,在其他组件中,比如服务的发现与注册中寻找的是@EnableDiscoveryClient注解
AnnotationMetadata importingClassMetadata可以获得com.zhazha.springcloud.OrderFeignMain80启动类上的注解
public interface ImportBeanDefinitionRegistrar {
default void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry,
BeanNameGenerator importBeanNameGenerator) {
registerBeanDefinitions(importingClassMetadata, registry);
}
/**
* Register bean definitions as necessary based on the given annotation metadata of
* the importing {@code @Configuration} class.
* <p>Note that {@link BeanDefinitionRegistryPostProcessor} types may <em>not</em> be
* registered here, due to lifecycle constraints related to {@code @Configuration}
* class processing.
* <p>The default implementation is empty.
* @param importingClassMetadata annotation metadata of the importing class
* @param registry current bean definition registry
*/
default void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
}
}
在本案例中,OpenFeign使用@Import导入的是FeignClientsRegistrar类
从下面函数入手分析:
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
registerDefaultConfiguration(metadata, registry);
registerFeignClients(metadata, registry);
}
第一段代码主要分析到的是defaultConfiguration属性,该属性用来自定义所有Feign客户端的配置,使用@Configuration进行配置。当然也可以为某一个Feign客户端进行配置。具体配置方法见@FeignClient的configuration属性。
没用到不过也注册了一个Bean,但不分析(懒)
核心代码:
public void registerFeignClients(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
LinkedHashSet<BeanDefinition> candidateComponents = new LinkedHashSet<>();
// 获得启动类上的注解EnableFeignClients
Map<String, Object> attrs = metadata.getAnnotationAttributes(EnableFeignClients.class.getName());
// 获取该注解的 clients 属性,看看是否有被@feignClient 注解标记的类
// 我们项目中并未使用该注解的clients属性,所以获取为 clients.length 长度为 0
final Class<?>[] clients = attrs == null ? null : (Class<?>[]) attrs.get("clients");
if (clients == null || clients.length == 0) {
// 这段代码主要目的就是扫描 basePackages 注解的包路径,本案例中我主动添加了 com.zhazha.springcloud.service
// 路径,所以会扫描该路径下的所有 FeignClient 注解标记的类
ClassPathScanningCandidateComponentProvider scanner = getScanner();
scanner.setResourceLoader(this.resourceLoader);
scanner.addIncludeFilter(new AnnotationTypeFilter(FeignClient.class));
// 源码在下面标记 ① 的地方
Set<String> basePackages = getBasePackages(metadata);
for (String basePackage : basePackages) {
// 将扫描出来的类都添加到 Set 集合中
// 在本案例中只能扫描到 PaymentFeignService 类
// 只有他添加了 @FeignClient(value = "cloud-payment-service") 注解
candidateComponents.addAll(scanner.findCandidateComponents(basePackage));
}
}
else {
for (Class<?> clazz : clients) {
candidateComponents.add(new AnnotatedGenericBeanDefinition(clazz));
}
}
for (BeanDefinition candidateComponent : candidateComponents) {
if (candidateComponent instanceof AnnotatedBeanDefinition) {
// verify annotated class is an interface
AnnotatedBeanDefinition beanDefinition = (AnnotatedBeanDefinition) candidateComponent;
AnnotationMetadata annotationMetadata = beanDefinition.getMetadata();
// @FeignClient 注解的类只能是接口
Assert.isTrue(annotationMetadata.isInterface(), "@FeignClient can only be specified on an interface");
// 拿到 FeignClient 注解的所有属性信息
// 这里只能拿到value = "cloud-payment-service" 的值,其他都是默认的
Map<String, Object> attributes = annotationMetadata
.getAnnotationAttributes(FeignClient.class.getCanonicalName());
// 此处 name = cloud-payment-service
// 此处源码:看 ②
// 可以明显看到优先级
String name = getClientName(attributes);
// 获取 FeignClient 注解configuration属性的值
// 我们什么都没填,但还是注入了一个 bean
registerClientConfiguration(registry, name, attributes.get("configuration"));
// 核心代码 看 ③
registerFeignClient(registry, annotationMetadata, attributes);
}
}
}
①getBasePackages源码:
protected Set<String> getBasePackages(AnnotationMetadata importingClassMetadata) {
Map<String, Object> attributes = importingClassMetadata
.getAnnotationAttributes(EnableFeignClients.class.getCanonicalName());
Set<String> basePackages = new HashSet<>();
for (String pkg : (String[]) attributes.get("value")) {
if (StringUtils.hasText(pkg)) {
basePackages.add(pkg);
}
}
for (String pkg : (String[]) attributes.get("basePackages")) {
if (StringUtils.hasText(pkg)) {
basePackages.add(pkg);
}
}
for (Class<?> clazz : (Class[]) attributes.get("basePackageClasses")) {
basePackages.add(ClassUtils.getPackageName(clazz));
}
if (basePackages.isEmpty()) {
basePackages.add(ClassUtils.getPackageName(importingClassMetadata.getClassName()));
}
return basePackages;
}
② getClientName源码:
private String getClientName(Map<String, Object> client) {
if (client == null) {
return null;
}
String value = (String) client.get("contextId");
if (!StringUtils.hasText(value)) {
value = (String) client.get("value");
}
if (!StringUtils.hasText(value)) {
value = (String) client.get("name");
}
if (!StringUtils.hasText(value)) {
value = (String) client.get("serviceId");
}
if (StringUtils.hasText(value)) {
return value;
}
throw new IllegalStateException(
"Either 'name' or 'value' must be provided in @" + FeignClient.class.getSimpleName());
}
③registerFeignClient:
// 第一个函数是注册类本体
// 第二个是 PaymentService 上的注解元信息,在registerFeignClient方法体中只做了 getClassName 这一项操作
// 注解 @FeignClient 的属性 key value 我们只配置了 value -> cloud-payment-service
private void registerFeignClient(BeanDefinitionRegistry registry, AnnotationMetadata annotationMetadata,
Map<String, Object> attributes) {
// 获取 PaymentService 的类名 com.zhazha.springcloud.service.PaymentFeignService
String className = annotationMetadata.getClassName();
// PaymentService 的类加载器
Class clazz = ClassUtils.resolveClassName(className, null);
ConfigurableBeanFactory beanFactory = registry instanceof ConfigurableBeanFactory
? (ConfigurableBeanFactory) registry : null;
// contextId = cloud-payment-service
String contextId = getContextId(beanFactory, attributes);
// name = cloud-payment-service
String name = getName(attributes);
FeignClientFactoryBean factoryBean = new FeignClientFactoryBean();
factoryBean.setBeanFactory(beanFactory);
factoryBean.setName(name);
factoryBean.setContextId(contextId);
factoryBean.setType(clazz);
// 判断application.yml是否设置了 feign.client.refresh-enabled 配置项,我们这里没配置
factoryBean.setRefreshableClient(isClientRefreshEnabled());
BeanDefinitionBuilder definition = BeanDefinitionBuilder.genericBeanDefinition(clazz, () -> {
factoryBean.setUrl(getUrl(beanFactory, attributes));
factoryBean.setPath(getPath(beanFactory, attributes));
factoryBean.setDecode404(Boolean.parseBoolean(String.valueOf(attributes.get("decode404"))));
Object fallback = attributes.get("fallback");
if (fallback != null) {
factoryBean.setFallback(fallback instanceof Class ? (Class<?>) fallback
: ClassUtils.resolveClassName(fallback.toString(), null));
}
Object fallbackFactory = attributes.get("fallbackFactory");
if (fallbackFactory != null) {
factoryBean.setFallbackFactory(fallbackFactory instanceof Class ? (Class<?>) fallbackFactory
: ClassUtils.resolveClassName(fallbackFactory.toString(), null));
}
// 需要注意的 FeignClientFactoryBean 实现了 FactoryBean,所以如果需要定义一个对象时
// 将会调用 FactoryBean 接口的 getObject 方法, 用于定义一个对象
// 但这段代码并没有被立即调用
return factoryBean.getObject();
});
definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
definition.setLazyInit(true);
validate(attributes);
AbstractBeanDefinition beanDefinition = definition.getBeanDefinition();
beanDefinition.setAttribute(FactoryBean.OBJECT_TYPE_ATTRIBUTE, className);
// 注意看这一行,我们 feign 修饰的对象 PaymentService 并没有被注入,FactoryBean也没有调用 getObject
beanDefinition.setAttribute("feignClientsRegistrarFactoryBean", factoryBean);
// has a default, won't be null
boolean primary = (Boolean) attributes.get("primary");
beanDefinition.setPrimary(primary);
String[] qualifiers = getQualifiers(attributes);
if (ObjectUtils.isEmpty(qualifiers)) {
qualifiers = new String[] { contextId + "FeignClient" };
}
// 这里将 PaymentSercice 注入到 Spring 容器中
BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className, qualifiers);
BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
// 这里还是判断是否开启 feign.client.refresh-enabled
// 这里我们没有配置,所以不走
// 但实际上注入了 key = feign.Request.Options-cloud-payment-service 的 paymentService
// 使用 refreshScope 创建Request.Options bean 定义
registerOptionsBeanDefinition(registry, contextId);
}
至此,注册完成,注意此时仅仅只是注册到 DefaultListableBeanFactory容器的 beanDefinitionMap中,并没有实例化!
从这行代码判断出来的,并没有实例化它,实例化的话需要去拿到远程的实现对象
而实例化的过程需要关注 Spring 异常熟悉的org.springframework.context.support.AbstractApplicationContext#refresh方法
我们来总结下,整个流程:
- 使用
@Import将FeignClientsRegistrar对象注入到Spring容器 - 使用
ClassPathScanningCandidateComponentProvider扫描FeignClient注解的BasePackages属性所对应的所有对象,并将其存入candidateComponents容器中 - 遍历
candidateComponents对象获取FeignClient注解的属性信息 - 创建
FeignClientFactoryBean对象,并填充这些属性信息,该对象实现了FactoryBean为以后用于容器在初始化单例对象时调用该接口的getObject函数 - 创建
holder对象,并以 key =com.zhazha.springcloud.service.PaymentFeignService,value =PaymentFeignService的beanDefinition对象 - 这里虽然往容器中存入对象,但明摆着该
Bean无法被使用,毕竟只是一个接口,需要特殊处理
实例化过程
从OrderFeignController开始入手接着去解析controller下面的PaymentService依赖
接着从 BeanFactory里面拿到PaymentService接口
最终他会回到上面我们分享过的源码片段:
BeanDefinitionBuilder definition = BeanDefinitionBuilder.genericBeanDefinition(clazz, () -> {
factoryBean.setUrl(getUrl(beanFactory, attributes));
factoryBean.setPath(getPath(beanFactory, attributes));
factoryBean.setDecode404(Boolean.parseBoolean(String.valueOf(attributes.get("decode404"))));
Object fallback = attributes.get("fallback");
if (fallback != null) {
factoryBean.setFallback(fallback instanceof Class ? (Class<?>) fallback
: ClassUtils.resolveClassName(fallback.toString(), null));
}
Object fallbackFactory = attributes.get("fallbackFactory");
if (fallbackFactory != null) {
factoryBean.setFallbackFactory(fallbackFactory instanceof Class ? (Class<?>) fallbackFactory
: ClassUtils.resolveClassName(fallbackFactory.toString(), null));
}
return factoryBean.getObject(); // 会在这里创建一个对象
});
接着就能看到核心代码:
@Override
public Object getObject() {
return getTarget();
}
/**
* @param <T> the target type of the Feign client
* @return a {@link Feign} client created with the specified data and the context
* information
*/
<T> T getTarget() {
// FeignContext 在 org.springframework.cloud.openfeign.FeignAutoConfiguration#feignContext 中
// 被注入到 Spring容器中
// 对于 FeignContext 的说明:①
FeignContext context = beanFactory != null ? beanFactory.getBean(FeignContext.class)
: applicationContext.getBean(FeignContext.class);
Feign.Builder builder = feign(context);
if (!StringUtils.hasText(url)) {
if (LOG.isInfoEnabled()) {
LOG.info("For '" + name + "' URL not provided. Will try picking an instance via load-balancing.");
}
if (!name.startsWith("http")) {
url = "http://" + name;
}
else {
url = name;
}
url += cleanPath();
// 这里将会创建一个代理对象 ②
return (T) loadBalance(builder, context, new HardCodedTarget<>(type, name, url));
}
if (StringUtils.hasText(url) && !url.startsWith("http")) {
url = "http://" + url;
}
String url = this.url + cleanPath();
Client client = getOptional(context, Client.class);
if (client != null) {
if (client instanceof FeignBlockingLoadBalancerClient) {
// not load balancing because we have a url,
// but Spring Cloud LoadBalancer is on the classpath, so unwrap
client = ((FeignBlockingLoadBalancerClient) client).getDelegate();
}
if (client instanceof RetryableFeignBlockingLoadBalancerClient) {
// not load balancing because we have a url,
// but Spring Cloud LoadBalancer is on the classpath, so unwrap
client = ((RetryableFeignBlockingLoadBalancerClient) client).getDelegate();
}
builder.client(client);
}
applyBuildCustomizers(context, builder);
Targeter targeter = get(context, Targeter.class);
return (T) targeter.target(this, builder, context, new HardCodedTarget<>(type, name, url));
}
①FeignContext:
FeignContext继承了NameContextFactory抽象类
它的功能主要是为每一个contextId创建一个独立的ApplicationContext
该对象在FeignAutoConfiguration配置类中将FeignContext注入到Spring容器中
② feign.ReflectiveFeign#newInstance:
@Override
public <T> T newInstance(Target<T> target) {
Map<String, MethodHandler> nameToHandler = targetToHandlersByName.apply(target);
Map<Method, MethodHandler> methodToHandler = new LinkedHashMap<Method, MethodHandler>();
List<DefaultMethodHandler> defaultMethodHandlers = new LinkedList<DefaultMethodHandler>();
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 {
methodToHandler.put(method, nameToHandler.get(Feign.configKey(target.type(), method)));
}
}
InvocationHandler handler = factory.create(target, methodToHandler);
T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(),
new Class<?>[] {target.type()}, handler);
for (DefaultMethodHandler defaultMethodHandler : defaultMethodHandlers) {
defaultMethodHandler.bindTo(proxy);
}
return proxy;
}
至此实例化结束
OpenFeign负载均衡源码分析
前面的创建和实例化都结束了,现在是使用过程
feign.SynchronousMethodHandler#invoke:
public Object invoke(Object[] argv) throws Throwable {
RequestTemplate template = buildTemplateFromArgs.create(argv);
Options options = findOptions(argv);
Retryer retryer = this.retryer.clone();
while (true) {
try {
// 核心代码 ①
return executeAndDecode(template, options);
} 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;
}
}
}
接着会去这个函数:feign.SynchronousMethodHandler#executeAndDecode
核心代码是:response = client.execute(request, options);
接着进入这里:org.springframework.cloud.openfeign.loadbalancer.FeignBlockingLoadBalancerClient#execute
public Response execute(Request request, Request.Options options) throws IOException {
final URI originalUri = URI.create(request.url());
String serviceId = originalUri.getHost();
Assert.state(serviceId != null, "Request URI does not contain a valid hostname: " + originalUri);
String hint = getHint(serviceId);
DefaultRequest<RequestDataContext> lbRequest = new DefaultRequest<>(
new RequestDataContext(buildRequestData(request), hint));
Set<LoadBalancerLifecycle> supportedLifecycleProcessors = LoadBalancerLifecycleValidator
.getSupportedLifecycleProcessors(
loadBalancerClientFactory.getInstances(serviceId, LoadBalancerLifecycle.class),
RequestDataContext.class, ResponseData.class, ServiceInstance.class);
supportedLifecycleProcessors.forEach(lifecycle -> lifecycle.onStart(lbRequest));
// 核心代码
ServiceInstance instance = loadBalancerClient.choose(serviceId, lbRequest);
// ...
}
进入服务选择函数:
@Override
public <T> ServiceInstance choose(String serviceId, Request<T> request) {
// 获取 loadbalancer策略 ①
ReactiveLoadBalancer<ServiceInstance> loadBalancer = loadBalancerClientFactory.getInstance(serviceId);
if (loadBalancer == null) {
return null;
}
// 核心代码 ②
Response<ServiceInstance> loadBalancerResponse = Mono.from(loadBalancer.choose(request)).block();
if (loadBalancerResponse == null) {
return null;
}
return loadBalancerResponse.getServer();
}
①:
最终发现负载均衡策略是RoundRobinLoadBalancer轮询
②org.springframework.cloud.loadbalancer.core.RoundRobinLoadBalancer#choose:
public Mono<Response<ServiceInstance>> choose(Request request) {
ServiceInstanceListSupplier supplier = serviceInstanceListSupplierProvider
.getIfAvailable(NoopServiceInstanceListSupplier::new);
return supplier.get(request).next()
.map(serviceInstances -> processInstanceResponse(supplier, serviceInstances));
}
熟悉的接口:org.springframework.cloud.loadbalancer.core.ReactorLoadBalancer
回到org.springframework.cloud.openfeign.loadbalancer.FeignBlockingLoadBalancerClient#execute:
@Override
public Response execute(Request request, Request.Options options) throws IOException {
final URI originalUri = URI.create(request.url());
String serviceId = originalUri.getHost();
String hint = getHint(serviceId);
DefaultRequest<RequestDataContext> lbRequest = new DefaultRequest<>(
new RequestDataContext(buildRequestData(request), hint));
Set<LoadBalancerLifecycle> supportedLifecycleProcessors = LoadBalancerLifecycleValidator
.getSupportedLifecycleProcessors(
loadBalancerClientFactory.getInstances(serviceId, LoadBalancerLifecycle.class),
RequestDataContext.class, ResponseData.class, ServiceInstance.class);
supportedLifecycleProcessors.forEach(lifecycle -> lifecycle.onStart(lbRequest));
// 负载均衡,从多个服务提供者列表中选取一个
ServiceInstance instance = loadBalancerClient.choose(serviceId, lbRequest);
org.springframework.cloud.client.loadbalancer.Response<ServiceInstance> lbResponse = new DefaultResponse(
instance);
// 有默认的负载均衡策略,所以该分支不走
if (instance == null) {
// ...
}
// 计算出目标 ip 地址 ①
String reconstructedUrl = loadBalancerClient.reconstructURI(instance, originalUri).toString();
// 创建请求
Request newRequest = buildRequest(request, reconstructedUrl);
LoadBalancerProperties loadBalancerProperties = loadBalancerClientFactory.getProperties(serviceId);
// 最后玩这里走,进行请求处理
return executeWithLoadBalancerLifecycleProcessing(delegate, options, newRequest, lbRequest, lbResponse,
supportedLifecycleProcessors, loadBalancerProperties.isUseRawStatusCodeInResponseData());
}
然后就是 ip 从哪里获得?
从负载均衡这行ServiceInstance instance = loadBalancerClient.choose(serviceId, lbRequest);获取了目标服务的IP地址
接着在String reconstructedUrl = loadBalancerClient.reconstructURI(instance, originalUri).toString();
①reconstructURI源码:
private static URI doReconstructURI(ServiceInstance serviceInstance, URI original) {
// host = 192.168.19.1 这是我们提供服务的服务器 ip 之一
String host = serviceInstance.getHost();
// scheme = http
String scheme = Optional.ofNullable(serviceInstance.getScheme())
.orElse(computeScheme(original, serviceInstance));
// port = 8002
int port = computePort(serviceInstance.getPort(), scheme);
if (Objects.equals(host, original.getHost()) && port == original.getPort()
&& Objects.equals(scheme, original.getScheme())) {
return original;
}
// false
boolean encoded = containsEncodedParts(original);
// 组合完毕:http://192.168.19.1:8002/payment/get/1
return UriComponentsBuilder.fromUri(original).scheme(scheme).host(host).port(port).build(encoded).toUri();
}
最终在这里处理请求:
static Response executeWithLoadBalancerLifecycleProcessing(Client feignClient, Request.Options options,
Request feignRequest, org.springframework.cloud.client.loadbalancer.Request lbRequest,
org.springframework.cloud.client.loadbalancer.Response<ServiceInstance> lbResponse,
Set<LoadBalancerLifecycle> supportedLifecycleProcessors, boolean loadBalanced, boolean useRawStatusCodes)
throws IOException {
supportedLifecycleProcessors.forEach(lifecycle -> lifecycle.onStartRequest(lbRequest, lbResponse));
// ...
}
至此,大体上的源码分析完毕