在了解服务注册反注册后,就该到服务调用环节了。

进行服务调用之前,需要组装请求头、请求体,构建客户端对象,通过服务提供方url地址调用远程服务。此方式虽然可以实现远程调用,但是需要使用者了解底层调用,并且还会给开发人员带来重复编码的问题。

为此,今天就来了解下SpringCloudOpenFeign是如何实现远程调用的

3.服务调用

3.1 开启Feign远程调用

当我们需要使用feign进行远程调用时,只需要在入口处加上@EnableFeignClients注解即可

3.2 扫描FeignClient注解标注的接口

@EnableFeignClients注解引入FeignClientsRegistrar配置类,该配置类会执行扫描动作,扫描项目中被@FeignClient注解标注的接口

ClassPathScanningCandidateComponentProvider scanner = getScanner();
scanner.setResourceLoader(this.resourceLoader);
scanner.addIncludeFilter(new AnnotationTypeFilter(FeignClient.class));
Set<String> basePackages = getBasePackages(metadata);
for (String basePackage : basePackages) {candidateComponents.addAll(scanner.findCandidateComponents(basePackage));
}
复制代码

关键代码:scanner.addIncludeFilter(new AnnotationTypeFilter(FeignClient.class));

3.3 创建并注册FeignClientFactoryBean

遍历3.2中扫描到的组件,每个组件创建一个FeignClientFactoryBean并注入到IOC容器中,FeignClientFactoryBean的类型就是使用@FeignClient标注的接口

private void registerFeignClient(BeanDefinitionRegistry registry, AnnotationMetadata annotationMetadata,Map<String, Object> attributes) {String className = annotationMetadata.getClassName();Class clazz = ClassUtils.resolveClassName(className, null);ConfigurableBeanFactory beanFactory = registry instanceof ConfigurableBeanFactory? (ConfigurableBeanFactory) registry : null;String contextId = getContextId(beanFactory, attributes);String name = getName(attributes);// 1. 创建FeignClientFactoryBeanFeignClientFactoryBean factoryBean = new FeignClientFactoryBean();// 2. 设置属性factoryBean.setBeanFactory(beanFactory);factoryBean.setName(name);factoryBean.setContextId(contextId);factoryBean.setType(clazz);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();});definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);definition.setLazyInit(true);validate(attributes);AbstractBeanDefinition beanDefinition = definition.getBeanDefinition();beanDefinition.setAttribute(FactoryBean.OBJECT_TYPE_ATTRIBUTE, className);beanDefinition.setAttribute("feignClientsRegistrarFactoryBean", factoryBean);// has a default, won't be nullboolean primary = (Boolean) attributes.get("primary");beanDefinition.setPrimary(primary);String[] qualifiers = getQualifiers(attributes);if (ObjectUtils.isEmpty(qualifiers)) {qualifiers = new String[] { contextId + "FeignClient" };}BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className, qualifiers);// 3.注入到IOC容器中BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
}
复制代码

3.4 注入FeignClientFactoryBean

当我们调用远程服务接口时,往往需要通过@Autowired注解的方式注入服务,然后调用对应的方法

@RestController
public class OpenfeignConsumerController {@Autowiredprivate IndexClient indexClient;@GetMapping("/index/{name}")public String index(@PathVariable(name = "name") String name) {return indexClient.index(name);}
}@FeignClient(value = "openfeign-provider-service")
public interface IndexClient {@GetMapping("/index/{name}")String index(@PathVariable(name = "name") String name);
}
复制代码

IndexClient@FeignClient注解标注,根据3.3章节可以了解到其实际上是一个FeignClientFactoryBean

FeignClientFactoryBean实现了FactoryBean接口,因此当使用@Autowired注解进行注入的时候,注入的是FeignClientFactoryBeangetObject()方法返回的对象

3.5 FeignContext

从命名可以得知FeignContextfeign的上下文,其存在着一个Map类型的context属性

private Map<String, AnnotationConfigApplicationContext> contexts = new ConcurrentHashMap<>();
复制代码

