前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >SpringCloudRPC远程调用核心原理:FeignRPC动态代理实例创建流程

SpringCloudRPC远程调用核心原理:FeignRPC动态代理实例创建流程

作者头像
愿天堂没有BUG
发布2022-10-31 11:29:19
5680
发布2022-10-31 11:29:19
举报

Feign的RPC动态代理实例的创建流程

在介绍Feign远程代理实例的创建流程之前,先总结一下Feign整体执行流程。

Feign的整体运作流程

首先回顾一下Feign的整体运作流程。Feign英文直译为假装/装作,也就是说Feign是一个伪客户端,即它不做任何的HTTP请求处理。

在应用启动的初始化过程中,Feign完成了以下两项工作:

(1)对于每一个RPC远程调用Java接口,Feign根据@FeignClient注解生成本地JDK动态代理实例。

(2)对于Java接口中的每一个RPC远程调用方法,Feign首先根据Spring MVC(如@GetMapping)类型注解生成方法处理器实例,该实例内部包含一个请求模板RequestTemplate实例。

在远程调用REST请求执行的过程中,Feign完成了以下两项工作:

(1)Feign使用远程方法调用的实际参数替换掉RequestTemplate模板实例中的参数,生成最终的HTTP请求。

(2)将HTTP请求通过feign.Client客户端实例发送到Provider服务端。

总之,Feign根据注解生成动态代理RPC客户端实例和HTTPRequest请求,大大简化了HTTP远程API的调用。

使用Feign进行开发,开发人员既可以使用注解的方式定制本地JDK动态代理实例,又可以通过注解的方式调整Request请求模板,结合起来,使得整个远程RPC调用的工作变得非常轻松和容易。

总体来说,Feign整体运行流程大致如下:

(1)通过应用启动类上的@EnableFeignClients注解开启Feign的装配和远程代理实例创建。

在@EnableFeignClients注解源码中可以看到导入了FeignClientsRegistrar类,该类用于扫描@FeignClient注解过的RPC接口。

(2)通过对@FeignClient注解RPC接口扫描创建远程调用的动态代理实例。

FeignClientsRegistrar类会进行包扫描,扫描所有包下@FeignClient注解过的接口,创建RPC接口的FactoryBean工厂类实例,并将这些FactoryBean注入Spring IOC容器中。

如果应用某些地方需要注入RPC接口的实例(比如被@Resource引用),Spring就会通过注册的FactoryBean工厂类实例的getObject()方法获取RPC接口的动态代理实例。

在创建RPC接口的动态代理实例时,Feign会为每一个RPC接口创建一个调用处理器,也会为接口的每一个RPC方法创建一个方法处理器,并且将方法处理器缓存在调用处理器的dispatch映射成员中。

在创建动态代理实例时,Feign也会通过RPC方法的注解为每一个RPC方法生成一个RequesTemplate请求模板实例,RequestTemplate中包含请求的所有信息,如请求URL、请求类型(如GET)、请求参数等。

(3)发生RPC调用时,通过动态代理实例类完成远程Provider的HTTP调用。当动态代理实例类的方法被调用时,Feign会根据RPC方法的反射实例从调用处理器的dispatch成员中取得方法处理器,然后由MethodHandler方法处理器开始HTTP请求处理。

MethodHandler会结合实际的调用参数,通过RequesTemplate模板实例生成Request请求实例。最后,将Request请求实例交给feign.Client客户端实例进一步完成HTTP请求处理。

(4)在完成远程HTTP调用前需要进行客户端负载均衡的处理。

在Spring Cloud微服务架构中,同一个Provider微服务一般都会运行多个实例,所以说客户端的负载均衡能力其实是必选项,而不是可选项。

生产环境下,Feign必须和Ribbon结合在一起使用,所以方法处理器MethodHandler的客户端client成员必须是具备负载均衡能力的LoadBalancerFeignClient类型,而不是完成HTTP请求提交的ApacheHttpClient等类型。只有在负载均衡计算出最佳的Provider实例之后,才能开始HTTP请求的提交。

