本系列代码地址:https://github.com/JoJoTec/sp...

在应用云原生的很多微服务中,比拟小规模的可能间接依附云服务中的负载均衡器进行外部域名与服务映射,通过健康检查接口判断实例衰弱状态,而后间接应用 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-clo...

首先,依据咱们之前剖析 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@ConditionalOnMissingBeanpublic Contract feignContract(ConversionService feignConversionService) {    boolean decodeSlash = feignClientProperties == null || feignClientProperties.isDecodeSlash();    return new SpringMvcContract(this.parameterProcessors, feignConversionService, decodeSlash);}@Beanpublic 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@AllArgsConstructorpublic 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 注册进去:

@Beanpublic 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