好得很程序员自学网

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

SpringBoot如何实现接口版本控制

SpringBoot 接口版本控制

一个系统在上线后会不断迭代更新,需求也会不断变化,有可能接口的参数也会发生变化,如果在原有的参数上直接修改,可能会影响到现有项目的正常运行,这时我们就需要设置不同的版本,这样即使参数发生变化,由于老版本没有变化,因此不会影响上线系统的运行。

这里我们选择使用带有一位小数的浮点数作为版本号,在请求地址末尾中带上版本号,大致的地址如:http://api/test/1.0,其中,1.0即代表的是版本号。具体做法请看代码

自定义一个版本号的注解接口ApiVersion.java

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

import org.springframework.web.bind.annotation.Mapping;

import java.lang.annotation.*;

/**

  * 版本控制

  * @author Zac

  */

@Target ({ElementType.METHOD, ElementType.TYPE})

@Retention (RetentionPolicy.RUNTIME)

@Documented

@Mapping

public @interface ApiVersion {

     /**

      * 标识版本号

      * @return

      */

     double value();

}

版本号筛选器ApiVersionCondition

?

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

import org.springframework.web.servlet.mvc.condition.RequestCondition;

import javax.servlet.http.HttpServletRequest;

import java.util.regex.Matcher;

import java.util.regex.Pattern;

/**

  * 版本号匹配筛选器

  * @author Zac

  */

public class ApiVersionCondition implements RequestCondition<ApiVersionCondition> {

 

     /**

      * 路径中版本的正则表达式匹配, 这里用 /1.0的形式

      */

     private static final Pattern VERSION_PREFIX_PATTERN = Pattern.compile( "^\\S+/([1-9][.][0-9])$" );

     private double apiVersion;

      public ApiVersionCondition( double apiVersion) {

         this .apiVersion = apiVersion;

     }

 

     @Override

     public ApiVersionCondition combine(ApiVersionCondition other) {

         // 采用最后定义优先原则,则方法上的定义覆盖类上面的定义

         return new ApiVersionCondition(other.getApiVersion());

     }

 

     @Override public ApiVersionCondition getMatchingCondition(HttpServletRequest request) {

          Matcher m = VERSION_PREFIX_PATTERN.matcher(request.getRequestURI());

         if (m.find()) {

             Double version = Double.valueOf(m.group( 1 ));

             // 如果请求的版本号大于配置版本号, 则满足

             if (version >= this .apiVersion) {

                 return this ;

             }

         }

         return null ;

     }

 

     @Override

     public int compareTo(ApiVersionCondition other, HttpServletRequest request) {

         // 优先匹配最新的版本号

         return Double.compare(other.getApiVersion(), this .apiVersion);

     }

     public double getApiVersion() {

         return apiVersion;

     }

}

版本号匹配拦截器

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

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;

 

/**

  * 版本号匹配拦截器

  * @author Zac

  */

public class CustomRequestMappingHandlerMapping extends RequestMappingHandlerMapping {

      @Override protected RequestCondition<ApiVersionCondition> getCustomTypeCondition(Class<?> handlerType) {

         ApiVersion apiVersion = AnnotationUtils.findAnnotation(handlerType, ApiVersion. class );

         return createCondition(apiVersion);

     }

 

     @Override protected RequestCondition<ApiVersionCondition> getCustomMethodCondition(Method method) {

         ApiVersion apiVersion = AnnotationUtils.findAnnotation(method, ApiVersion. class );

         return createCondition(apiVersion);

     }

     private RequestCondition<ApiVersionCondition> createCondition(ApiVersion apiVersion) {

         return apiVersion == null ? null : new ApiVersionCondition(apiVersion.value());

     } 

}

配置WebMvcRegistrationsConfig

?

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.boot.SpringBootConfiguration;

import org.springframework.boot.autoconfigure.web.WebMvcRegistrationsAdapter;

import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;

 

@SpringBootConfiguration

public class WebMvcRegistrationsConfig extends WebMvcRegistrationsAdapter {

 