在LoadBalancerFeignClient内部有一个delegate委托成员,其类型可能为feign.client.Default、ApacheHttpClient、OkHttpClient等,最终由该delegate客户端委托成员完成HTTP请求的提交。

至此,整体的Feign运作流程大家应该都比较熟悉了。其实,上面介绍的大致逻辑和前面介绍的模拟Feign RPC执行流程类似,只是Feign实际的运作流程的每一个环节更加细致和复杂。

RPC动态代理容器实例的FactoryBean工厂类

为了方便Feign的RPC客户端动态代理实例的使用,还需要将其注册到Spring IOC容器,以方便使用者通过@Resource或@Autoware注解将其注入其他的依赖属性。

一般情况下,Spring通过@Service等注解进行Bean实例化的配置,但是在某些情况下(比如在Bean实例化时)需要大量的配置信息,默认的Bean实例化机制是无能为力的。为此,Spring提供了一个 org.springframework.bean.factory.FactoryBean工厂接口,用户可以通过该接口在Java代码中实现定制Bean实例化的逻辑。

FactoryBean在Spring框架中占用重要的地位,Spring自身就提供了70多个FactoryBean的实现。它们隐藏了一些复杂Bean实例化的细节,给上层应用带来了便利。FactoryBean注册到容器之后,从Spring上下文通过ID或者类型获取IOC容器Bean时,获取的实际上是FactoryBean的getObject()返回的对象,而不是FactoryBean本身。

Feign的RPC客户端动态代理IOC容器实例只能通过FactoryBean方式创建,原因有两点:代理对象为通过JDK反射机制动态创建的Bean,不是直接定义的普通实现类;它配置的属性值比较多,而且是通过@FeignClient注解配置完成的。

所以,Feign提供了一个用于获取RPC容器实例的工厂类,名为FeignClientFactoryBean类。工厂类FeignClientFactoryBean的部分源码如下:

代码语言:javascript
复制
package org.springframework.cloud.openfeign;
...
class FeignClientFactoryBean implements FactoryBean<Object>, InitializingBean, ApplicationContextAware {
 private Class<?> type; //RPC接口的class对象
 private String name; //RPC接口配置的远程provider微服务名称,如demo-provider
 private String url; //RPC接口配置的url值,由 @FeignClient注解负责配置
 private String path; //RPC接口配置的path值,由 @FeignClient注解负责配置
 private boolean decode404;
 private ApplicationContext applicationContext;
 private Class<?> fallback;
 private Class<?> fallbackFactory;
 ...
 //获取IOC容器的Feign.Builder建造者Bean
 protected Builder feign(FeignContext context) {
 FeignLoggerFactory loggerFactory = this.get(context, FeignLoggerFactory.class);
 Logger logger = loggerFactory.create(this.type);
 //从IOC容器获取Feign.Builder实例
//并且设置编码器、解码器、日志器、方法解析器
 Builder builder = ((Builder)this.get(context, Builder.class))
 .logger(logger)
 .encoder((Encoder)this.get(context, Encoder.class))
 .decoder((Decoder)this.get(context, Decoder.class))
 .contract((Contract)this.get(context,Contract.class));
 this.configureFeign(context, builder);
 return builder;
 }
 //通过ID或者类型获取IOC容器Bean时调用
 public Object getObject() throws Exception {
 //委托到 getTarget 方法
 return this.getTarget();
 }
 //委托方法:获取RPC动态代理Bean
 <T> T getTarget() {
 FeignContext context = (FeignContext)this.applicationContext.
getBean(FeignContext.class);
 //获取Feign.Builder建造者实例
 Builder builder = this.feign(context);
 String url;
 ...
 }
 ...
}

前面讲到,FeignClientsRegistrar类会进行包扫描,扫描所有包下@FeignClient注解过的接口,并创建RPC接口的FactoryBean工厂类实例,并将这些FactoryBean注入Spring IOC容器中。