也就是说,每个@FeignClient对应一个AnnotationConfigApplicationContext上下文,本身应用也存在这一个AnnotationConfigApplicationContext

因此就形成了父子上下文

3.6 构建Feign Builder

Builder builder = this.feign(context);
复制代码
protected Builder feign(FeignContext context) {FeignLoggerFactory loggerFactory = (FeignLoggerFactory)this.get(context, FeignLoggerFactory.class);Logger logger = loggerFactory.create(this.type);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);this.applyBuildCustomizers(context, builder);return builder;
}
复制代码

构建Feign Builder就是从容器中获取相关组件进行设置,然后在对Feign Builder进行个性化配置

3.7 从容器获取组件

3.6章节中this.get(context, xxxx.class))这个方法频繁出现,需要了解一下该方法具体实现,此处以this.get(context, FeignLoggerFactory.class)为例

protected <T> T get(FeignContext context, Class<T> type) {T instance = context.getInstance(this.contextId, type);if (instance == null) {throw new IllegalStateException("No bean found of type " + type + " for " + this.contextId);} else {return instance;}
}
复制代码

3.5章节中介绍过 FeignContext,其维护了一个子容器集合,因此首先会先从子容器集合中获取指定名称的子容器

既然FeignContext维护了子容器集合,那么就必须了解子容器是如何创建的

protected AnnotationConfigApplicationContext getContext(String name) {if (!this.contexts.containsKey(name)) {synchronized (this.contexts) {if (!this.contexts.containsKey(name)) {this.contexts.put(name, createContext(name));}}}return this.contexts.get(name);
}protected AnnotationConfigApplicationContext createContext(String name) {AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();if (this.configurations.containsKey(name)) {for (Class<?> configuration : this.configurations.get(name).getConfiguration()) {context.register(configuration);}}for (Map.Entry<String, C> entry : this.configurations.entrySet()) {if (entry.getKey().startsWith("default.")) {for (Class<?> configuration : entry.getValue().getConfiguration()) {context.register(configuration);}}}context.register(PropertyPlaceholderAutoConfiguration.class, this.defaultConfigType);context.getEnvironment().getPropertySources().addFirst(new MapPropertySource(this.propertySourceName,Collections.<String, Object>singletonMap(this.propertyName, name)));if (this.parent != null) {// Uses Environment from parent as well as beanscontext.setParent(this.parent);// jdk11 issue// https://github.com/spring-cloud/spring-cloud-netflix/issues/3101context.setClassLoader(this.parent.getClassLoader());}context.setDisplayName(generateDisplayName(name));context.refresh();return context;
}
复制代码

每次先从集合中取,如果集合中没有对应的子容器则进行创建,然后让容器中注册了PropertyPlaceholderAutoConfigurationthis.defaultConfigType

通过FeignContext的构造函数,我们可以了解到this.defaultConfigType就是FeignClientsConfiguration

打开FeignClientsConfiguration可以看到里面声明了EncoderDecoderContractBean

3.8 小结

到这里脑海中应该有个大概的总结:

  • @FeignClient对应FeignClientFactoryBean
  • 注入@FeignClient标注的接口,实际上注入的是FeignClientFactoryBeangetObject()返回的对象
  • FeignContext做为feign的上下文,为每个@FeignClient创建一个子容器,子容器中声明所需要的Bean
  • 之所以为每个@FeignClient声明一个子容器,是会了让@FeignClient@FeignClient的配置进行隔离

3.9 创建Feign实例

之前通过Feign Builder链式方式设置了相关属性,现在就可以通过Feign Builder来创建Feign实例

public Feign build() {Client client = Capability.enrich(this.client, capabilities);Retryer retryer = Capability.enrich(this.retryer, capabilities);List<RequestInterceptor> requestInterceptors = this.requestInterceptors.stream().map(ri -> Capability.enrich(ri, capabilities)).collect(Collectors.toList());Logger logger = Capability.enrich(this.logger, capabilities);Contract contract = Capability.enrich(this.contract, capabilities);Options options = Capability.enrich(this.options, capabilities);Encoder encoder = Capability.enrich(this.encoder, capabilities);Decoder decoder = Capability.enrich(this.decoder, capabilities);// 1.InvocationHandlerFactory用于创建InvocationHandlerInvocationHandlerFactory invocationHandlerFactory =Capability.enrich(this.invocationHandlerFactory, capabilities);QueryMapEncoder queryMapEncoder = Capability.enrich(this.queryMapEncoder, capabilities);// 2.SynchronousMethodHandler用于创建SynchronousMethodHandlerSynchronousMethodHandler.Factory synchronousMethodHandlerFactory =new SynchronousMethodHandler.Factory(client, retryer, requestInterceptors, logger,logLevel, decode404, closeAfterDecode, propagationPolicy, forceDecoding);ParseHandlersByName handlersByName =new ParseHandlersByName(contract, options, encoder, decoder, queryMapEncoder,errorDecoder, synchronousMethodHandlerFactory);return new ReflectiveFeign(handlersByName, invocationHandlerFactory, queryMapEncoder);
}
复制代码

3.10 创建代理

@Override
public <T> T newInstance(Target<T> target) {// 1.遍历@FeignClient标注接口中的方法,通过SynchronousMethodHandler.Factory创建 MethodHandlerMap<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 {// 2. 构建Method与MethodHandler的映射关系,用于方法路由methodToHandler.put(method, nameToHandler.get(Feign.configKey(target.type(), method)));}}// 3. 通过InvocationHandlerFactory创建InvocationHandlerInvocationHandler handler = factory.create(target, methodToHandler);// 4. 生成代理对象T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(),new Class<?>[] {target.type()}, handler);for (DefaultMethodHandler defaultMethodHandler : defaultMethodHandlers) {defaultMethodHandler.bindTo(proxy);}return proxy;
}
复制代码

到此我们可以得知

@Autowired
private IndexClient indexClient;
复制代码

注入的是一个代理对象,当调用IndexClient方法的时候,会回调ReflectiveFeign.FeignInvocationHandlerinvoke方法

3.11 方法调用

@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();}return dispatch.get(method).invoke(args);
}
复制代码

