好得很程序员自学网

<tfoot draggable='sEl'></tfoot>

SpringBoot API增加version版本号方式

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

查看更多关于SpringBoot API增加version版本号方式的详细内容...

  阅读:18次