FeignClientsRegistrar类的RPC接口的FactoryBean工厂类实例的注册源码节选如下:

代码语言:javascript
复制
class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar, ... {
 ...
 //为每一个RPC客户端接口注册一个beanDefinition,其beanClass为FeignClientFactoryBean
 private void registerFeignClient(BeanDefinitionRegistry registry,
 AnnotationMetadata annotationMetadata,
 Map<String, Object> attributes) {
 String className = annotationMetadata.getClassName();
 BeanDefinitionBuilder definition =
 BeanDefinitionBuilder.genericBeanDefinition(FeignClientFactoryBean.class);
 this.validate(attributes);
 //RPC接口配置的url值
 definition.addPropertyValue("url", this.getUrl(attributes));
 //RPC接口配置的path值
 definition.addPropertyValue("path", this.getPath(attributes));
 String name = this.getName(attributes);
 definition.addPropertyValue("name", name); //RPC接口配置的远程provider名称
 definition.addPropertyValue("type", className); //RPC接口的全路径类名
 definition.addPropertyValue("decode404", attributes.get("decode404"));
 definition.addPropertyValue("fallback", attributes.get("fallback"));
 definition.addPropertyValue("fallbackFactory", attributes.get("fallbackFactory"));
 definition.setAutowireMode(2);
 //别名
 String alias = name + "FeignClient";
 AbstractBeanDefinition beanDefinition = definition.getBeanDefinition();
 //RPC接口配置的primary值
 boolean primary = (Boolean)attributes.get("primary");
 beanDefinition.setPrimary(primary);
 String qualifier = this.getQualifier(attributes);
 if (StringUtils.hasText(qualifier)) {
 alias = qualifier;
 }
 BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className, new String[]{alias});
 BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
 }
}

FeignClientsRegistrar类的registerFeignClient()方法为扫描到的每一个RPC客户端接口注册一个beanDefinition实例(Bean的),其中的beanClass为FeignClientFactoryBean。

registerFeignClient()方法的attributes参数值来自于RPC客户端接口

@FeignClient注解所配置的值,在该方法上设置断点,在uaa-provider启动时可以看到的attributes参数的具体信息如图3-19所示。

图3-19 registerFeignClient()方法的attributes参数值

Feign.Builder建造者容器实例

当从Spring IOC容器获取RPC接口的动态代理实例时,也就是当FeignClientFactoryBean的getObject()方法被调用时,其调用的getTarget()方法首先从IOC容器获取配置好的Feign.Builder建造者容器实例,然后通过Feign.Builder建造者容器实例的target()方法完成RPC动态代理实例的创建。

说明

这里将Builder翻译为建造者,以便同构造器进行区分。

Feign.Builder建造者容器实例在自动配置类

FeignClientsConfiguration中完成配置,通过其源码可以看到,配置类的feignBuilder(...)方法通过调用Feign.builder()静态方法创建了一个建造者容器实例。

自动配置类FeignClientsConfiguration的部分源码如下:

代码语言:javascript
复制
package org.springframework.cloud.openfeign;
//省略import
//Feign客户端的配置类
@Configuration
public class FeignClientsConfiguration {
 //容器实例:请求结果解码器
 @Bean
 @ConditionalOnMissingBean
 public Decoder feignDecoder() {
 return new OptionalDecoder(new ResponseEntityDecoder(
new SpringDecoder(this.messageConverters)));
}
 //容器实例:请求编码器
 @Bean
 @ConditionalOnMissingBean
 public Encoder feignEncoder() {
 return new SpringEncoder(this.messageConverters);
 }
 //容器实例:请求重试实例,如果没有定制,就默认返回NEVER_RETRY(不重试)实例
 @Bean
 @ConditionalOnMissingBean
 public Retryer feignRetryer() {
 return Retryer.NEVER_RETRY;
} //容器实例:Feign.Builder客户端建造者实例,以“请求重试实例”作为参数进行初始化
 @Bean
 @Scope("prototype")
 @ConditionalOnMissingBean
 public Builder feignBuilder(Retryer retryer) {
 return Feign.builder().retryer(retryer);
 }
...
}