如上根据方法路由到对应的SynchronousMethodHandler,然后调用其invoke()

@Override
public Object invoke(Object[] argv) throws Throwable {// 1.构建请求模板RequestTemplate template = buildTemplateFromArgs.create(argv);Options options = findOptions(argv);Retryer retryer = this.retryer.clone();while (true) {try {// 2.通过http方式发起远程调用并返回结果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;}}
}
复制代码

到此就实现了远程服务调用,可以看到使用Openfeign进行远程调用十分简单,只需搭配相关注解就可以像调用本地方法一样调用远程服务

4.结语

通过本文Openfeign讲解后,你也可以动手尝试使用HttpClient配合动态代理实现一个RPC框架,满足自己的成就感

你必须懂也可以懂的微服务系列三:服务调用相关推荐

  1. 微服务系列:服务注册与发现的实现原理、及实现优劣势比较

    服务注册与发现的来源 首先,服务注册与发现是来自于微服务架构的产物. 在传统的服务架构中,服务的规模处于运维人员的可控范围内.当部署服务的多个节点时,一般使用静态配置的方式实现服务信息的设定.而在微服 ...

  2. springcloud微服务系列之服务注册与发现组件Eureka

    一.Eurake的简介 二.使用Eureka进行服务的注册消费 1.创建一个服务注册中心 2.创建服务的提供者 3.创建服务的消费者 总结 一.Eurake的简介 今天我们来介绍下springclou ...

  3. micro、M3O微服务系列(三)

    文章目录 概述 安装 主机上(本地安装) go方式 docker binary kubernetes 服务器 特征 用法 帮助 基础命令 命令行 内置命令 signup login 动态命令 User ...

  4. 微服务系列:Dubbo与SpringCloud的Ribbon、Hystrix、Feign的优劣势比较

    在微服务架构中,分布式通信.分布式事务.分布式锁等问题是亟待解决的几个重要问题. Spring Cloud是一套完整的微服务解决方案,基于 Spring Boot 框架.确切的说,Spring Clo ...

  5. 搞懂分布式技术28:微服务(Microservice)那点事

    微服务(Microservice)那点事 肥侠 2016-01-13 09:46:53 浏览58371 评论15 分布式系统与计算 微服务 摘要: 微服务架构被提出很短的时间内,就被越来越多的开发人员 ...

  6. 什么是分布式微服务架构?三分钟彻底弄懂什么是分布式和微服务

    一.微服务简介 1. 微服务的诞生 微服务是基于分而治之的思想演化出来的.过去传统的一个大型而又全面的系统,随着互联网的发展已经很难满足市场对技术的需求,于是我们从单独架构发展到分布式架构,又从分布式 ...

  7. 一分钟弄懂什么是分布式和微服务

    简单的说,微服务是架构设计方式,分布式是系统部署方式,两者概念不同 微服务是啥? 这里不引用书本上的复杂概论了,简单来说微服务就是很小的服务,小到一个服务只对应一个单一的功能,只做一件事.这个服务可以 ...

  8. 【分享】老调重弹,既懂技术又懂管理的人才发展中的实际问题

    背景:最近参加了校友组织的计算机技术小圈子(20+人)分享会,分享主题:老调重弹,既懂技术又懂管理的人才发展中的实际问题,分享人是马老师,86级校友.果然是满满干货,本文结合分享内容和大家对分享内容不 ...

  9. 像哆啦A梦懂大雄一样懂客户,我们也会拥有百宝箱

    一般卫生间的标语是这样的: 来也匆匆,去也冲冲 阿里云 卫生间的标语是这样的: 像哆啦A梦懂大雄一样懂客户 我们也会拥有百宝箱 这样的: 如果白居易是个产品经理 老奶奶玩的6就发布 老奶奶玩不转就继续 ...