     @Override

     public RequestMappingHandlerMapping getRequestMappingHandlerMapping() {

         return new CustomRequestMappingHandlerMapping();

     } 

}

controller层实现

/**

      * version

      * @return

      */

     @GetMapping ( "/api/test/{version}" )

     @ApiVersion ( 2.0 )

     public String searchTargetImage() {

         return "Hello! Welcome to Version2" ;

     } 

/**

      * 多目标类型搜索,关联图片查询接口

      *

      * @param attribute

      * @return

      */

     @GetMapping ( "/api/test/{version}" )

     @ApiVersion ( 1.0 )

     public AppResult searchTargetImage() {

         return "Hello! Welcome to Version1" ;

     }

SpringBoot 2.x 接口多版本

准备将现有的接口加上版本管理,兼容以前的版本。网上一调研,发现有很多示例,但是还是存在以下两个问题。

1.大部分使用Integer作为版本号,但是通常的版本号形式为v1.0.0,

2.版本号携带在header中,对接调用不清晰。

针对以上两个问题,做如下改造。

1.自定义接口版本注解ApiVersion

后面条件映射使用equals匹配,此处是否将String变为String[]应对多个版本使用同一代码的问题。

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

package com.yugioh.api.common.core.version;

import org.springframework.web.bind.annotation.Mapping;

import java.lang.annotation.*;

/**

  * 接口版本

  *

  * @author lieber

  */

@Target ({ElementType.METHOD, ElementType.TYPE})

@Retention (RetentionPolicy.RUNTIME)

@Documented

@Mapping

public @interface ApiVersion { 

     String value() default "1.0.0" ;

}

2.请求映射条件ApiVersionCondition

?

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

package com.yugioh.api.common.core.version;

import lombok.AllArgsConstructor;

import lombok.Getter;

import lombok.extern.slf4j.Slf4j;

import org.springframework.web.servlet.mvc.condition.RequestCondition;

import javax.servlet.http.HttpServletRequest;

import java.util.Objects;

import java.util.regex.Matcher;

import java.util.regex.Pattern;

/**

  * 版本控制

  *

  * @author lieber

  */

@AllArgsConstructor

@Getter

@Slf4j

public class ApiVersionCondition implements RequestCondition<ApiVersionCondition> {

     private String version;

     private final static Pattern VERSION_PREFIX_PATTERN = Pattern.compile( ".*v(\\d+(.\\d+){0,2}).*" );

     public final static String API_VERSION_CONDITION_NULL_KEY = "API_VERSION_CONDITION_NULL_KEY" ;

     @Override

     public ApiVersionCondition combine(ApiVersionCondition other) {

         // 方法上的注解优于类上的注解

         return new ApiVersionCondition(other.getVersion());

     }

     @Override

     public ApiVersionCondition getMatchingCondition(HttpServletRequest request) {

         Matcher m = VERSION_PREFIX_PATTERN.matcher(request.getRequestURI());

         if (m.find()) {

             String version = m.group( 1 );

             if ( this .compareTo(version)) {

                 return this ;

             }

         }

         // 将错误放在request中,可以在错误页面明确提示,此处可重构为抛出运行时异常

         request.setAttribute(API_VERSION_CONDITION_NULL_KEY, true );

         return null ;

     }

     @Override

     public int compareTo(ApiVersionCondition other, HttpServletRequest request) {

         return this .compareTo(other.getVersion()) ? 1 : - 1 ;

     }

     private boolean compareTo(String version) {

         return Objects.equals(version, this .version);

     }

}

3.创建自定义匹配处理器ApiVersionRequestMappingHandlerMapping

网上大部分只重写了getCustomTypeCondition和getCustomMethodCondition方法。这里为了解决路由映射问题,重写getMappingForMethod方法,在路由中加入前缀{version},加入后路由变为/api/{version}/xxx

?

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

package com.yugioh.api.common.core.version;

import org.springframework.core.annotation.AnnotatedElementUtils;

import org.springframework.core.annotation.AnnotationUtils;