Feign.Builder类是feign.Feign抽象类的一个内部类,作为Feign默认的建造者。Feign.Builder类的部分源码如下:

代码语言:javascript
复制
package feign;
...
public abstract class Feign {
 ...
 //建造者方法
 public static Builder builder() {
 return new Builder();
 }
 //内部类:建造者类
 public static class Builder {
...
//创建RPC客户端的动态代理实例
 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);
 //RPC方法解析器
 ParseHandlersByName handlersByName = new ParseHandlersByName
(contract, options, encoder, decoder, errorDecoder, synchronousMethodHandlerFactory);
 //反射式Feign实例
 return new ReflectiveFeign(handlersByName, invocationHandlerFactory);
 }
 }

当FeignClientFactoryBean工厂类的getObject()方法被调用后,通过Feign.Builder容器实例的target()方法完成RPC动态代理实例的创建。Feign.Builder的target()实例方法首先调用内部的build()方法创建一个Feign实例,然后通过该实例的newInstance(...)方法创建最终的RPC动态代理实例。默认情况下,所创建的Feign实例为ReflectiveFeign类型,二者的关系如图3-20所示。

图3-20 Feign和ReflectiveFeign二者之间的关系

这里通过单步断点演示一下。通过开发调试工具(如IDEA)在Feign.Builder的target(...)方法唯一的一行代码上设置一个断点,然后以调试模式启动uaa-provider服务,在工程启动的过程中可以看到断点所在的语句会被执行到。

断点被执行到之后,通过IDEA的Evaluate工具计算一下target()方法运行时的target实参值,可以看到,它的实参值就是对DemoClient远程接口信息的一种二次封装,如图3-21所示。

图3-21 DemoClient动态代理实例创建时的target()方法处的断点信息

总结一下,当从Spring容器获取RPC接口的动态代理实例时,对应的FeignClientFactoryBean的getObject()方法会被调用到,然后通过Feign.Builder建造者容器实例的target()方法创建RPC接口的动态代理实例,并缓存到Spring IOC容器中。

默认的RPC动态代理实例的创建流程

默认情况下,Feign.Builder建造者实例的target()方法会调用自身的build()方法创建一个ReflectiveFeign(反射式Feign)实例,然后调用该实例的newInstance()方法创建远程接口最终的JDK动态代理实例。

ReflectiveFeign(反射式Feign)类的实例的newInstance()方法创建RPC动态代理实例的具体步骤是什么呢?先看看ReflectiveFeign的源码,具体如下:

