本系列代码地址:https://github.com/JoJoTec/spring-cloud-parent

在使用云原生的很多微服务中,比较小规模的可能直接依靠云服务中的负载均衡器进行内部域名与服务映射,通过健康检查接口判断实例健康状态,然后直接使用 OpenFeign 生成对应域名的 Feign Client。Spring Cloud 生态中,对 OpenFeign 进行了封装,其中的 Feign Client 的各个组件,也是做了一定的定制化,可以实现在 OpenFeign Client 中集成服务发现与负载均衡。在此基础上,我们还结合了 Resilience4J 组件,实现了微服务实例级别的线程隔离,微服务方法级别的断路器以及重试。

我们先来分析下 Spring Cloud OpenFeign

Spring Cloud OpenFeign 解析

从 NamedContextFactory 入手

Spring Cloud OpenFeign 的 github 地址:https://github.com/spring-cloud/spring-cloud-openfeign

首先,根据我们之前分析 spring-cloud-loadbalancer 的流程,我们先从继承 NamedContextFactory 的类入手,这里是 FeignContext,通过其构造函数,得到其中的默认配置类:

FeignContext.java

public FeignContext() {
super(FeignClientsConfiguration.class, "feign", "feign.client.name");
}

从构造方法可以看出,默认的配置类是:FeignClientsConfiguration。我们接下来详细分析这个配置类中的元素,并与我们之前分析的 OpenFeign 的组件结合起来。

负责解析类元数据的 Contract,与 spring-web 的 HTTP 注解相结合

为了开发人员更好上手使用和理解,最好能实现使用 spring-web 的 HTTP 注解(例如 @RequestMapping@GetMapping 等等)去定义 FeignClient 接口。在 FeignClientsConfiguration 中就是这么做的:

FeignClientsConfiguration.java

@Autowired(required = false)
private FeignClientProperties feignClientProperties; @Autowired(required = false)
private List<AnnotatedParameterProcessor> parameterProcessors = new ArrayList<>(); @Autowired(required = false)
private List<FeignFormatterRegistrar> feignFormatterRegistrars = new ArrayList<>(); @Bean
@ConditionalOnMissingBean
public Contract feignContract(ConversionService feignConversionService) {
boolean decodeSlash = feignClientProperties == null || feignClientProperties.isDecodeSlash();
return new SpringMvcContract(this.parameterProcessors, feignConversionService, decodeSlash);
} @Bean
public FormattingConversionService feignConversionService() {
FormattingConversionService conversionService = new DefaultFormattingConversionService();
for (FeignFormatterRegistrar feignFormatterRegistrar : this.feignFormatterRegistrars) {
feignFormatterRegistrar.registerFormatters(conversionService);
}
return conversionService;
}

其核心提供的 Feign 的 Contract 就是 SpringMvcContractSpringMvcContract 主要包含两部分核心逻辑:

  • 定义 Feign Client 专用的 Formatter 与 Converter 注册
  • 使用 AnnotatedParameterProcessor 来解析 SpringMVC 注解以及我们自定义的注解

定义 Feign Client 专用的 Formatter 与 Converter 注册

首先,Spring 提供了类型转换机制,其中单向的类型转换为实现 Converter 接口;在 web 应用中,我们经常需要将前端传入的字符串类型的数据转换成指定格式或者指定数据类型来满足我们调用需求,同样的,后端开发也需要将返回数据调整成指定格式或者指定类型返回到前端页面(在 Spring Boot 中已经帮我们做了从 json 解析和返回对象转化为 json,但是某些特殊情况下,比如兼容老项目接口,我们还可能使用到),这个是通过实现 Formatter 接口实现。举一个简单的例子:

定义一个类型:

@Data
@AllArgsConstructor
public class Student {
private final Long id;
private final String name;
}

我们定义可以通过字符串解析出这个类的对象的 Converter,例如 "1,zhx" 就代表 id = 1 并且 name = zhx:

public class StringToStudentConverter implements Converter<String, Student> {
@Override
public Student convert(String from) {
String[] split = from.split(",");
return new Student(
Long.parseLong(split[0]),
split[1]);
}
}

然后将这个 Converter 注册:

@Configuration(proxyBeanMethods = false)
public class TestConfig implements WebMvcConfigurer {
@Override
public void addFormatters(FormatterRegistry registry) {
registry.addConverter(new StringToStudentConverter());
}
}

编写一个测试接口:

@RestController
@RequestMapping("/test")
public class TestController {
@GetMapping("/string-to-student")
public Student stringToStudent(@RequestParam("student") Student student) {
return student;
}
}

