SpringBoot⼊门系列(⼆⼗⼀)如何优雅的设计RestfulAPI接
⼝版本号,实现A。。。
有些⼈可能会问,为什么我看到很多公司的api接⼝⽂档⾥⾯,都有/api/v1/ 这样的地址呢?其实,/api 就是为了和⼀般的业务地址区分,标明这个地址是api 的接⼝。v1 则代表版本号。
可能很多⼈⼜会问了,为什么要版本号呢?那么,接下来就聊⼀聊Restful 接⼝为什么要加版本号? 如何优雅的设计 Restful API 接⼝版本号?
⼀、为什么加版本号
⼀般来说,api 接⼝是提供给其他系统或是其他公司使⽤,不能随意频繁的变更。然⽽,需求和业务不断变化,接⼝和参数也会发⽣相应的变化。如果直接对原来的接⼝进⾏修改,势必会影响线其他系统的正常运⾏。这就必须对api 接⼝进⾏有效的版本控制。
例如,添加⽤户的接⼝,由于业务需求变化,接⼝的字段属性也发⽣了变化⽽且可能和之前的功能不兼容。为了保证原有的接⼝调⽤⽅不受影响,只能重新定义⼀个新的接⼝。
Api 版本控制的⽅式:
1、域名区分管理,即不同的版本使⽤不同的域名,st,st
2、请求url 路径区分,在同⼀个域名下使⽤不同的url路径,test/api/v1/,test/api/v2
3、请求参数区分,在同⼀url路径下,增加version=v1或v2 等,然后根据不同的版本,选择执⾏不同的⽅法。
实际项⽬中,⼀般选择第⼆种:请求url路径区分。因为第⼆种既能保证⽔平扩展,有不影响以前的⽼版本。
⼆、Spring Boot如何实现
实现⽅案:
1、⾸先创建⾃定义的@APIVersion 注解和⾃定义URL匹配规则ApiVersionCondition。
2、然后创建⾃定义的 RequestMappingHandlerMapping 匹配对应的request,选择符合条件的method handler。
1、创建⾃定义注解
⾸先,在fig 包下,创建⼀个⾃定义版本号标记注解 @ApiVersion。
package fig;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* API版本控制注解
*/
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface ApiVersion {
/**
* @return 版本号
*/
int value() default 1;
}
说明:
ApiVersion 为⾃定义的注解,API版本控制,返回对应的版本号。
2、⾃定义url匹配逻辑
创建 ApiVersionCondition 类,并继承RequestCondition 接⼝,作⽤是:版本号筛选,将提取请求URL中版本号,与注解上定义的版本号进⾏⽐对,以此来判断某个请求应落在哪个controller上。
在fig 包下创建ApiVersionCondition 类,重写 RequestCondition,创建⾃定义的url匹配逻辑。
package fig;
import org.springframework.web.dition.RequestCondition;
import javax.servlet.http.HttpServletRequest;
import Matcher;
import Pattern;
public class ApiVersionCondition implements RequestCondition<ApiVersionCondition> {
private final static Pattern VERSION_PREFIX_PATTERN = Patternpile(".*v(\\d+).*");
private int apiVersion;
ApiVersionCondition(int apiVersion) {
this.apiVersion = apiVersion;
}
private int getApiVersion() {
return apiVersion;
}
@Override
public ApiVersionCondition combine(ApiVersionCondition apiVersionCondition) {
return new ApiVersion());
}
@Override
public ApiVersionCondition getMatchingCondition(HttpServletRequest httpServletRequest) {
springboot架构图Matcher m = VERSION_PREFIX_PATTERN.RequestURI());
if (m.find()) {
Integer version = Integer.up(1));
if (version >= this.apiVersion) {
return this;
}
}
return null;
}
@Override
public int compareTo(ApiVersionCondition apiVersionCondition, HttpServletRequest httpServletRequest) {
ApiVersion() - this.apiVersion;
}
}
当⽅法级别和类级别都有ApiVersion注解时,⼆者将进⾏合并(ApiVersionRequestConditionbine)。最终将提取请求URL中版本号,与注解上定义的版本号进⾏⽐对,判断url是否符合版本要求。
3、⾃定义匹配的处理器
在fig 包下创建 ApiRequestMappingHandlerMapping 类,重写部分 RequestMappingHandlerMapping 的⽅法。package fig;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.dition.RequestCondition;
import org.springframework.web.hod.annotation.RequestMappingHandlerMapping;
import flect.Method;
public class ApiRequestMappingHandlerMapping extends RequestMappingHandlerMapping {
private static final String VERSION_FLAG = "{version}";
private static RequestCondition<ApiVersionCondition> createCondition(Class<?> clazz) {
RequestMapping classRequestMapping = Annotation(RequestMapping.class);
if (classRequestMapping == null) {
return null;
}
StringBuilder mappingUrlBuilder = new StringBuilder();
if (classRequestMapping.value().length > 0) {
mappingUrlBuilder.append(classRequestMapping.value()[0]);
}
String mappingUrl = String();
if (!ains(VERSION_FLAG)) {
return null;
}
ApiVersion apiVersion = Annotation(ApiVersion.class);
return apiVersion == null ? new ApiVersionCondition(1) : new ApiVersionCondition(apiVersion.value());
}
@Override
protected RequestCondition<?> getCustomMethodCondition(Method method) {
return Class());
}
@Override
protected RequestCondition<?> getCustomTypeCondition(Class<?> handlerType) {
return createCondition(handlerType);
}
}
4、配置注册⾃定义的RequestMappingHandlerMapping
重写请求过处理的⽅法,将之前创建的 ApiRequestMappingHandlerMapping 注册到系统中。
package fig;
import org.springframework.boot.autoconfigure.web.servlet.WebMvcRegistrations;
import t.annotation.Configuration;
import org.springframework.web.hod.annotation.RequestMappingHandlerMapping;
@Configuration
public class WebMvcRegistrationsConfig implements WebMvcRegistrations {
@Override
public RequestMappingHandlerMapping getRequestMappingHandlerMapping() {
return new ApiRequestMappingHandlerMapping();
}
}
上⾯四步,把api 版本控制配置完了。代码看着复杂,其实都是重写spring boot 内部的处理流程。
测试
配置完成之后,接下来编写测试的控制器进⾏测试。
1、在Controller/api ⽬录下,分别创建UserV1Controller 和 UserV2Controller
UserV1Controller
@RequestMapping("api/{version}/user")
@RestController
public class UserV1Controller {
@GetMapping("/test")
public String test() {
return "version1";
}
@GetMapping("/extend")
public String extendTest() {
return "user v1 extend";
}
}
UserV2Controller
@RequestMapping("api/{version}/user")
@RestController
@ApiVersion(2)
public class UserV2Controller {
@GetMapping("/test")
public String test() {
return "user v2 test";
}
}
2、启动项⽬后,输⼊相关地址,查看版本控制是否⽣效
测试结果:
正确的接⼝地址
继承的接⼝地址
说明:
上图的前两个截图说明,请求正确的版本地址,会⾃动匹配版本的对应接⼝。当请求的版本⼤于当前版本时,默认匹配当前版本。
第三个截图说明,当请求对应的版本不存在接⼝时,会匹配之前版本的接⼝,即请求/v2/user/extend 接⼝时,由于v2 控制器未实现该接⼝,所以⾃动匹配v1 版本中的接⼝。这就是所谓的版本继承。
最后
以上,就把Spring Boot 如何优雅的设计 Restful API 接⼝版本号,实现 API 版本控制介绍完了。版本控制和权限验证是rest api 的基础,虽然看着⽐较复杂,但是理解了,要实现还是⽐较简单的。
这个系列课程的完整源码,也会提供给⼤家。⼤家关注我的(架构师精进),回复:springboot源码。获取这个系列课程的完整源码。
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论