在进行REST接口的开发中,如果项目不断的进行迭代开发,需求不断的变化,会出现不同的版本,一个接口版本1和版本2的业务逻辑可能完全不同,但是又需要兼容之前的版本,我们可能不能在之前的接口进行修改,只能重新另外一个版本的接口,那该如何实现了?目前有几种方法,常见的有:一种是在url中加入版本号,第二种是在请求头中加入版本号。下面我给出一个小demo,基于在请求的url中加入版本号,扩展可以根据自己的需要。
新建一个springboot项目,pom.xml的配置如下:
4.0.0
com.jack
springboot_version
0.0.1-SNAPSHOT
jar
springboot_version
Demo project for Spring Boot
org.springframework.boot
spring-boot-starter-parent
2.0.4.RELEASE
UTF-8
UTF-8
1.8
org.springframework.boot
spring-boot-starter-web
org.springframework.boot
spring-boot-starter-test
test
org.springframework.boot
spring-boot-maven-plugin
配置文件如下:
server:
port: 9090
package com.jack.springboot_version.annotation;
import org.springframework.web.bind.annotation.Mapping;
import java.lang.annotation.*;
/**
* create by jack 2018/8/19
*
* @auther jack
* @date: 2018/8/19 10:44
* @Description:
*版本控制注解
*/
@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Mapping
public @interface ApiVersion {
/**
* 标识版本号
* @return
*/
int value();
}
package com.jack.springboot_version.config;
import org.springframework.lang.Nullable;
import org.springframework.web.servlet.mvc.condition.RequestCondition;
import javax.servlet.http.HttpServletRequest;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* create by jack 2018/8/19
*
* @auther jack
* @date: 2018/8/19 10:57
* @Description:
*/
public class ApiVersionCondition implements RequestCondition {
// 路径中版本的前缀, 这里用 /v[1-9]/的形式
private final static Pattern VERSION_PREFIX_PATTERN = Pattern.compile("v(\\d+)/");
/**
* api的版本
*/
private int apiVersion;
public ApiVersionCondition(int apiVersion) {
this.apiVersion = apiVersion;
}
/**
* 将不同的筛选条件合并
* @param apiVersionCondition
* @return
*/
@Override
public ApiVersionCondition combine(ApiVersionCondition apiVersionCondition) {
//return null;
// 采用最后定义优先原则,则方法上的定义覆盖类上面的定义
return new ApiVersionCondition(apiVersionCondition.getApiVersion());
}
/**
* 根据request查找匹配到的筛选条件
* @param httpServletRequest
* @return
*/
@Nullable
@Override
public ApiVersionCondition getMatchingCondition(HttpServletRequest httpServletRequest) {
//return null;
Matcher m = VERSION_PREFIX_PATTERN.matcher(httpServletRequest.getRequestURI());
if(m.find()){
Integer version = Integer.valueOf(m.group(1));
if(version >= this.apiVersion)
{
return this;
}
}
return null;
}
/**
* 不同筛选条件比较,用于排序
* @param apiVersionCondition
* @param httpServletRequest
* @return
*/
@Override
public int compareTo(ApiVersionCondition apiVersionCondition, HttpServletRequest httpServletRequest) {
//return 0;
// 优先匹配最新的版本号
return apiVersionCondition.getApiVersion() - this.apiVersion;
}
public int getApiVersion() {
return apiVersion;
}
}
package com.jack.springboot_version.config;
import com.jack.springboot_version.annotation.ApiVersion;
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;
/**
* create by jack 2018/8/19
*
* @auther jack
* @date: 2018/8/19 10:49
* @Description:
* 重写RequestMappingHandlerMapping类的一些方法
*/
public class CustomRequestMappingHandlerMapping extends RequestMappingHandlerMapping {
@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);
}
private RequestCondition createCondition(ApiVersion apiVersion) {
return apiVersion == null ? null : new ApiVersionCondition(apiVersion.value());
}
}
4,自定义WebMvcConfigurationSupport
核心代码如下:
package com.jack.springboot_version.config;
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
/**
* create by jack 2018/8/19
*
* @auther jack
* @date: 2018/8/19 10:50
* @Description:
*/
@SpringBootConfiguration
public class WebConfig extends WebMvcConfigurationSupport {
/**
* 重写请求过处理的方法
* @return
*/
@Override
public RequestMappingHandlerMapping requestMappingHandlerMapping() {
//return super.requestMappingHandlerMapping();
RequestMappingHandlerMapping handlerMapping = new CustomRequestMappingHandlerMapping();
handlerMapping.setOrder(0);
return handlerMapping;
}
}
1,版本1的控制器:
package com.jack.springboot_version.controller.v1;
import com.jack.springboot_version.annotation.ApiVersion;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* create by jack 2018/8/19
*
* @auther jack
* @date: 2018/8/19 10:52
* @Description:
*/
@ApiVersion(1)
@RestController
@RequestMapping("{version}/hello")
public class Hello1Controller {
@RequestMapping("/world")
public String helloWorld(){
System.out.println("版本是1的接口");
return "hello,world .version is 1";
}
}
2,版本2的控制器:
package com.jack.springboot_version.controller.v2;
import com.jack.springboot_version.annotation.ApiVersion;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* create by jack 2018/8/19
*
* @auther jack
* @date: 2018/8/19 10:52
* @Description:
*/
@ApiVersion(2)
@RestController
@RequestMapping("{version}/hello")
public class Hello2Controller {
@RequestMapping("/world")
public String helloWorld(){
System.out.println("版本是2的接口");
return "hello,world .version is 2";
}
}
使用postman进行测试:
1,测试版本1,:
测试url:http://localhost:9090/v1/hello/world
测试结果:
2,测试版本2,:
测试url:http://localhost:9090/v2/hello/world
测试结果:
源码地址
总结:通过自定义springmvc的url匹配规则,实现接口的版本控制,url增加了版本号,如果不存在高版本的版本接口则匹配代码中版本号最高的处理逻辑。使用版本号对我们项目的接口的迭代开发提供了方便。