调用 /test/string-to-student?student=1,zhx,可以看到返回:

{
"id": 1,
"name": "zhx"
}

同样的,我们也可以通过 Formatter 实现:

public class StudentFormatter implements Formatter<Student> {
@Override
public Student parse(String text, Locale locale) throws ParseException {
String[] split = text.split(",");
return new Student(
Long.parseLong(split[0]),
split[1]);
} @Override
public String print(Student object, Locale locale) {
return object.getId() + "," + object.getName();
}
}

然后将这个 Formatter 注册:

@Configuration(proxyBeanMethods = false)
public class TestConfig implements WebMvcConfigurer {
@Override
public void addFormatters(FormatterRegistry registry) {
registry.addFormatter(new StudentFormatter());
}
}

Feign 也提供了这个注册机制,为了和 spring-webmvc 的注册机制区分开,使用了 FeignFormatterRegistrar 继承了 FormatterRegistrar 接口。然后通过定义 FormattingConversionService 这个 Bean 实现 Formatter 和 Converter 的注册。例如:

假设我们有另一个微服务需要通过 FeignClient 调用上面这个接口,那么就需要定义一个 FeignFormatterRegistrar 将 Formatter 注册进去:

@Bean
public FeignFormatterRegistrar getFeignFormatterRegistrar() {
return registry -> {
registry.addFormatter(new StudentFormatter());
};
}

之后我们定义 FeignClient:

@FeignClient(name = "test-server", contextId = "test-server")
public interface TestClient {
@GetMapping("/test/string-to-student")
Student get(@RequestParam("student") Student student);
}

在调用 get 方法时,会调用 StudentFormatter 的 print 将 Student 对象输出为格式化的字符串,例如 {"id": 1,"name": "zhx"} 会变成 1,zhx

AnnotatedParameterProcessor 来解析 SpringMVC 注解以及我们自定义的注解

AnnotatedParameterProcessor 是用来将注解解析成 AnnotatedParameterContext 的 Bean,AnnotatedParameterContext 包含了 Feign 的请求定义,包括例如前面提到的 Feign 的 MethodMetadata 即方法元数据。默认的 AnnotatedParameterProcessor 包括所有 SpringMVC 对于 HTTP 方法定义的注解对应的解析,例如 @RequestParam 注解对应的 RequestParamParameterProcessor

RequestParamParameterProcessor.java

public boolean processArgument(AnnotatedParameterContext context, Annotation annotation, Method method) {
//获取当前参数属于方法的第几个
int parameterIndex = context.getParameterIndex();
//获取参数类型
Class<?> parameterType = method.getParameterTypes()[parameterIndex];
//要保存的解析的方法元数据 MethodMetadata
MethodMetadata data = context.getMethodMetadata(); //如果是 Map,则指定 queryMap 下标,直接返回
//这代表一旦使用 Map 作为 RequestParam,则其他的 RequestParam 就会被忽略,直接解析 Map 中的参数作为 RequestParam
if (Map.class.isAssignableFrom(parameterType)) {
checkState(data.queryMapIndex() == null, "Query map can only be present once.");
data.queryMapIndex(parameterIndex);
//返回解析成功
return true;
}
RequestParam requestParam = ANNOTATION.cast(annotation);
String name = requestParam.value();
//RequestParam 的名字不能是空
checkState(emptyToNull(name) != null, "RequestParam.value() was empty on parameter %s", parameterIndex);
context.setParameterName(name); Collection<String> query = context.setTemplateParameter(name, data.template().queries().get(name));
//将 RequestParam 放入 方法元数据 MethodMetadata
data.template().query(name, query);
//返回解析成功
return true;
}

我们也可以实现 AnnotatedParameterProcessor 来自定义我们的注解,配合 SpringMVC 的注解一起使用去定义 FeignClient

微信搜索“我的编程喵”关注公众号,每日一刷,轻松提升技术,斩获各种offer

