栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

SpringCloudRPC远程调用核心原理:FeignRPC动态代理实例创建流程,java反射原理面试题

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

SpringCloudRPC远程调用核心原理:FeignRPC动态代理实例创建流程,java反射原理面试题

在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的部分源码如下:

package org.springframework.cloud.openfeign;

class FeignClientFactoryBean implements FactoryBean, 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 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工厂类实例的注册源码节选如下:

class FeignClientsRegistrar implements importBeanDefinitionRegistrar, … {

//为每一个RPC客户端接口注册一个beanDefinition,其beanClass为FeignClientFactoryBean

private void registerFeignClient(BeanDefinitionRegistry registry,

Annotationmetadata annotationmetadata,

Map 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”));

definitio

《一线大厂Java面试题解析+后端开发学习笔记+最新架构讲解视频+实战项目源码讲义》

【docs.qq.com/doc/DSmxTbFJ1cmN1R2dB】 完整内容开源分享

n.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的部分源码如下:

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类的部分源码如下:

package feign;

public abstract class Feign {

//建造者方法

public static Builder builder() {

return new Builder();

}

//内部类:建造者类

public static class Builder {

//创建RPC客户端的动态代理实例

public T target(Target 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的源码,具体如下:

package feign;

//省略import

public class ReflectiveFeign extends Feign {

//方法解析器

private final ParseHandlersByName targetToHandlersByName;

//调用处理器工厂

private final InvocationHandlerFactory factory;

//创建RPC客户端动态代理实例

public T newInstance(Target target) {

//方法解析: 方法名和方法处理器的映射

Map nameToHandler = targetToHandlersByName.apply(target);

//方法反射对象和方法处理器的映射

Map methodToHandler = new linkedHashMap();

//创建一个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)类的一个内部类,它的源码如下:

package feign;

//省略import

public class ReflectiveFeign extends Feign {

//内部类:方法解析器

static final class ParseHandlersByName {

//同步方法处理器工厂

private final SynchronousMethodHandler.Factory factory;

//RPC接口元数据解析

public Map apply(Target key) {

//解析RPC方法元数据,返回一个方法元数据列表

List metadata =

contract.parseAndValidatatemetadata(key.type());

Map result =

new linkedHashMap();

//迭代RPC方法元数据列表

for (Methodmetadata md : metadata) {

//通过方法处理器工厂factory创建SynchronousMethodHandler同步方法处理实例

result.put(md.configKey(),

factory.create(key, md, buildTemplate, options, decoder, errorDecoder));

}

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/606422.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号