Spring Boot ApiVersion版本增强
admin 发布于:2022-05-09 10:09:56
阅读:loading
最初见到ApiVersion还是在拉取了swagger-bootstrap-ui项目源代码后查看了其中的示例代码,发现了有个@ApiVersion的东西,搁现在已经记不起当时到底是发现了什么新大陆,反正是在一番好奇的想法中去百科了一大堆资料,东拼西凑的各种组合,最终就有了本文的实践结果。所以本次所讲的ApiVersion是指通过注解参数来给Controller的请求地址前缀增加版本编号的实现,请注意通过代码注解实现,并非采用手动编写地址的方式(实际上在工作的项目代码中也有遇到类似的场景,但是都是手动/v1的形式编写),毕竟专业的人采用专业的方式做专业的事情。
本文所述的ApiVersion的实现是通过覆盖webmvc的ApiRequestMappingHandlerMapping来实现扩展RequestMapping路径的目的,提供@ApiVersion注解来编写具体的Api版本号,分别可标记为class类层级、method方法层级和二者并存时,详细如下:
(1)当标记为class层级时,表示该类的requestMapping路径前统一增加以“v” + 版本号,该类中的所有方法前缀均增加次部分版本号,如:“/v1/user”;
(2)当标记为method层级时,表示该类中的当前方法的访问路径增加上述规则,同上;
(3)当标记在class和method同时被标记时除了当前类的所有方法增加上述规则时,会增加方法中的版本号,即“v” + 版本号 + "/" + 版本号,如:“/v1/1/user”;
引入一段常规代码示例进行初步展示,参考如下图所示:
上图代码是普通的Spring MVC的Controller代码,集成了swagger组件的Api文档,提供了两个Get类型的Restful接口,它们的接口地址都是“/api_version/integer”,正常情况该写法会导致导致服务启动失败,原因是因为它们的接口类型和接口地址相同,但需注意在35行增加了@ApiVersion,该注解的实现即是将方法的RequestMapping路径覆盖了,即路径为“/v3/integer”,所以可以正常启动。
本次示例转换编写了类的维度、方法维度和类与方法并存时的三种场景的示例来演示接口的路径,参考如下图所示:
(类维度的@ApiVersion)
(类与方法两个维度的@ApiVersion)
package cn.chendd.base.api.version.commponents;
import cn.chendd.base.api.version.annotations.ApiVersion;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.web.servlet.mvc.condition.*;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
import java.lang.reflect.Method;
/**
* ApiRequestMapping重写
*
* @author chendd
* @date 2019/10/16 22:34
*/
public class ApiRequestMappingHandlerMapping extends RequestMappingHandlerMapping {
/**
* 加入版本控制
*/
@Override
protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) {
RequestMappingInfo info = super.getMappingForMethod(method, handlerType);
if (info != null) {
return createApiVersionInfo(method, handlerType, info);
}else{
return info;
}
}
@Override
protected RequestCondition<ApiVesrsionCondition> getCustomTypeCondition(Class<?> handlerType) {
ApiVersion apiVersion = (ApiVersion) AnnotationUtils.findAnnotation(handlerType, ApiVersion.class);
return createCondition(apiVersion);
}
@Override
protected RequestCondition<ApiVesrsionCondition> getCustomMethodCondition(Method method) {
ApiVersion apiVersion = (ApiVersion) AnnotationUtils.findAnnotation(method, ApiVersion.class);
return createCondition(apiVersion);
}
private RequestMappingInfo createApiVersionInfo(Method method, Class<?> handlerType, RequestMappingInfo info) {
boolean hasType = false;
ApiVersion typeAnnotation = AnnotationUtils.findAnnotation(handlerType, ApiVersion.class);
if (typeAnnotation != null) {
hasType = true;
}
ApiVersion methodAnnotation = AnnotationUtils.findAnnotation(method, ApiVersion.class);
if (methodAnnotation != null) {
RequestCondition<?> methodCondition = getCustomMethodCondition(method);
//
String value = !hasType ? ("v" + methodAnnotation.value()) : methodAnnotation.value();
info = createApiVersionInfo(value, methodCondition).combine(info);
}
//如果方法上增加有版本号,再判断类上是否存在版本号,如果存在则结果为:/类版本/方法版本
//如果不需要实现版本路径叠加则将此处修改为 if - else 逻辑即可
if (hasType) {
RequestCondition<?> typeCondition = getCustomTypeCondition(handlerType);
info = createApiVersionInfo("v" + typeAnnotation.value(), typeCondition).combine(info);
}
return info;
}
private RequestMappingInfo createApiVersionInfo(String value , RequestCondition<?> customCondition) {
//多组地址映射路径
String[] patterns = new String[]{ value };
return new RequestMappingInfo(
/*new PatternsRequestCondition(patterns, getUrlPathHelper(), getPathMatcher(), useSuffixPatternMatch(),
useTrailingSlashMatch(), getFileExtensions()),*/
new PatternsRequestCondition(patterns , getUrlPathHelper() , getPathMatcher() , useTrailingSlashMatch()),
new RequestMethodsRequestCondition(), new ParamsRequestCondition(),
new HeadersRequestCondition(), new ConsumesRequestCondition(),
new ProducesRequestCondition(), customCondition);
}
private RequestCondition<ApiVesrsionCondition> createCondition(ApiVersion apiVersion) {
return apiVersion == null ? null : new ApiVesrsionCondition(apiVersion.value());
}
}
源码工程下载:源码下载.zip;
点赞