最新文章

  1. android qt 对比_QT for android 比较完美解决 全屏问题
  2. linux echo 写二进制文件
  3. window.open()具体解释及浏览器兼容性问题
  4. linux fg 参数,Linux的bg和fg命令简单介绍
  5. 华人的战场——MSU视频编码大赛
  6. 微信小程序 长按图片不出现菜单_微信小程序实现长按删除图片的示例
  7. Redis主从架构和哨兵架构模式
  8. 泛微 E9开发视频教程
  9. zscore标准化步骤_数据的标准化
  10. Proptech崛起,房地产产业数智化,成了2020年的新风口
  11. 程序员常用的这十个电子书下载网站,你值得拥有
  12. 年底大标预备!2019通信招投标大势到底是怎样的?
  13. Redis入门到实战(实战篇)缓存更新、穿透、雪崩、击穿!
  14. Android果冻效果(阻尼动画)
  15. 【电脑使用技巧】1TB的硬盘只有931G 硬盘容量去哪儿了?
  16. Unity3D 无限滚动列表
  17. 第09讲 推断未知:统计推断的基本框架
  18. python输入姓名专业班级口号_有创意的班级口号14班
  19. 深圳物联网培训:要想成为一名物联网工程师,需要学习哪些知识?
  20. i.MX 8M Mini Cortex-M4 Hello World

热门文章

  1. java 保留数字与中文_java 转中文数字
  2. dijkstra邻接表_掌握算法-图论-最短路径算法-Dijkstra算法
  3. Python-OpenCV 笔记6 -- 轮廓(Contours)
  4. python源码精要(8)-CPython源代码结构
  5. 【机器学习】浅析机器学习各大算法的适用场景
  6. “数字强市 数创未来” | 山东省数据应用创新创业大赛烟台赛场火热招募中!...
  7. 【学术相关】魔术乘法:张成奇教授40年磨一剑!
  8. 温州大学《深度学习》课程课件(十二、自然语言处理和词嵌入)
  9. 网易云音乐:基于分布式图学习的推荐系统优化之路
  10. 【论文解读】NLP重铸篇之Word2vec