zoukankan      html  css  js  c++  java
  • Spring MVC 实现REST风格API版本控制

    Spring MVC 实现REST风格API版本控制

    项目的开发会是一个迭代一直更新的过程,从软件工程的角度说,除非项目进入废弃,否则项目从开始到维护升级都是一个不断更新的项目。项目的版本更新和升级这个概念很好理解,但是实施起来切实是一个痛点。现在大部分的系统后端架构通常都是基于一个网关对外暴露API接口,这些API包括网页端API接口,APP端接口和小程序端接口等。

    后端项目的升级可以通过我们自己来升级,但是对于用户而言,类似APP或者PC端程序用户而言,更新程序与后端接口达成一致性是很困难的,特别是你的项目群体特别大的时候,没有出现特别大的后端版本更新的时候,通常都只是提示用户更新而已。所以会造成部分用户更新至最新的版本,而部分用户还停留在旧版本中。所以一个接口操作会存在不同版本客户端请求。相比PC端应用程序和移动端应用程序,C/S架构的应用程序,可以做到及时更新,但是也会存在一个问题,

    • 对于当前的接口,Web端API更新到最新了,但是移动端或者PC端用户还没有更新上来。

    所以系统的更新同样需要考虑接口的版本问题。本文章综合网络上的各种解决方案,写一个基于Spring MVC的REST风格的API版本方法。

    上代码:

    定义一个Api版本的注解

    
    /**
     * @author shaoyayu
     * @date 2021/12/10
     * @apiNote api接口的注解
     */
    @Target({ElementType.METHOD,ElementType.TYPE})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Mapping
    public @interface ApiVersion {
        int value();
    }
    
    

    定义一个类实现RequestCondition

    
    /**
     * @author shaoyayu
     * @date 2021/12/10
     * @apiNote
     */
    @Getter
    @Setter
    @Slf4j
    public class ApiVersionCondition implements RequestCondition<ApiVersionCondition> {
    
        private static final Pattern VERSION_PREFIX_PATTERN = Pattern.compile("v(\\d+)/");
    
        private int apiVersion;
    
        public ApiVersionCondition(int apiVersion) {
            this.apiVersion = apiVersion;
        }
    
        @Override
        public ApiVersionCondition combine(ApiVersionCondition apiVersionCondition) {
            return new ApiVersionCondition(apiVersionCondition.getApiVersion());
        }
    
        @Override
        public ApiVersionCondition getMatchingCondition(HttpServletRequest request) {
            try {
                Matcher m = VERSION_PREFIX_PATTERN.matcher(request.getRequestURI());
                if (m.find()){
                    int version = Integer.parseInt(m.group(1));
                    if (version>=this.apiVersion){
                        return this;
                    }
                }
                return null;
            }catch (Exception e){
                log.info("api 版本转换异常:"+request.getRequestURI());
            }
            return null;
        }
    
        @Override
        public int compareTo(ApiVersionCondition other, HttpServletRequest request) {
            return other.getApiVersion() - this.apiVersion;
        }
    }
    
    

    到这里已经完成一半了,但是需要一个继承RequestMappingHandlerMapping的实现类

    
    /**
     * @author shaoyayu
     * @date 2021/12/10
     * @apiNote
     */
    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());
        }
    }
    
    
    

    剩下的就是把这个实现RequestMappingHandlerMapping的CustomRequestMappingHandlerMapping注入到容器中。

    有两种方法实现注册。一通过配置类里面,注入

    
    /**
     * @author shaoyayu
     * @date 2021/12/10
     * @apiNote
     */
    @Configuration
    public class WebConfiguration extends WebMvcAutoConfiguration {
    
        @Bean
        public RequestMappingHandlerMapping requestMappingHandlerMapping(){
            RequestMappingHandlerMapping handlerMapping = new CustomRequestMappingHandlerMapping();
            handlerMapping.setOrder(0);
            return handlerMapping;
        }
    
    }
    
    

    这种方式注入Spring Boot版本越高也是不推荐这种方法。

    第二种方法,通过继承WebMvcConfigurationSupport,在createRequestMappingHandlerMapping方法中返回

    /**
     * @author shaoyayu
     * @date 2021/12/10
     * @apiNote
     */
    @Configuration
    public class CustomWebMvcConfigurationSupport extends WebMvcConfigurationSupport {
        @Override
        protected RequestMappingHandlerMapping createRequestMappingHandlerMapping() {
            RequestMappingHandlerMapping  handlerMapping = new CustomRequestMappingHandlerMapping();
            handlerMapping.setOrder(0);
            return handlerMapping;
        } 
    }
    
    
    

    个人推荐第二种方式。

    测试

    定义一个UserController

    
    /**
     * @author shaoyayu
     * @date 2021/12/10
     * @apiNote SonarLint: Remove this empty class, write its code or make it an "interface".
     */
    @Slf4j
    @RestController
    @RequestMapping("{version}/web/user")
    public class UserController {
    
        @GetMapping("/hello")
        public Map<String,Object> hello(){
            Map<String, Object> map = new HashMap<>();
            map.put("code",200);
            map.put("ok",true);
            map.put("msg","v api interface");
            map.put("data",null);
            return map;
        }
    
        @GetMapping("/hello")
        @ApiVersion(1)
        public Map<String,Object> hello1(){
            Map<String, Object> map = new HashMap<>();
            map.put("code",200);
            map.put("ok",true);
            map.put("msg","v1 api interface");
            map.put("data",null);
            return map;
        }
    
        @GetMapping("/hello")
        @ApiVersion(2)
        public Map<String,Object> hello2(){
            Map<String, Object> map = new HashMap<>();
            map.put("code",200);
            map.put("ok",true);
            map.put("msg","v2 api interface");
            map.put("data",null);
            return map;
        }
    
    }
    
    

    测试的结果

    • 如访问/web/user/hello不带版本号,出现404的结果

    • 访问/v/web/user/hello会调用第一个方法

    • 访问/v1/web/user/helo会调用第二个方法。

    • 访问/v2/web/user/helo会调用第三个方法。

    • 访问/v3/web/user/hello会调用第三个方法。

    综合的测试结果,

    1. 不带v的版本控制会出现404

    2. 版本号会向下访问比自己更低一级的版本

    记得加油学习哦^_^
  • 相关阅读:
    异步编程
    写代码写至最有面向对象味道
    GitHub上整理
    用CQRS+ES实现DDD
    前端开发
    让低版本的IE浏览器 强制渲染为IE8 或者 以上 浏览器模式
    NHibernate系列
    hadoop搭建开发环境及编写Hello World
    Linux date -s(转)
    即时编译和打包您的 Groovy 脚本(转)
  • 原文地址:https://www.cnblogs.com/shaoyayu/p/15669965.html
Copyright © 2011-2022 走看看