import org.springframework.web.servlet.mvc.condition.RequestCondition;

import org.springframework.web.servlet.mvc.method.RequestMappingInfo;

import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;

import java.lang.reflect.Method;

/**

  * 自定义匹配处理器

  *

  * @author lieber

  */

public class ApiVersionRequestMappingHandlerMapping extends RequestMappingHandlerMapping {

     private final static String VERSION_PREFIX = "{version}" ;

     @Override

     protected RequestCondition<?> getCustomTypeCondition(Class<?> handlerType) {

         ApiVersion apiVersion = AnnotationUtils.findAnnotation(handlerType, ApiVersion. class );

         return createCondition(apiVersion);

     }

     @Override

     protected RequestCondition<?> getCustomMethodCondition(Method method) {

         ApiVersion apiVersion = AnnotationUtils.findAnnotation(method, ApiVersion. class );

         return createCondition(apiVersion);

     }

     @Override

     protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) {

         RequestMappingInfo requestMappingInfo = super .getMappingForMethod(method, handlerType);

         if (requestMappingInfo == null ) {

             return null ;

         }

         return createCustomRequestMappingInfo(method, handlerType, requestMappingInfo);

     }

     private RequestMappingInfo createCustomRequestMappingInfo(Method method, Class<?> handlerType, RequestMappingInfo requestMappingInfo) {

         ApiVersion methodApi = AnnotatedElementUtils.findMergedAnnotation(method, ApiVersion. class );

         ApiVersion handlerApi = AnnotatedElementUtils.findMergedAnnotation(handlerType, ApiVersion. class );

         if (methodApi != null || handlerApi != null ) {

             return RequestMappingInfo.paths(VERSION_PREFIX).options( this .config).build().combine(requestMappingInfo);

         }

         return requestMappingInfo;

     }

     private RequestMappingInfo.BuilderConfiguration config = new RequestMappingInfo.BuilderConfiguration();

     private RequestCondition<ApiVersionCondition> createCondition(ApiVersion apiVersion) {

         return apiVersion == null ? null : new ApiVersionCondition(apiVersion.value());

     }

}

4.使用ApiVersionConfig配置来决定是否开启多版本

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

package com.yugioh.api.common.core.version;

import lombok.Data;

import org.springframework.boot.context.properties.ConfigurationProperties;

import org.springframework.context.annotation.Configuration;

/**

  * 版本管理器配置

  *

  * @author lieber

  */

@Data

@Configuration

@ConfigurationProperties (prefix = "api.config.version" , ignoreInvalidFields = true )

public class ApiVersionConfig {

     /**

      * 是否开启

      */

     private boolean enable;

}

5.配置WebMvcRegistrations,启用自定义路由Mapping

?

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

package com.yugioh.api.common.core.version;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.boot.autoconfigure.web.servlet.WebMvcRegistrations;

import org.springframework.context.annotation.Configuration;

import org.springframework.core.Ordered;

import org.springframework.core.annotation.Order;

import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;

/**

  * @author lieber

  */

@Configuration

@Order (Ordered.HIGHEST_PRECEDENCE)

public class ApiWebMvcRegistrations implements WebMvcRegistrations {

     private final ApiVersionConfig apiVersionConfig;

     @Autowired

     public ApiWebMvcRegistrations(ApiVersionConfig apiVersionConfig) {

         this .apiVersionConfig = apiVersionConfig;

     }

     @Override

     public RequestMappingHandlerMapping getRequestMappingHandlerMapping() {

         if (apiVersionConfig.isEnable()) {

             return new ApiVersionRequestMappingHandlerMapping();

         }

         return null ;

     }

}

至此我们就能在项目中愉快的使用@APIVersion来指定版本了,但是现在这个和swagger整合还会有问题,继续研究中…

以上为个人经验,希望能给大家一个参考,也希望大家多多支持。

原文链接:https://blog.csdn.net/asd120829243/article/details/90175930

查看更多关于SpringBoot如何实现接口版本控制的详细内容...

  阅读:22次