SpringBoot 增加 API Version
基于restful风格上,增加version版本号
例如: get /api/v1/users/
一、增加ApiVersion自定义注解
作用于Controller上,指定API版本号
这里版本号使用了double ,考虑到小版本的情况,例如1.1
1
2
3
4
5
6
7
8
9
10
11
12
13
|
import java.lang.annotation.*; /** * API Version type */ @Target ({ElementType.METHOD, ElementType.TYPE}) @Retention (RetentionPolicy.RUNTIME) @Documented public @interface ApiVersion { /** * api version begin 1 */ double version() default 1 ; } |
二、新增RequestCondition自定义匹配条件
Spring提供RequestCondition接口,用于定义API匹配条件
这里通过自定义匹配条件,识别ApiVersion,进行版本匹配
getMatchingCondition 用于检查URL中,是否符合/v{版本号},用于过滤无版本号接口;
compareTo 用于决定多个相同API时,使用哪个接口进行处理;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
|
import org.springframework.web.servlet.mvc.condition.RequestCondition; import javax.servlet.http.HttpServletRequest; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * API version condition * @author w * @date 2020-11-16 */ public class ApiVersionCondition implements RequestCondition<ApiVersionCondition> { /** * 接口路径中的版本号前缀,如: api/v[1-n]/test */ private final static Pattern VERSION_PREFIX_PATTERN = Pattern.compile( "/v([0-9]+\\.{0,1}[0-9]{0,2})/" ); /** API VERSION interface **/ private ApiVersion apiVersion; ApiVersionCondition(ApiVersion apiVersion){ this .apiVersion = apiVersion; } /** * [当class 和 method 请求url相同时,触发此方法用于合并url] * 官方解释: * - 某个接口有多个规则时,进行合并 * - 比如类上指定了@RequestMapping的 url 为 root * - 而方法上指定的@RequestMapping的 url 为 method * - 那么在获取这个接口的 url 匹配规则时,类上扫描一次,方法上扫描一次,这个时候就需要把这两个合并成一个,表示这个接口匹配root/method * @param other 相同api version condition * @return ApiVersionCondition */ @Override public ApiVersionCondition combine(ApiVersionCondition other) { // 此处按优先级,method大于class return new ApiVersionCondition(other.getApiVersion()); } /** * 判断是否成功,失败返回 null;否则,则返回匹配成功的条件 * @param httpServletRequest http request * @return 匹配成功条件 */ @Override public ApiVersionCondition getMatchingCondition(HttpServletRequest httpServletRequest) { // 通过uri匹配版本号 System.out.println(httpServletRequest.getRequestURI()); Matcher m = VERSION_PREFIX_PATTERN.matcher(httpServletRequest.getRequestURI()); if (m.find()) { // 获得符合匹配条件的ApiVersionCondition System.out.println( "groupCount:" +m.groupCount()); double version = Double.valueOf(m.group( 1 )); if (version >= getApiVersion().version()) { return this ; } } return null ; } /** * 多个都满足条件时,用来指定具体选择哪一个 * @param other 多个时 * @param httpServletRequest http request * @return 取版本号最大的 */ @Override public int compareTo(ApiVersionCondition other, HttpServletRequest httpServletRequest) { // 当出现多个符合匹配条件的ApiVersionCondition,优先匹配版本号较大的 return other.getApiVersion().version() >= getApiVersion().version() ? 1 : - 1 ; } public ApiVersion getApiVersion() { return apiVersion; } } |
三、重写RequestMappingHandlerMapping处理
通过重写 RequestMappingHandlerMapping 类,对RequestMappering进行识别@ApiVersion注解,针对性处理;
这里考虑到有些接口不存在版本号,则使用Spring原来的ApiVersionRequestMappingHandlerMapping继续处理;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
|
import org.springframework.core.annotation.AnnotationUtils; import org.springframework.web.servlet.mvc.condition.RequestCondition; import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping; import java.lang.reflect.Method; /** * API version setting * @author w * @date 2020-11-15 */ public class ApiVersionRequestMappingHandlerMapping extends RequestMappingHandlerMapping { /** * class condition * - 在class上加@ApiVersion注解&url加{version} * @param handlerType class type * @return ApiVersionCondition */ @Override protected RequestCondition<?> getCustomTypeCondition(Class<?> handlerType) { ApiVersion apiVersion = AnnotationUtils.findAnnotation(handlerType,ApiVersion. class ); return null == apiVersion ? super .getCustomTypeCondition(handlerType) : new ApiVersionCondition(apiVersion); } /** * method condition * - 在方法上加@ApiVersion注解&url加{version} * @param method method object * @return ApiVersionCondition */ @Override protected RequestCondition<?> getCustomMethodCondition(Method method) { ApiVersion apiVersion = AnnotationUtils.findAnnotation(method,ApiVersion. class ); return null == apiVersion ? super .getCustomMethodCondition(method) : new ApiVersionCondition(apiVersion); } } |
四、Controller接口增加@ApiVersion注解
通过@ApiVersion注解指定该接口版本号
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
|
import com.panda.common.web.controller.BasicController; import com.panda.common.web.version.ApiVersion; import com.panda.core.umc.service.UserInfoService; import com.panda.core.umc.vo.QueryUsersConditionVo; import com.panda.face.umc.dto.user.QueryUsersReq; import org.springframework.beans.BeanUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; /** * 用户信息服务 * @author w * @date 2020-11-06 */ @RequestMapping ( "/api" ) @RestController public class UserInfoController extends BasicController{ @Autowired private UserInfoService userInfoService; /** * 查询所有用户信息 * @param req 查询条件信息 */ @ApiVersion @RequestMapping (value = "{version}/users" , method = RequestMethod.GET) @ResponseBody public ResponseEntity getUsers( @PathVariable ( "version" ) String version, QueryUsersReq req){ QueryUsersConditionVo condition = new QueryUsersConditionVo(); BeanUtils.copyProperties(req,condition); condition.setOrderBy( "CREATE_TIME" ); condition.setSort( "DESC" ); return assemble( "1111" ); } /** * 查询所有用户信息 * @param req 查询条件信息 */ @ApiVersion (version = 1.1 ) @RequestMapping (value = "{version}/users" , method = RequestMethod.GET) @ResponseBody public ResponseEntity getUsersV2( @PathVariable ( "version" ) String version, QueryUsersReq req){ QueryUsersConditionVo condition = new QueryUsersConditionVo(); BeanUtils.copyProperties(req,condition); condition.setOrderBy( "CREATE_TIME" ); condition.setSort( "DESC" ); return assemble( "222" ); } /** * 根据用户ID获取用户信息 * @param userId 用户ID */ @RequestMapping (value = "/users/uid/{userId}" , method = RequestMethod.GET) @ResponseBody public ResponseEntity getUserInfo( @PathVariable ( "userId" ) String userId){ return assemble(userInfoService.selectByUserId(userId)); } } |
五、测试调用
通过访问以下URL,测试返回结果
GET http://127.0.0.1/api/v1/users
GET http://127.0.0.1/api/v1.1/users
GET http://127.0.0.1/api/v1.2/users
GET http://127.0.0.1/api/users/uid/U0001
六、总结
1.通过@ApiVersion注解方式,可以灵活指定接口版本;
2.缺点很明显,需要在URL上加入{version},才能进行匹配成功,这种PathVariable识别过于模糊,后期排查问题增加困难;
3.建议通过包名增加v1/v2明显区分版本,且在controller的URL上直接写死v1版本号,这种更直观;
SpringBoot的项目API版本控制
一、自定义版本号标记注解
1
2
3
4
5
6
7
8
9
|
@Target ({ElementType.METHOD, ElementType.TYPE}) @Retention (RetentionPolicy.RUNTIME) @Documented public @interface ApiVersion { /** * 标识版本号,从1开始 */ int value() default 1 ; } |
二、重写RequestCondition,自定义url匹配逻辑
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
|
@Data @Slf4j public class ApiVersionCondition implements RequestCondition<ApiVersionCondition> { /** * 接口路径中的版本号前缀,如: api/v[1-n]/fun */ private final static Pattern VERSION_PREFIX = Pattern.compile( "/v(\\d+)/" ); private int apiVersion; ApiVersionCondition( int apiVersion) { this .apiVersion = apiVersion; } /** * 最近优先原则,方法定义的 @ApiVersion > 类定义的 @ApiVersion */ @Override public ApiVersionCondition combine(ApiVersionCondition other) { return new ApiVersionCondition(other.getApiVersion()); } /** * 获得符合匹配条件的ApiVersionCondition */ @Override public ApiVersionCondition getMatchingCondition(HttpServletRequest request) { Matcher m = VERSION_PREFIX.matcher(request.getRequestURI()); if (m.find()) { int version = Integer.valueOf(m.group( 1 )); if (version >= getApiVersion()) { return this ; } } return null ; } /** * 当出现多个符合匹配条件的ApiVersionCondition,优先匹配版本号较大的 */ @Override public int compareTo(ApiVersionCondition other, HttpServletRequest request) { return other.getApiVersion() - getApiVersion(); } } |
说明:
getMatchingCondition方法中,控制了只有版本小于等于请求参数中的版本的 ApiCondition 才满足规则
compareTo 指定了当有多个ApiCoondition满足这个请求时,选择最大的版本
三、重写RequestMappingHandlerMapping,自定义匹配的处理器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
public class ApiRequestMappingHandlerMapping extends RequestMappingHandlerMapping { @Override protected RequestCondition<?> getCustomTypeCondition(Class<?> handlerType) { // 扫描类上的 @ApiVersion ApiVersion apiVersion = AnnotationUtils.findAnnotation(handlerType, ApiVersion. class ); return createRequestCondition(apiVersion); } @Override protected RequestCondition<?> getCustomMethodCondition(Method method) { // 扫描方法上的 @ApiVersion ApiVersion apiVersion = AnnotationUtils.findAnnotation(method, ApiVersion. class ); return createRequestCondition(apiVersion); } private RequestCondition<ApiVersionCondition> createRequestCondition(ApiVersion apiVersion) { if (Objects.isNull(apiVersion)) { return null ; } int value = apiVersion.value(); Assert.isTrue(value >= 1 , "Api Version Must be greater than or equal to 1" ); return new ApiVersionCondition(value); } } |
四、配置注册自定义WebMvcRegistrations
1
2
3
4
5
6
7
|
@Configuration public class WebMvcRegistrationsConfig implements WebMvcRegistrations { @Override public RequestMappingHandlerMapping getRequestMappingHandlerMapping() { return new ApiRequestMappingHandlerMapping(); } } |
五、编写测试接口
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
@RestController @RequestMapping ( "/api/{version}" ) public class ApiControler { @GetMapping ( "/fun" ) public String fun1() { return "fun 1" ; } @ApiVersion ( 5 ) @GetMapping ( "/fun" ) public String fun2() { return "fun 2" ; } @ApiVersion ( 9 ) @GetMapping ( "/fun" ) public String fun3() { return "fun 5" ; } } |
页面测试效果:
以上为个人经验,希望能给大家一个参考,也希望大家多多支持服务器之家。
原文链接:https://blog.csdn.net/bluewait321/article/details/110192577