zoukankan      html  css  js  c++  java
  • [转]如何优雅的设计 Spring Boot API 接口版本号

    原文:https://blog.mariojd.cn/how-to-design-spring-boot-api-version-number-elegantly.html

      一般来说,系统上线以后,需求仍会发生变动,功能也会迭代更新。可能是接口参数发生变更,也有可能是业务逻辑需要调整,如果直接在原来的接口上进行修改,必然会影响原有服务的正常运行。

       常见的解决方案,是在接口路径中加入版本号用于区分,此外还可以在参数甚至 header 里带上版本号。这里以在请求路径中带上版本号为例,如:http://IP:PORT/api/v1/test ,v1 即代表的是版本号。当然了,可以像这样,直接写死在 @RequestMapping("api/v1/test") 属性中,不过下面提供了更为优雅的解决方案。

    1. 自定义版本号标记注解:

    @Target({ElementType.METHOD, ElementType.TYPE})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface ApiVersion {
    
        /**
         * 标识版本号,从1开始
         */
        int value() default 1;
    
    }

    2. 重写相应的 RequestCondition

    @Data
    @Slf4j
    public class ApiVersionCondition implements RequestCondition<ApiVersionCondition> {
    
        /**
         * 接口路径中的版本号前缀,如: api/v[1-n]/test
         */
        private final static Pattern VERSION_PREFIX_PATTERN = 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_PATTERN.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();
        }
    
    }

    3. 重写部分 RequestMappingHandlerMapping 的方法

    public class CustomRequestMappingHandlerMapping 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);
        }
    
    }

    4. 配置注册自定义的 CustomRequestMappingHandlerMapping

    @Slf4j
    @Configuration
    public class WebMvcConfig extends WebMvcConfigurationSupport {
    
        @Override
        public RequestMappingHandlerMapping requestMappingHandlerMapping() {
            return new CustomRequestMappingHandlerMapping();
        }
    
    }

    5. 编写接口,标记上相应的 @ApiVersion

    @Slf4j
    @ApiVersion
    @RestController
    @RequestMapping("api/{version}/test")
    public class TestController {
    
        @GetMapping
        public String test01(@PathVariable String version) {
            return "test01 : " + version;
        }
    
        @GetMapping
        @ApiVersion(2)
        public String test02(@PathVariable String version) {
            return "test02 : " + version;
        }
    
    }

    6. 启动 Application,测试及查看结果

  • 相关阅读:
    使用video2chars将视频转字符图
    Android调用打印机
    WPF多线程UI更新
    设计模式在JDK中的应用
    23种设计模式(概念、原则、场景、优点、缺点、应用)简述
    Bridge 、 Adapter 和 Facade 的区别
    UML类图与类的关系详解
    uml 类图依赖与关联的区别
    UML类图几种”关系“的总结
    面试官问你有什么要问的时候,大胆的提出类似问题
  • 原文地址:https://www.cnblogs.com/huanshilang/p/12097048.html
Copyright © 2011-2022 走看看