导读:对于业务系统而言,系统的架构决定了系统的可扩展性,应用的技术的决定了开发的效率,是否能够快速迭代,是决定产品是否能够占领市场的一个很重要因素。很多公司在进行技术升级时,都会或多或少遇到一些问题,本文主要讲解老项目接入OpenFeign组件时重构的思想、遇到的问题、解决的方案,以及为什么要这样做,其他人遇到类似问题也可以做参考,需要了解OpenFeign API的同学可以去github官网。
1. 背景
课件系统构建初期正好是微服务概念阶段,当时还没有 Spring Boot 这套框架,我们内部自己搭建了一套前后端分离,微服务化的系统。根据开发人员的使用习惯,系统中存在两种 http 请求的方式:1. 用 Apache HttpClient 写了一个工具类封装了 GET、POST 等各种请求并覆盖了不同的使用场景, 2. 使用 Spring 自带的 RestTemplate。较原生的东西都存在一个很大的优点:扩展方便,但同时也存在一个很大的缺点:应用时会书写大量的代码,不利于后期维护。
2020 年来业务快速增长,面对层出不穷的客户需求,不单单为了目前的快速迭代,从系统可扩展性与稳定性考虑,从长远角度考虑,技术必须要做升级,引入 OpenFeign 就是计划之一。
2. 为什么选用 OpenFeign
面向接口编程
扩展性好
支持熔段与负载均衡
支持自定义序列化与反序列化机制
可以自己集成OkHttp, Apache HttpClient等
支持各种日志组件
很方便的注入拦截器
...
Spring Cloud也用的OpenFeign
3. 落地过程
3.1 开始实践
因为此项目用的是Struts2+Spring的架构,所以直接引入
spring-cloud-starter-openfeign
的方式行不通,同时未避免引入其它问题,也不能强上,所以要自己另找门路了。
最开始按照官方 demo 重构了一个接口,抽象出来如下:
这里有一个很明显的问题,每次请求其他服务都要 new 一个 Builder,这明显是框架级别统一的配置不符合系统设计的原则,当代码写完,这种写法就被 pass 了。
把Builder当作bean注入
看似比较完美了,但是会造成频繁生成 Feign 接口代理类对象,同时会造成 gc 频繁,所以也不可取。参考下 Spring Cloud,是以 api 接口的维度注入的 bean,所以有了下面的方案。
以接口维度注入
这里会产生一个疑问,在使用 BankFeign 的时候会不会有线程安全问题,通过跟踪源码,不会产生线程安全问题。
这里又产生了一个新的问题,此时 bean 是以域名的维度注入的,每写一个 Api 接口,就需要手动注入一个 Feign 客户端 Bean,是否可以自动注入呢?后期还要引入服务注册中心,这种方式也不太适合。所以这种看似比较完美的方案也必须放弃。
3.2 挑战
如何把 Feign interface 自动注入成 Bean?这里有两种方案:
若使用第一种方案,为防止项目启动时找不到依赖,需要在创建项目中 Bean 之前,把 Feign 的 Bean 注入到容器中,还是按照标准的方法出牌吧,选择了第二种。
自定义注解
FeignClient
,两个属性url和name就满足需求了
重写扫描类
ClassPathScanningCandidateComponentProvider
实现
ImportBeanDefinitionRegistrar
,注入Feign接口的BeanDefinition
信息。这里会有一个问题,接口是没办法变成Definition
注入到容器中的,因为接口根本不能new
。
解决方案:
重写FactoryBean
,把 Feign 接口的Definition
注入到FactoryBean
中,实际向容器中注入FactoryBean
,通过getObject
方法返回实际的FeignClient
对象。
3.3 落地成功
经过对 OpenFeign 与 Spring Framework 的一步步抛析,最终方案落地。
4. 总结
遵循系统设计的原则
把握好Spring启动时的各个阶段
向优秀者学习
关于作者
徐海兴,好未来软件开发工程师,专注于系统架构设计。
评论