你必须懂也可以懂的微服务系列三:服务调用
在了解服务注册
与反注册
后,就该到服务调用环节了。
进行服务调用之前,需要组装请求头、请求体,构建客户端对象,通过服务提供方url地址调用远程服务。此方式虽然可以实现远程调用,但是需要使用者了解底层调用,并且还会给开发人员带来重复编码的问题。
为此,今天就来了解下SpringCloud
的OpenFeign
是如何实现远程调用的
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
注解进行注入的时候,注入的是FeignClientFactoryBean
中getObject()
方法返回的对象
3.5 FeignContext
从命名可以得知FeignContext
是feign
的上下文,其存在着一个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;
}
复制代码
每次先从集合中取,如果集合中没有对应的子容器则进行创建,然后让容器中注册了
PropertyPlaceholderAutoConfiguration
和this.defaultConfigType
通过
FeignContext
的构造函数,我们可以了解到this.defaultConfigType
就是FeignClientsConfiguration
打开
FeignClientsConfiguration
可以看到里面声明了Encoder
、Decoder
、Contract
等Bean
3.8 小结
到这里脑海中应该有个大概的总结:
@FeignClient
对应FeignClientFactoryBean
- 注入
@FeignClient
标注的接口,实际上注入的是FeignClientFactoryBean
中getObject()
返回的对象 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.FeignInvocationHandler
的invoke
方法
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
框架,满足自己的成就感
你必须懂也可以懂的微服务系列三:服务调用相关推荐
- 微服务系列:服务注册与发现的实现原理、及实现优劣势比较
服务注册与发现的来源 首先,服务注册与发现是来自于微服务架构的产物. 在传统的服务架构中,服务的规模处于运维人员的可控范围内.当部署服务的多个节点时,一般使用静态配置的方式实现服务信息的设定.而在微服 ...
- springcloud微服务系列之服务注册与发现组件Eureka
一.Eurake的简介 二.使用Eureka进行服务的注册消费 1.创建一个服务注册中心 2.创建服务的提供者 3.创建服务的消费者 总结 一.Eurake的简介 今天我们来介绍下springclou ...
- micro、M3O微服务系列(三)
文章目录 概述 安装 主机上(本地安装) go方式 docker binary kubernetes 服务器 特征 用法 帮助 基础命令 命令行 内置命令 signup login 动态命令 User ...
- 微服务系列:Dubbo与SpringCloud的Ribbon、Hystrix、Feign的优劣势比较
在微服务架构中,分布式通信.分布式事务.分布式锁等问题是亟待解决的几个重要问题. Spring Cloud是一套完整的微服务解决方案,基于 Spring Boot 框架.确切的说,Spring Clo ...
- 搞懂分布式技术28:微服务(Microservice)那点事
微服务(Microservice)那点事 肥侠 2016-01-13 09:46:53 浏览58371 评论15 分布式系统与计算 微服务 摘要: 微服务架构被提出很短的时间内,就被越来越多的开发人员 ...
- 什么是分布式微服务架构?三分钟彻底弄懂什么是分布式和微服务
一.微服务简介 1. 微服务的诞生 微服务是基于分而治之的思想演化出来的.过去传统的一个大型而又全面的系统,随着互联网的发展已经很难满足市场对技术的需求,于是我们从单独架构发展到分布式架构,又从分布式 ...
- 一分钟弄懂什么是分布式和微服务
简单的说,微服务是架构设计方式,分布式是系统部署方式,两者概念不同 微服务是啥? 这里不引用书本上的复杂概论了,简单来说微服务就是很小的服务,小到一个服务只对应一个单一的功能,只做一件事.这个服务可以 ...
- 【分享】老调重弹,既懂技术又懂管理的人才发展中的实际问题
背景:最近参加了校友组织的计算机技术小圈子(20+人)分享会,分享主题:老调重弹,既懂技术又懂管理的人才发展中的实际问题,分享人是马老师,86级校友.果然是满满干货,本文结合分享内容和大家对分享内容不 ...
- 像哆啦A梦懂大雄一样懂客户,我们也会拥有百宝箱
一般卫生间的标语是这样的: 来也匆匆,去也冲冲 阿里云 卫生间的标语是这样的: 像哆啦A梦懂大雄一样懂客户 我们也会拥有百宝箱 这样的: 如果白居易是个产品经理 老奶奶玩的6就发布 老奶奶玩不转就继续 ...
最新文章
- android qt 对比_QT for android 比较完美解决 全屏问题
- linux echo 写二进制文件
- window.open()具体解释及浏览器兼容性问题
- linux fg 参数,Linux的bg和fg命令简单介绍
- 华人的战场——MSU视频编码大赛
- 微信小程序 长按图片不出现菜单_微信小程序实现长按删除图片的示例
- Redis主从架构和哨兵架构模式
- 泛微 E9开发视频教程
- zscore标准化步骤_数据的标准化
- Proptech崛起,房地产产业数智化,成了2020年的新风口
- 程序员常用的这十个电子书下载网站,你值得拥有
- 年底大标预备!2019通信招投标大势到底是怎样的?
- Redis入门到实战(实战篇)缓存更新、穿透、雪崩、击穿!
- Android果冻效果(阻尼动画)
- 【电脑使用技巧】1TB的硬盘只有931G 硬盘容量去哪儿了?
- Unity3D 无限滚动列表
- 第09讲 推断未知:统计推断的基本框架
- python输入姓名专业班级口号_有创意的班级口号14班
- 深圳物联网培训:要想成为一名物联网工程师,需要学习哪些知识?
- i.MX 8M Mini Cortex-M4 Hello World
热门文章
- java 保留数字与中文_java 转中文数字
- dijkstra邻接表_掌握算法-图论-最短路径算法-Dijkstra算法
- Python-OpenCV 笔记6 -- 轮廓(Contours)
- python源码精要(8)-CPython源代码结构
- 【机器学习】浅析机器学习各大算法的适用场景
- “数字强市 数创未来” | 山东省数据应用创新创业大赛烟台赛场火热招募中!...
- 【学术相关】魔术乘法:张成奇教授40年磨一剑!
- 温州大学《深度学习》课程课件(十二、自然语言处理和词嵌入)
- 网易云音乐:基于分布式图学习的推荐系统优化之路
- 【论文解读】NLP重铸篇之Word2vec