基于CustomCondition实现spring mvc的版本号路由插件
需求
先描述下我实现的个人需求, 项目中有对接客户端, 客户端的版本号是多样且并存的.
有些时候提供给客户端的某个接口, 在不同的版本号下逻辑不同, 且不能做成兼容. 需要不同版本的接口并存. 此时一般方案可能需要修改url地址, 开一个新接口给新版本.
所以这里期望有一种办法可以使不同版本的接口并存在同一个url下, 只通过 header
请求头来实现路由和隔离.
提出以下几点效果预设
- 支持快速接入
- 针对不同项目中的版本号规则能自主切换, 支持扩展
- 不影响不需要版本控制的接口(有些接口所有版本客户端都一致那种)
实现后效果
快速接入
- 导入依赖(没上传官方仓库, 只能通过down源码先打包到本地或者私有仓库之类的)
- 在springBoot启动类上加注解
@EnableWebVersionControl
- (可选)在
application.yml/application.xml
中配置参数,
提供了两个参数
webmvc.version.mapping.header-key
表示请求中的版本号字段放在哪个key里, 默认值为version
webmvc.version.mapping.clazz
表示启用的版本判断逻辑是什么, 目前提供了两种判断, 一种是int型,IntegerVersionRequestCondition
另一种是String型,StringVersionRequestCondition
, 默认是型
int型和string型的区别:
int型对 数字编号类型的版本号生效
string型对 形如 2.1.2 类型的版本号生效, 不限制.
的位数 - 对需要版本号控制的接口替换注释
@PostMappingWithVersion
(目前只对POST做了支持), 在minVersion
,maxVersion
属性上加上版本号上下限, 可选加order
排序, 数字越小优先级越高 - 客户端请求接口测试, 通过请求头实现路由, 若版本号不在匹配规则里, 则会返回404.版本号规则左右都为闭区间
效果
- 不影响不加控制的接口
- 加控制的接口支持版本号匹配
- 某个请求匹配到多个适配的接口时, 支持通过order参数排序, 数字越小优先级越高, 注: 排序只在除版本号外的属性完全相同的情况下才生效
实现方案
大概的流程思路如下:
- 先写一个自己的注解, 类似@RequestMapping这种, 让版本规则能作为参数写入注解
- 通过spring mvc代码中预留的customCondition实现版本号匹配和校验规则, 主要是实现getMatchingCondition和compareTo方法, 为了便于扩展, 这里实现一个抽象层的版本号控制器. 具体的匹配规则和校验等规则预留接口供子类实现.
- 通过实现RequestMappingHandlerMapping中的getCustomMethodCondition方法, 将实现好的CustomCondition注入到HandlerMapping., 为了便于扩展, 具体注入哪种类型的customCondition支持动态扩展. 通过获取class的方式反射注入.
- 通过WebMvcRegistrations注入自实现的RequestMappingHandlerMapping,
- 通过@Import编写注解开关类实现类似@EnableXxx功能的注解
- 编写一个抽象的VersionRequestCondition类, 支持扩展和切换version匹配规则.
源码
代码比较多, 直接上传github了
https://github.com/sunwenjieIT/spring-boot-webmvc-version-mapping
后面讲下比较核心的几个点
重要逻辑点
- 怎么避免相同的url在注入容器时不报错?
其实只需要实现RequestCondition接口, 把版本号信息放入
正常情况下 两个完全相同的url(所有的其他http参数都一致, 如是否post/get, 对应的请求头等等), 在启动时就会报错, 提示你重复了. 这个重复的校验是在注入过程中校验的.
AbstractHandlerMethodMapping.MappingRegistry.assertUniqueMethodMapping(HandlerMethod newHandlerMethod, T mapping)
这个方法负责实际的校验工作. 做两件事.
1. 先查注册的url是否有之前已经注册的HandlerMethod
2. 如果发现之前已经注册了一个HandlerMethod, 那比对一下两者是否一致.
3. 步骤2中结果若比对一致, 报错, 提示mapping重复注册.
有两个部分的代码要重点看
第一个是T的这个mapping, 作为map的key, 他的hash方法是被重写过的. T的类型根据接口的种类来区分, 种类说的是接入的方式, 比如xml配置的, 从@Controller来的等等, 通常接口通过@Controller来的则T类型为 RequestMappingInfo. 他的hash被重写成了如下形式
@Override
public int hashCode() {
return (this.patternsCondition.hashCode() * 31 + // primary differentiation
this<