SpringCloud升级之路2020.0.x版-29.Spring Cloud OpenFeign 的解析(1)的更多相关文章

  1. SpringCloud升级之路2020.0.x版-21.Spring Cloud LoadBalancer简介

    本系列代码地址:https://github.com/HashZhang/spring-cloud-scaffold/tree/master/spring-cloud-iiford 我们使用 Spri ...

  2. SpringCloud升级之路2020.0.x版-22.Spring Cloud LoadBalancer核心源码

    本系列代码地址:https://github.com/HashZhang/spring-cloud-scaffold/tree/master/spring-cloud-iiford 经过上一节的详细分 ...

  3. Spring Cloud 升级之路 - 2020.0.x - 6. 使用 Spring Cloud LoadBalancer (1)

    本项目代码地址:https://github.com/HashZhang/spring-cloud-scaffold/tree/master/spring-cloud-iiford 我们使用 Spri ...

  4. SpringCloud升级之路2020.0.x版-1.背景

    本系列为之前系列的整理重启版,随着项目的发展以及项目中的使用,之前系列里面很多东西发生了变化,并且还有一些东西之前系列并没有提到,所以重启这个系列重新整理下,欢迎各位留言交流,谢谢!~ Spring ...

  5. SpringCloud升级之路2020.0.x版-41. SpringCloudGateway 基本流程讲解(1)

    本系列代码地址:https://github.com/JoJoTec/spring-cloud-parent 接下来,将进入我们升级之路的又一大模块,即网关模块.网关模块我们废弃了已经进入维护状态的 ...

  6. SpringCloud升级之路2020.0.x版-6.微服务特性相关的依赖说明

    本系列代码地址:https://github.com/HashZhang/spring-cloud-scaffold/tree/master/spring-cloud-iiford spring-cl ...

  7. SpringCloud升级之路2020.0.x版-10.使用Log4j2以及一些核心配置

    本系列代码地址:https://github.com/HashZhang/spring-cloud-scaffold/tree/master/spring-cloud-iiford 我们使用 Log4 ...

  8. SpringCloud升级之路2020.0.x版-34.验证重试配置正确性(1)

    本系列代码地址:https://github.com/JoJoTec/spring-cloud-parent 在前面一节,我们利用 resilience4j 粘合了 OpenFeign 实现了断路器. ...

  9. SpringCloud升级之路2020.0.x版-43.为何 SpringCloudGateway 中会有链路信息丢失

    本系列代码地址:https://github.com/JoJoTec/spring-cloud-parent 在开始编写我们自己的日志 Filter 之前,还有一个问题我想在这里和大家分享,即在 Sp ...

随机推荐

  1. 5UCMS判断当前栏目高亮(用于当前所在栏目加背景图片或颜色)

    5UCMS判断当前栏目高亮标签 比较简单的是频道页(channel.html): 大类代码: <!--menu:{ $row=10 $table=channel }--> <li { ...

  2. python 深度学习 库文件安装出错汇总

    Cython_bbox FairMOT | win10下cython-bbox安装的心酸之路_是阳阳呀的博客-CSDN博客 swig 安装polyiou.py https://blog.csdn.ne ...

  3. python学习笔记(四)-文件操作

    文件读写"""一.文件打开有3种方式 1.读 r #如果打开的文件的时候没有指定模式,那么默认是读 读写模式 r+,只要沾上r,文件不存在的时候,打开都会报错 2.写 w ...

  4. english note(6.3 to 6.8)

    6.3 http://www.51voa.com/VOA_Special_English/pakistan-town-struggles-with-rise-in-hiv-infections-821 ...

  5. ❤️❤️新生代农民工爆肝8万字,整理Python编程从入门到实践(建议收藏)已码:8万字❤️❤️

    @ 目录 开发环境搭建 安装 Python 验证是否安装成功 安装Pycharm 配置pycharm 编码规范 基本语法规则 保留字 单行注释 多行注释 行与缩进 多行语句 数据类型 空行 等待用户输 ...

  6. element-ui上传多个文件时会发送多个请求

    1. element-ui的默认 默认是异步多次请求上传单个文件 如果业务就是单纯的上传文件,那么这个样子是没有问题的 前端代码参考 https://element-plus.gitee.io/#/z ...

  7. MySQL8.0.20下载与安装详细图文教程,mysql安装教程

    MySQL下载与安装(8.0.20版)教程 mysql安装包+mysql学习视频+mysql面试指南视频教程 下载地址: 链接:https://pan.baidu.com/s/1FmLFhGlajBQ ...

  8. JS中变量的命名规范

    命名规范 包含数字.字母.下划线和$,但 不能以数字开头 变量名严格区分大小写 变量名不能是关键字和保留字 变量名要见名知意 如果变量名有多个单词组成,推荐使用 小驼峰命名法 命名时,尽量使用英语,如 ...

  9. 如何无缝迁移 SpringCloud/Dubbo 应用到 Serverless 架构

    作者 | 行松 阿里巴巴云原生团队 本文整理自<Serverless 技术公开课>,"Serverless"公众号后台回复"入门",即可获取系列文章 ...

  10. Java(3)基本数据类型及其类型转换

    作者:季沐测试笔记 原文地址:https://www.cnblogs.com/testero/p/15201501.html 博客主页:https://www.cnblogs.com/testero ...