0. 环境
- nacos版本:1.4.1
- Spring Cloud : Hoxton.SR9(
没用2020.0.2版本后面说明
) - Spring Boot :2.4.4
- Spring Cloud alibaba: 2.2.5.RELEASE
- Spring Cloud openFeign 2.2.2.RELEASE
测试代码:github.com/hsfxuebao/s…
2020.0.X版本
开始的OpenFeign底层不再使用Ribbon了
1. FeignClient的bean注册过程
1.1 @EnableFeignClients
想要集成 Feign
客户端,需要我们通过注解 @EnableFeignClients
来开启。这个注解开启了FeignClient
的解析过程。这个注解的声明如下,它用到了一个@Import注解,我们知道Import是用来导入一个配置类的,接下来去看一下FeignClientsRegistrar
的定义:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(FeignClientsRegistrar.class)
public @interface EnableFeignClients {
}
复制代码
FeignClientsRegistrar实现了ImportBeanDefinitionRegistrar,它是一个动态注入bean的接口,Spring Boot启动的时候,会去调用这个类中的registerBeanDefinitions来实现动态Bean的装载。它的作用类似于ImportSelector。
然后就会进入 FeignClientsRegistrar# registerBeanDefinitions
。registerDefaultConfiguration
方法内部从 SpringBoot 启动类上检查是否有@EnableFeignClients
, 有该注解的话, 则完成 Feign 框架相关的一些配置内容注册registerFeignClients
方法内部从 classpath
中, 扫描获得 @FeignClient
修饰的类, 将类的内容解析为 BeanDefinition
, 最终通过调用 Spring 框架中的BeanDefinitionReaderUtils.resgisterBeanDefinition
将解析处理过的 FeignClientBeanDeifinition
添加到 spring 容器中.
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
// todo 将@EnableFeignClients注解中的defaultConfiguration属性注册到一个缓存map
registerDefaultConfiguration(metadata, registry);
// todo 1) 扫描所有@FeignClient注解的接口,即扫描到所有Feign接口
// 2) 将每个@FeignClient注解的configuration属性注册到一个缓存map
// 3) 根据@FeignClient注解元数据生成 FeignClientFactoryBean 的BeanDefinition,
// 并将这个BeanDefinition注册到一个缓存map
registerFeignClients(metadata, registry);
}
复制代码
registerDefaultConfiguration
:private void registerDefaultConfiguration(AnnotationMetadata metadata, BeanDefinitionRegistry registry) { // 获取到@EnableFeignClients注解的属性值 // 第二个参数true,表示将Class类型的属性变为了String类型 Map<String, Object> defaultAttrs = metadata .getAnnotationAttributes(EnableFeignClients.class.getName(), true); if (defaultAttrs != null && defaultAttrs.containsKey("defaultConfiguration")) { String name; // 判断当前注解所标注的类是否为闭合类 if (metadata.hasEnclosingClass()) { name = "default." + metadata.getEnclosingClassName(); } else { name = "default." + metadata.getClassName(); } // todo 注册这个defaultConfiguration属性 registerClientConfiguration(registry, name, defaultAttrs.get("defaultConfiguration")); } } private void registerClientConfiguration(BeanDefinitionRegistry registry, Object name, Object configuration) { // 生成一个Builder BeanDefinitionBuilder builder = BeanDefinitionBuilder .genericBeanDefinition(FeignClientSpecification.class); builder.addConstructorArgValue(name); builder.addConstructorArgValue(configuration); registry.registerBeanDefinition( name + "." + FeignClientSpecification.class.getSimpleName(), builder.getBeanDefinition()); // 获取到构建的BeanDefinition } 复制代码
registerFeignClients
public void registerFeignClients(AnnotationMetadata metadata, BeanDefinitionRegistry registry) { // 定义一个扫描器 ClassPathScanningCandidateComponentProvider scanner = getScanner(); // 初始化扫描器 scanner.setResourceLoader(this.resourceLoader); Set<String> basePackages; // 获取@EnableFeignClients注解的属性元数据 Map<String, Object> attrs = metadata .getAnnotationAttributes(EnableFeignClients.class.getName()); AnnotationTypeFilter annotationTypeFilter = new AnnotationTypeFilter( FeignClient.class); // 获取clients属性值 final Class<?>[] clients = attrs == null ? null : (Class<?>[]) attrs.get("clients"); // 若clients属性为空,则启用类路径扫描 if (clients == null || clients.length == 0) { // 为扫描器添加一个扫描@FeignClient注解的Filter scanner.addIncludeFilter(annotationTypeFilter); // todo 获取@EnableFeignClients注解中所有指定的基本包 basePackages = getBasePackages(metadata); } else { // 指定了clients属性,将所有指定的Feign接口类添加到集合 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))); } // 遍历这些基本包,将其它的@FeignClient接口找到并写入到集合 for (String basePackage : basePackages) { Set<BeanDefinition> candidateComponents = scanner .findCandidateComponents(basePackage); // 遍历 候选组件 集合(所有Feign接口都在这个集合中) for (BeanDefinition candidateComponent : candidateComponents) { // 只处理Feign接口组件 if (candidateComponent instanceof AnnotatedBeanDefinition) { // verify annotated class is an interface AnnotatedBeanDefinition beanDefinition = (AnnotatedBeanDefinition) candidateComponent; // 从BeanDefinition中获取注解元数据 AnnotationMetadata annotationMetadata = beanDefinition.getMetadata(); // 断言:若不是接口,直接抛出异常 Assert.isTrue(annotationMetadata.isInterface(), "@FeignClient can only be specified on an interface"); // 获取@FeignClient注解属性 Map<String, Object> attributes = annotationMetadata .getAnnotationAttributes( FeignClient.class.getCanonicalName()); // todo 获取Feign名称 String name = getClientName(attributes); // 将当前遍历的Feign接口的configuration注册到缓存map registerClientConfiguration(registry, name, attributes.get("configuration")); // 将FeignClientFactoryBean的BeanDefinition注册到缓存map registerFeignClient(registry, annotationMetadata, attributes); } } } } 复制代码
registerFeignClient
在这个方法中,就是去组装BeanDefinition,也就是Bean的定义,然后注册到Spring IOC
容器。
我们关注一下,BeanDefinitionBuilder是用来构建一个BeanDefinition的,它是通过private void registerFeignClient(BeanDefinitionRegistry registry, AnnotationMetadata annotationMetadata, Map<String, Object> attributes) { // 获取FeignClient接口的类全路径 String className = annotationMetadata.getClassName(); // 生成一个beanFactory,其会为FeignClientFactoryBean生成一些必要的组件 BeanDefinitionBuilder definition = BeanDefinitionBuilder .genericBeanDefinition(FeignClientFactoryBean.class); validate(attributes); 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"; // 获取到FeignClientFactoryBean的beanDefinition 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; } // 生成beanDefinition的holder,通过holder可以获取到这个beanDefinition BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className, new String[] { alias }); // 把BeanDefinition的这个bean定义注册到IOC容器 BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry); } 复制代码
genericBeanDefinition
来构建的,并且传入了一个FeignClientFactoryBean的类,代码如下。public static BeanDefinitionBuilder genericBeanDefinition(Class<?> beanClass) { BeanDefinitionBuilder builder = new BeanDefinitionBuilder(new GenericBeanDefinition()); builder.beanDefinition.setBeanClass(beanClass); return builder; } 复制代码
1.2 小节
综上代码分析,其实实现逻辑很简单。
- 创建一个
BeanDefinitionBuilder
。 - 创建一个工厂
Bean
,并把从@FeignClient
注解中解析的属性设置到这个FactoryBean
中 - 调用
registerBeanDefinition
注册到IOC容器中
2. 动态代理过程详解
我们可以发现,FeignClient
被动态注册成了一个FactoryBean
.
Spring Cloud FengnClient实际上是利用Spring的代理工厂来生成代理类,所以在这里地方才会把所有的FeignClient的BeanDefinition设置为FeignClientFactoryBean类型,而FeignClientFactoryBean继承自FactoryBean,它是一个工厂Bean。在Spring中,FactoryBean是一个工厂Bean,用来创建代理Bean。工厂 Bean 是一种特殊的 Bean, 对于 Bean 的消费者来说, 他逻辑上是感知不到这个 Bean 是普通的 Bean 还是工厂 Bean, 只是按照正常的获取 Bean 方式去调用, 但工厂bean 最后返回的实例不是工厂Bean 本身, 而是执行工厂 Bean 的 getObject 逻辑返回的示例。
简单来说,FeignClient
标注的这个接口,会通过FeignClientFactoryBean.getObject()
这个方法获得一个代理对象。
2.1 FeignClientFactoryBean.getObject
getObject
调用的是getTarget方法,它从applicationContext取出FeignContext
,FeignContext继承了NamedContextFactory
,它是用来统一维护feign中各个feign客户端相互隔离的上下文
。
springboot启动时会加载类路径下/META-INF/spring.factories
中key为org.springframework.boot.autoconfigure.EnableAutoConfiguration
的类,FeignContext
注册到容器是在FeignAutoConfiguration
上完成的。FeignContext
是全局唯一的上下文,它继承了NamedContextFactory
,它是用来统一维护feign中各个feign客户端相互隔离的上下文
。
@Autowired(required = false)
private List<FeignClientSpecification> configurations = new ArrayList<>();
@Bean
public FeignContext feignContext() {
FeignContext context = new FeignContext();
context.setConfigurations(this.configurations);
return context;
}
复制代码
在初始化FeignContext
时,会把configurations
在容器中放入FeignContext
中。configurations 的来源就是在前面registerFeignClients
方法中将@FeignClient
的配置 configuration。
接着,构建feign.builder
,在构建时会向FeignContext获取配置的Encoder,Decoder等各种信息。FeignContext在上文中已经提到会为每个Feign客户端分配了一个容器,它们的父容器就是spring容器。
配置完Feign.Builder之后,再判断是否需要LoadBalance
,如果需要,则通过LoadBalance
的方法来设置。实际上他们最终调用的是Target.target()
方法。
@Override
public Object getObject() throws Exception {
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() {
// 实例化Feign上下文对象FeignContext
FeignContext context = this.applicationContext.getBean(FeignContext.class);
// todo 从Spring子容器中获取相应的实例
Feign.Builder builder = feign(context);
// 若url属性为空,则说明其要采用负载均衡方式调用提供者,生成有负载均衡功能的代理类
if (!StringUtils.hasText(this.url)) {
// 若name属性不以http开头
if (!this.name.startsWith("http")) {
this.url = "http://" + this.name;
}
else {
this.url = this.name;
}
// 将规范化的path属性连接到url后
// 规范化path:若path属性不以斜杠(/)开头,则为其添加斜杠,以斜杠结尾,则去掉尾部斜杠
this.url += cleanPath();
// 负载均衡调用
return (T) loadBalance(builder, context,
new HardCodedTarget<>(this.type, this.name, this.url));
}
// 这是url不为空的情况,即采用直连方式访问提供者,则生成默认的代理类
if (StringUtils.hasText(this.url) && !this.url.startsWith("http")) {
this.url = "http://" + this.url;
}
String url = this.url + cleanPath();
// 从Spring子容器中获取Client
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();
}
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();
}
builder.client(client);
}
// todo 生成默认的代理类
Targeter targeter = get(context, Targeter.class);
return (T) targeter.target(this, builder, context,
new HardCodedTarget<>(this.type, this.name, url));
}
复制代码
Feign.Builder
用来构建Feign对象,基于builder实现上下文信息的构建,代码如下。
protected Feign.Builder feign(FeignContext context) {
// todo
FeignLoggerFactory loggerFactory = get(context, FeignLoggerFactory.class);
Logger logger = loggerFactory.create(type);
// @formatter:off
Feign.Builder builder = get(context, Feign.Builder.class)
// required values
.logger(logger)
.encoder(get(context, Encoder.class))
.decoder(get(context, Decoder.class))
.contract(get(context, Contract.class)); //contract协议,用来实现模版解析(后面再详细分析)
// @formatter:on
configureFeign(context, builder);
applyBuildCustomizers(context, builder);
return builder;
}
复制代码
从代码中可以看到,feign
方法,主要是针对不同的服务提供者生成Feign的上下文信息,比如logger
、encoder
、decoder
等。因此,从这个分析过程中,我们不难猜测到它的原理结构,如下图所示:
父子容器隔离的实现方式如下
,当调用get方法时,会从context
中去获取指定type的实例对象。
//FeignContext.java
protected <T> T get(FeignContext context, Class<T> type) {
T instance = context.getInstance(contextId, type);
if (instance == null) {
throw new IllegalStateException(
"No bean found of type " + type + " for " + contextId);
}
return instance;
}
复制代码
接着,调用NamedContextFactory
中的getInstance
方法:
//NamedContextFactory.java
public <T> T getInstance(String name, Class<T> type) {
//根据`name`获取容器上下文
AnnotationConfigApplicationContext context = this.getContext(name);
try {
//再从容器上下文中获取指定类型的bean。
return context.getBean(type);
} catch (NoSuchBeanDefinitionException var5) {
return null;
}
}
复制代码
getContext
方法根据name
从contexts
容器中获得上下文对象,如果没有,则调用createContext
创建。
protected AnnotationConfigApplicationContext getContext(String name) {
if (!this.contexts.containsKey(name)) {
synchronized(this.contexts) {
if (!this.contexts.containsKey(name)) {
this.contexts.put(name, this.createContext(name));
}
}
}
return (AnnotationConfigApplicationContext)this.contexts.get(name);
}
复制代码
2.2 生成动态代理
loadBalance
:生成具备负载均衡能力的feign客户端,为feign客户端构建起绑定负载均衡客户端。
protected <T> T loadBalance(Feign.Builder builder, FeignContext context,
HardCodedTarget<T> target) {
//Feign发送请求以及接受响应的http client,默认是Client.Default的实现,可以修改成OkHttp、HttpClient等。
Client client = getOptional(context, Client.class);
if (client != null) {
builder.client(client);//针对当前Feign客户端,设置网络通信的client
//targeter表示HystrixTarger实例,因为Feign可以集成Hystrix实现熔断,所以这里会一层包装。
Targeter targeter = get(context, Targeter.class);
return targeter.target(this, builder, context, target);
}
throw new IllegalStateException(
"No Feign Client for loadBalancing defined. Did you forget to include spring-cloud-starter-netflix-ribbon?");
}
复制代码
Client client = (Client)this.getOptional(context, Client.class); 从上下文中获取一个 Client
,默认是LoadBalancerFeignClient
。它是在FeignRibbonClientAutoConfiguration
这个自动装配类中,通过Import
实现的
@Import({ HttpClientFeignLoadBalancedConfiguration.class,
OkHttpFeignLoadBalancedConfiguration.class,
DefaultFeignLoadBalancedConfiguration.class })
public class FeignRibbonClientAutoConfiguration {
.....
}
复制代码
这里的通过 DefaultFeignLoadBalancedConfiguration
注入客户端 Client
的实现
@Configuration(proxyBeanMethods = false)
class DefaultFeignLoadBalancedConfiguration {
@Bean
@ConditionalOnMissingBean
public Client feignClient(CachingSpringLoadBalancerFactory cachingFactory,
SpringClientFactory clientFactory) {
return new LoadBalancerFeignClient(new Client.Default(null, null), cachingFactory,
clientFactory);
}
}
复制代码
接下去进入 targeter.target(this, builder, context, target)
,携带着构建好的这些对象去创建代理实例 ,这里有两个实现 HystrixTargeter 、DefaultTargeter
很显然,我们没有配置 Hystrix ,这里会走 DefaultTargeter
class DefaultTargeter implements Targeter {
@Override
public <T> T target(FeignClientFactoryBean factory, Feign.Builder feign,
FeignContext context, Target.HardCodedTarget<T> target) {
return feign.target(target);
}
}
复制代码
然后会来到 feign.Feign.Builder#target(feign.Target)
public <T> T target(Target<T> target) {
return build().newInstance(target);
}
public Feign build() {
SynchronousMethodHandler.Factory synchronousMethodHandlerFactory =
new SynchronousMethodHandler.Factory(client, retryer, requestInterceptors, logger,
logLevel, decode404, closeAfterDecode, propagationPolicy);
ParseHandlersByName handlersByName =
new ParseHandlersByName(contract, options, encoder, decoder, queryMapEncoder,
errorDecoder, synchronousMethodHandlerFactory);
return new ReflectiveFeign(handlersByName, invocationHandlerFactory, queryMapEncoder);
}
复制代码
最终会调用 ReflectiveFeign.newInstance
这个方法是用来创建一个动态代理的方法,在生成动态代理之前,会根据Contract协议(协议解析规则,解析接口类的注解信息,解析成内部的MethodHandler的处理方式。
从实现的代码中可以看到熟悉的Proxy.newProxyInstance
方法产生代理类。而这里需要对每个定义的接口方法进行特定的处理实现,所以这里会出现一个MethodHandler
的概念,就是对应方法级别的InvocationHandler
。
public <T> T newInstance(Target<T> target) {
// 解析接口注解信息
//根据接口类和Contract协议解析方式,解析接口类上的方法和注解,转换成内部的MethodHandler处理方式
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);
// 基于Proxy.newProxyInstance 为接口类创建动态实现,将所有的请求转换给InvocationHandler 处理。
T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(),
new Class<?>[] {target.type()}, handler);
for (DefaultMethodHandler defaultMethodHandler : defaultMethodHandlers) {
defaultMethodHandler.bindTo(proxy);
}
return proxy;
}
复制代码
targetToHandlersByName.apply(target)
:根据Contract协议规则,解析接口类的注解信息,解析成内部表现:targetToHandlersByName.apply(target);
会解析接口方法上的注解,从而解析出方法粒度的特定的配置信息,然后生产一个SynchronousMethodHandler
然后需要维护一个<method,MethodHandler>
的map,放入InvocationHandler
的实现FeignInvocationHandler
中。
public Map<String, MethodHandler> apply(Target target) {
List<MethodMetadata> metadata = contract.parseAndValidateMetadata(target.type());
Map<String, MethodHandler> result = new LinkedHashMap<String, MethodHandler>();
for (MethodMetadata md : metadata) {
BuildTemplateByResolvingArgs buildTemplate;
if (!md.formParams().isEmpty() && md.template().bodyTemplate() == null) {
buildTemplate =
new BuildFormEncodedTemplateFromArgs(md, encoder, queryMapEncoder, target);
} else if (md.bodyIndex() != null) {
buildTemplate = new BuildEncodedTemplateFromArgs(md, encoder, queryMapEncoder, target);
} else {
buildTemplate = new BuildTemplateByResolvingArgs(md, queryMapEncoder, target);
}
if (md.isIgnored()) {
result.put(md.configKey(), args -> {
throw new IllegalStateException(md.configKey() + " is not a method handled by feign");
});
} else {
result.put(md.configKey(),
factory.create(target, md, buildTemplate, options, decoder, errorDecoder));
}
}
return result;
}
复制代码
2.3 小节
上述代码,其实也不难理解。
-
解析@FeignClient接口声明的方法,根据不同方法绑定不同的处理器。
- 默认方法,绑定
DefaultMethodHandler
- 远程方法,绑定
SynchronousMethodHandler
- 默认方法,绑定
-
使用JDK提供的Proxy创建动态代理
MethodHandler
,会把方法参数、方法返回值、参数集合、请求类型、请求路径进行解析存储,如下图所示。
-
被声明为
@FeignClient
注解的类,在被注入时,最终会生成一个动态代理对象FeignInvocationHandler
。当触发方法调用时,会被
FeignInvocationHandler#invoke
拦截,FeignClientFactoryBean
在实例化过程中所做的事情如下图所示。
总结来说就几个点:
- 解析Feign的上下文配置,针对当前的服务实例构建容器上下文并返回Feign对象
- Feign根据上下围配置把 log、encode、decoder、等配置项设置到Feign对象中
- 对目标服务,使用LoadBalance以及Hystrix进行包装
- 通过Contract协议,把FeignClient接口的声明,解析成MethodHandler
- 遍历MethodHandler列表,针对需要远程通信的方法,设置
SynchronousMethodHandler
处理器,用来实现同步远程调用。 - 使用JDK中的动态代理机制构建动态代理对象。
3. OpenFeign调用过程
OpenFeign调用过程图示:
在前面的分析中,我们知道OpenFeign最终返回的是一个 ReflectiveFeign.FeignInvocationHandler
的对象。那么当客户端发起请求时,会进入到 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();
}
// todo 利用分发器筛选方法,找到对应的handler 进行处理
return dispatch.get(method).invoke(args);
}
复制代码
而接着,在invoke方法中,会调用 this.dispatch.get(method)).invoke(args)
。this.dispatch.get(method)
会返回一个SynchronousMethodHandler
,进行拦截处理。这个方法会根据参数生成完成的RequestTemplate
对象,这个对象是Http
请求的模版,代码如下。
@Override
public Object invoke(Object[] argv) throws Throwable {
RequestTemplate template = buildTemplateFromArgs.create(argv);
Options options = findOptions(argv);
Retryer retryer = this.retryer.clone();
while (true) {
try {
// todo
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;
}
}
}
复制代码
经过上述的代码,我们已经将restTemplate
拼装完成,上面的代码中有一个 executeAndDecode()
方法,该方法通过RequestTemplate
生成Request请求对象
,然后利用Http Client获取response
,来获取响应信息。
Object executeAndDecode(RequestTemplate template, Options options) throws Throwable {
//转化为Http请求报文
Request request = targetRequest(template);
if (logLevel != Logger.Level.NONE) {
logger.logRequest(metadata.configKey(), logLevel, request);
}
Response response;
long start = System.nanoTime();
try {
//发起远程通信
response = client.execute(request, options);
// ensure the request is set. TODO: remove in Feign 12
//获取返回结果
response = response.toBuilder()
.request(request)
.requestTemplate(template)
.build();
} catch (IOException e) {
// .......
}
复制代码
经过上面的分析,这里的 client.execute
的 client
的类型是LoadBalancerFeignClient
,这里就很自然的进入 LoadBalancerFeignClient#execute
。
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);
FeignLoadBalancer.RibbonRequest ribbonRequest = new FeignLoadBalancer.RibbonRequest(
this.delegate, request, uriWithoutHost);
IClientConfig requestConfig = getClientConfig(options, clientName);
return lbClient(clientName)
.executeWithLoadBalancer(ribbonRequest, requestConfig).toResponse();
}
catch (ClientException e) {
IOException io = findIOException(e);
if (io != null) {
throw io;
}
throw new RuntimeException(e);
}
}
复制代码
其实这个execute
里面得流程就是 Ribbon
的那一套。我们可以简单的看一下。首先是构造URI,构造RibbonRequest
,选择 LoadBalance
,发起调用。
来看一下lbClient
选择负载均衡器的时候做了什么
public FeignLoadBalancer create(String clientName) {
FeignLoadBalancer client = this.cache.get(clientName);
if (client != null) {
return client;
}
IClientConfig config = this.factory.getClientConfig(clientName);
ILoadBalancer lb = this.factory.getLoadBalancer(clientName);
ServerIntrospector serverIntrospector = this.factory.getInstance(clientName,
ServerIntrospector.class);
client = this.loadBalancedRetryFactory != null
? new RetryableFeignLoadBalancer(lb, config, serverIntrospector,
this.loadBalancedRetryFactory)
: new FeignLoadBalancer(lb, config, serverIntrospector);
this.cache.put(clientName, client);
return client;
}
复制代码
可以得出的结论就是 this.factory.getLoadBalancer(clientName)
跟Ribbon
源码里的获取方式一样,无疑这里获取的就是默认的 ZoneAwareLoadBalancer
。然后包装成一个 FeignLoadBalancer
进行返回
既然负载均衡器选择完了,那么一定还有个地方通过该负载去选择一个服务,接着往下看:
public T executeWithLoadBalancer(final S request, final IClientConfig requestConfig) throws ClientException {
LoadBalancerCommand<T> command = buildLoadBalancerCommand(request, requestConfig);
try {
return command.submit(
new ServerOperation<T>() {
@Override
public Observable<T> call(Server server) {
URI finalUri = reconstructURIWithServer(server, request.getUri());
S requestForServer = (S) request.replaceUri(finalUri);
try {
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);
}
}
}
复制代码
上面这段代码就是通过获取到的负载进行执行请求,但是这个时候 服务还没有选择,我们跟进去 submit
请求看一看究竟:
public Observable<T> submit(final ServerOperation<T> operation) {
final ExecutionInfoContext context = new ExecutionInfoContext();
// .........
Observable<T> o =
(server == null ? selectServer() : Observable.just(server))
.concatMap(new Func1<Server, Observable<T>>() {
//........
});
// .......
}
复制代码
可以看到这里有个 selectServer
的方法 ,跟进去:
public Server getServerFromLoadBalancer(@Nullable URI original, @Nullable Object loadBalancerKey) throws ClientException {
String host = null;
int port = -1;
if (original != null) {
host = original.getHost();
}
if (original != null) {
Pair<String, Integer> schemeAndPort = deriveSchemeAndPortFromPartialUri(original);
port = schemeAndPort.second();
}
// Various Supported Cases
// The loadbalancer to use and the instances it has is based on how it was registered
// In each of these cases, the client might come in using Full Url or Partial URL
ILoadBalancer lb = getLoadBalancer();
if (host == null) {
// ............
} else {
// ...........if (shouldInterpretAsVip) {
Server svc = lb.chooseServer(loadBalancerKey);
if (svc != null){
host = svc.getHost();
if (host == null){
throw new ClientException(ClientException.ErrorType.GENERAL,
"Invalid Server for :" + svc);
}
logger.debug("using LB returned Server: {} for request: {}", svc, original);
return svc;
} else {
// just fall back as real DNS
logger.debug("{}:{} assumed to be a valid VIP address or exists in the DNS", host, port);
}
} else {
// consult LB to obtain vipAddress backed instance given full URL
//Full URL execute request - where url!=vipAddress
logger.debug("Using full URL passed in by caller (not using load balancer): {}", original);
}
}
// ..........
return new Server(host, port);
}
复制代码
可以看到的是这里获取到了之前构造好的 ZoneAwareLoadBalancer
然后调用 chooseServer
方法获取server
,这个是跟Ribbon
中是一样的流程,这里就不赘述了。
获取到了server 后,会回调先前 executeWithLoadBalancer
方法里构造的 ServerOperation
的 call 方法:
return command.submit(
new ServerOperation<T>() {
@Override
public Observable<T> call(Server server) {
URI finalUri = reconstructURIWithServer(server, request.getUri());
S requestForServer = (S) request.replaceUri(finalUri);
try {
return Observable.just(AbstractLoadBalancerAwareClient.this.execute(requestForServer, requestConfig));
}
catch (Exception e) {
return Observable.error(e);
}
}
})
.toBlocking()
.single();
复制代码
然后会执行 AbstractLoadBalancerAwareClient.this.execute(requestForServer, requestConfig)
进行最后的调用,实际上这里走的是 FeignLoadBalancer#execute
@Override
public RibbonResponse execute(RibbonRequest request, IClientConfig configOverride)
throws IOException {
Request.Options options;
if (configOverride != null) {
RibbonProperties override = RibbonProperties.from(configOverride);
options = new Request.Options(override.connectTimeout(this.connectTimeout),
override.readTimeout(this.readTimeout));
}
else {
options = new Request.Options(this.connectTimeout, this.readTimeout);
}
Response response = request.client().execute(request.toRequest(), options);
return new RibbonResponse(request.getUri(), response);
}
复制代码
而这里调用的 request.client().execute(request.toRequest(), options)
则是 DefaultFeignLoadBalancedConfiguration
注入的 LoadBalancerFeignClient
,在构造 LoadBalancerFeignClient
的时候 ,传递了个 feign.Client.Default
,然后利用 feign.Client.Default
构造了一个 RibbonRequest
。
所以这里走 feign.Client.Default#execute
:
@Override
public Response execute(Request request, Options options) throws IOException {
HttpURLConnection connection = convertAndSend(request, options);
return convertResponse(connection, request);
}
复制代码
利用 JDK 提供的 HttpURLConnection
发起远程的 HTTP
通讯。至此发起请求的流程就完成了。下面附上一张这个过程的流程图,对于Ribbon的调用过程请参考 :Ribbon 源码分析。
4. 总结
Spring Cloud OpenFeign 的核心工作原理经上文探究可以非常简单的总结为:
- 通过
@EnableFeignCleints
触发 Spring 应用程序对classpath
中@FeignClient
修饰类的扫描 - 解析到
@FeignClient
修饰类后,Feign
框架通过扩展Spring Bean Deifinition
的注册逻辑, 最终注册一个FeignClientFacotoryBean
进入 Spring 容器 - Spring 容器在初始化其他用到
@FeignClient
接口的类时, 获得的是FeignClientFacotryBean
产生的一个代理对象Proxy.
- 基于 java 原生的
动态代理机制
, 针对 Proxy 的调用, 都会被统一转发给 Feign 框架所定义的一个InvocationHandler
, 由该Handler
完成后续的HTTP 转换, 发送, 接收
, 翻译HTTP响应的工作。