暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

Springboot如何优雅的控制版本号

码酱 2021-06-29
2578

一、为什么需要版本号

 一般来说,api 接口是提供给其他系统或是其他公司使用,不能随意频繁的变更。然而,需求和业务不断变化,接口和参数也会发生相应的变化。如果直接对原来的接口进行修改,势必会影响其他系统的正常运行。这就必须对api 接口进行有效的版本控制。

例如,添加用户的接口,由于业务需求变化,接口的字段属性也发生了变化而且可能和之前的功能不兼容。为了保证原有的接口调用方不受影响,只能重新定义一个新的接口。

  • http://localhost:8080/api/v1/user

  • http://localhost:8080/api/v2/user

Api 版本控制的方式:

  • 域名区分管理,即不同的版本使用不同的域名,v1.api.test.com,v2.api.test.com

  • 请求url 路径区分,在同一个域名下使用不同的url路径,test.com/api/v1/,test.com/api/v2

  • 请求参数区分,在同一url路径下,增加version=v1或v2 等,然后根据不同的版本,选择执行不同的方法。

实际项目中,一般选择第二种:请求url路径区分。因为第二种既能保证水平扩展,又不影响以前的老版本。

二、自定义注解实现版本控制

大致实现方案:

  • 首先,创建自定义的MyApiVersion注解和启动注解EnableMyApiVersion

  • 然后,自定义url匹配逻辑MyApiVersionCondition,自定义匹配处理器CustomRequestMappingHandlerMapping

  • 最后配置注册自定义的RequestMappingHandlerMapping

1.自定义版本控制注解和启动注解

@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(value = RetentionPolicy.RUNTIME)
@Documented
public @interface MyApiVersion {
    /**
     * 标识版本号 默认走V1版本
     * @return
     */

    int value() default 1;
}

复制
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(ApiAutoConfiguration.class)
@Documented
public @interface EnableMyApiVersion 
{
}

复制
2.自定义url匹配逻辑,自定义匹配处理器

创建 MyApiVersionCondition 类,并继承RequestCondition 接口,作用是:版本号筛选,将提取请求URL中版本号,与注解上定义的版本号进行比对,以此来判断某个请求应落在哪个controller方法上。

自定义匹配处理器,重写部分 RequestMappingHandlerMapping 的方法。

public class MyApiVersionCondition implements RequestCondition<MyApiVersionCondition{

    // 路径中版本的前缀, 这里用 /v[1-9]/的形式
    private final static Pattern VERSION_PREFIX_PATTERN = Pattern.compile("v(\\d+)/");
    private int apiVersion;

    public MyApiVersionCondition(int apiVersion) {
        this.apiVersion = apiVersion;
    }

    @Override
    public MyApiVersionCondition combine(MyApiVersionCondition other) {
        // 采用最后定义优先原则,则方法上的定义覆盖类上面的定义
        return new MyApiVersionCondition(other.getApiVersion());
    }

    @Override
    @Nullable
    public MyApiVersionCondition getMatchingCondition(HttpServletRequest request) {
        Matcher m = VERSION_PREFIX_PATTERN.matcher(request.getRequestURI());
        if (m.find()) {
            Integer version = Integer.valueOf(m.group(1));
            if (version >= this.apiVersion) {
                return this;
            }
        }
        return null;
    }

    @Override
    public int compareTo(MyApiVersionCondition other, HttpServletRequest request) {
        // 优先匹配最新的版本号
        return other.getApiVersion() - this.apiVersion;
    }

    public int getApiVersion() {
        return apiVersion;
    }
}

复制


public class CustomRequestMappingHandlerMapping extends RequestMappingHandlerMapping {

    @Override
    protected RequestCondition<?> getCustomTypeCondition(Class<?> handlerType) {
        MyApiVersion apiVersion = AnnotationUtils.findAnnotation(handlerType, MyApiVersion.class);
        return createCondition(apiVersion);
    }

    @Override
    protected RequestCondition<?> getCustomMethodCondition(Method method) {
        MyApiVersion apiVersion = AnnotationUtils.findAnnotation(method, MyApiVersion.class);
        return createCondition(apiVersion);
    }

    private RequestCondition<MyApiVersionCondition> createCondition(MyApiVersion apiVersion) {
        return apiVersion == null ? null : new MyApiVersionCondition(apiVersion.value());
    }
}

复制

3.配置注册自定义的RequestMappingHandlerMapping

重写请求过处理的方法,将之前创建的 CustomRequestMappingHandlerMapping 注册到系统中。

public class ApiAutoConfiguration implements WebMvcRegistrations {

    @Override
    public RequestMappingHandlerMapping getRequestMappingHandlerMapping() {
        return new CustomRequestMappingHandlerMapping();
    }
}

复制
  • ApiAutoConfiguration没有使用Configuration自动注入,而是使用Import带入,目的是可以在程序中选择性启用或者不启用版本控制。

三、测试

1.测试Controller

@Slf4j
@RestController
@RequestMapping("/diku")
public class VersionController {

    @MyApiVersion
    @GetMapping("/{version}/test")
    public String myVersion() {
        log.info("版本控制测试!----V1");
        return "V1测试成功";
    }

    @MyApiVersion(2)
    @GetMapping("/{version}/test")
    public String myVersionTwo() {
        log.info("版本控制测试!----V2");
        return "V2测试成功";
    }
}

复制

   启动类加上启动版本控制注解

@EnableMyApiVersion
@SpringBootApplication
public class ApiApplication {
    public static void main(String[] args) {
        SpringApplication.run(ApiApplication.classargs);
    }
}

复制

2.使用postman进行测试 

这两个截图说明,请求正确的版本地址,会自动匹配版本的对应接口。

这个截图说明,当请求的版本大于当前版本时,默认匹配当前版本。


以上,就把Spring Boot 如何优雅的设计 Restful API 接口版本号,实现 API 版本控制介绍完了!主要就是梳理Springmvc的执行流程中的handlerMapping。

文章转载自码酱,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论