代码语言:javascript
复制
package feign;
//省略import
public class ReflectiveFeign extends Feign {
 //方法解析器
private final ParseHandlersByName targetToHandlersByName;
//调用处理器工厂
private final InvocationHandlerFactory factory;
 ...
 //创建RPC客户端动态代理实例
 public <T> T newInstance(Target<T> target) {
 //方法解析: 方法名和方法处理器的映射
 Map<String, MethodHandler> nameToHandler = targetToHandlersByName.apply(target);
//方法反射对象和方法处理器的映射
 Map<Method, MethodHandler> methodToHandler = new LinkedHashMap<Method, MethodHandler>();
 ...
//创建一个InvocationHandler调用处理器
 InvocationHandler handler = factory.create(target, methodToHandler);
//最后调用JDK的Proxy.newProxyInstance创建代理对象
T proxy = (T) Proxy.newProxyInstance(
target.type().getClassLoader(), new Class<?>[]{target.type()}, handler);
 ...
 //返回代理对象
 return proxy;
 }

终于看到Feign动态代理类实例的创建逻辑了,以上默认的Feign RPC动态代理客户端实例的创建流程和前面介绍的模拟动态代理RPC客户端实例的创建流程大致相似。

简单来说,默认的Feign RPC动态代理客户端实例的创建流程大致为以下4步:

(1)方法解析。解析远程接口中的所有方法,为每一个方法创建一个MethodHandler方法处理器,然后进行方法名称和方法处理器的Key-Value(键-值)映射nameToHandler。

(2)创建方法反射实例和方法处理器的映射。

通过方法名称和方法处理器的映射nameToHandler创建一个方法反射实例到方法处理器的Key-Value映射methodToHandler,作为方法远程调用时的分发处理映射实例。

(3)创建一个JDK调用处理器。

主要以methodToHandler为参数,创建一个InvocationHandler调用处理器实例。

(4)创建一个动态代理对象。

调用JDK的Proxy.newProxyInstance()方法创建一个动态代理实例,它的参数有3个:RPC远程接口的类装载器、RPC远程接口的Class实例以及上一步创建的InvocationHandler调用处理器实例。

远程接口的RPC动态代理实例的创建流程如图3-22所示。

图3-22 远程接口的RPC动态代理实例的创建流程

以上创建RPC动态代理客户端实例的4个步骤是需要理解和掌握的重点内容,后面的介绍会根据这4个步骤展开。

在 ReflectiveFeign.newInstance()方法中首先调用了ParseHandlersByName.apply()方法,解析RPC接口中的所有RPC方法配置(通过Contract解析),然后为每个RPC方法创建一个对应的MethodHandler方法处理器。

默认的ParseHandlersByName方法解析器是ReflectiveFeign(反射式Feign)类的一个内部类,它的源码如下:

代码语言:javascript
复制
package feign;
//省略import
public class ReflectiveFeign extends Feign {
 ...
 //内部类:方法解析器
 static final class ParseHandlersByName {
 //同步方法处理器工厂
 private final SynchronousMethodHandler.Factory factory;
...
 //RPC接口元数据解析
public Map<String, MethodHandler> apply(Target key) {
 //解析RPC方法元数据,返回一个方法元数据列表
 List<MethodMetadata> metadata =
contract.parseAndValidatateMetadata(key.type());
 Map<String, MethodHandler> result =
 new LinkedHashMap<String, MethodHandler>();
 //迭代RPC方法元数据列表
 for (MethodMetadata md : metadata) {
 ...
 //通过方法处理器工厂factory创建SynchronousMethodHandler同步方法处理实例
 result.put(md.configKey(),
 factory.create(key, md, buildTemplate, options, decoder, errorDecoder));
 }
 return result;
 }
 }

通过以上源码可以看到,方法解析器ParseHandlersByName创建方法处理器的过程是通过方法处理器工厂类实例factory的create()方法完成的。而默认的方法处理器工厂类Factory定义在SynchronousMethodHandler类中,其代码如下:

代码语言:javascript
复制
package feign;
//省略import
final class SynchronousMethodHandler implements MethodHandler {...
 static class Factory {
 public MethodHandler create(
 Target<?> target, MethodMetadata md,
 feign.RequestTemplate.Factory buildTemplateFromArgs,
 Options options, Decoder decoder, ErrorDecoder errorDecoder)
 {
 return new SynchronousMethodHandler(
 target, this.client, this.retryer, this.requestInterceptors,
 this.logger, this.logLevel, md, buildTemplateFromArgs, options,
 decoder, errorDecoder, this.decode404);
 }
...
}

通过以上源码可以看出,通过默认方法处理器工厂类Factory的create()方法创建的正是同步方法处理器SynchronousMethodHandler的实例。

接下来,简单介绍一下FeignInvocationHandler调用处理器的创建。和方法处理器类似,它的创建也是通过工厂模式完成的。默认的InvocationHandler实例是通过InvocationHandlerFactory工厂类完成的。该工厂类的源码大致如下:

代码语言:javascript
复制
package feign;
//调用处理器工厂接口
public interface InvocationHandlerFactory {
 InvocationHandler create(Target target, Map<Method, MethodHandler> dispatch);
...
 //默认实现类
 static final class Default implements InvocationHandlerFactory {
 //通过内部类FeignInvocationHandler构造一个默认的调用处理器
 @Override
 public InvocationHandler create(Target target, Map<Method, MethodHandler> dispatch) {
 return new ReflectiveFeign.FeignInvocationHandler(target, dispatch);
 }
 }
}

在上面的源码中,调用处理器工厂InvocationHandlerFactory仅仅是一个接口,只定义了一个唯一的create()方法,用于创建InvocationHandler调用处理器实例。

InvocationHandlerFactory工厂类提供了一个默认的实现类——Default内部类,其create()方法所创建的调用处理器实例就是前文反复提及的,也就是做过重点介绍的Feign的默认调用处理器类FeignInvocationHandler类的实例。

Contract远程调用协议规则类

在通过 ReflectiveFeign.newInstance()方法创建本地JDK Proxy实例时,首先需要调用方法解析器ParseHandlersByName的apply()方法,获取方法名和方法处理器的映射。

在ParseHandlersByName.apply()方法中,需要通过Contract协议规则类将远程调用Feign接口中的所有方法配置和注解解析成一个List<MethodMetadata>方法元数据列表。

Contract协议规则类与方法解析器、调用处理器的关系如图3-23所示。

图3-23 Contract协议规则类与方法解析器、调用处理器的关系

关于RPC接口的配置解析类,Spring Cloud Feign中有两个协议规则解析类:一个为Feign默认协议规则解析类(DefaultContract);另一个为SpringMvcContract协议规则解析类,后者用于解析使用了Spring MVC规则配置的RPC方法。

Spring Cloud Feign的协议规则解析如图3-24所示。

图3-24 Feign的Contract的协议规则解析示意图

Feign有一套自己的默认协议规则,定义了一系列RPC方法的配置注解,用于RPC方法所对应的HTTP请求相关的参数。下面是一个官方的简单实例:

代码语言:javascript
复制
public interface GitHub {
 @RequestLine("GET /repos/{owner}/{repo}/contributors")
 List<Contributor> getContributors(@Param("owner") String owner, @Param("repo") String repository);
 class Contributor {
 String login;
 int contributions;
 }
}

实例中的@RequestLine注解是一个Feign默认的配置注解,用于配置HTTP的Method请求类型和URI请求路径。

为了降低学习成本,Spring Cloud并没有推荐采用Feign自己的协议规则注解来进行RPC接口配置,而是推荐部分Spring MVC协议规则注解来进行RPC接口的配置,并且通过SpringMvcContract协议规则解析类进行解析。

采用Spring MVC协议规则注解进行RPC接口配置的好处为:对开发人员来说,远程调用RPC方法的注解配置和对应的服务端REST接口的注解配置可以基本保持一致,这样就降低了开发人员的学习成本和维护成本。

本文给大家讲解的内容是SpringCloudRPC远程调用核心原理:Feign的RPC动态代理实例的创建流程

  1. 下篇文章给大家讲解的是SpringCloudRPC远程调用核心原理:Feign远程调用的执行流程;
  2. 觉得文章不错的朋友可以转发此文关注小编;
  3. 感谢大家的支持!

本文就是愿天堂没有BUG给大家分享的内容,大家有收获的话可以分享下,想学习更多的话可以到微信公众号里找我,我等你哦。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2022-03-07,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 愿天堂没有BUG 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Feign的RPC动态代理实例的创建流程
  • Feign的整体运作流程
  • RPC动态代理容器实例的FactoryBean工厂类
  • Feign.Builder建造者容器实例
  • 默认的RPC动态代理实例的创建流程
  • Contract远程调用协议规则类
  • 本文给大家讲解的内容是SpringCloudRPC远程调用核心原理:Feign的RPC动态代理实例的创建流程
相关产品与服务
容器服务
腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档