一个项目上线后,会根据不同的需求不断的进行更新和维护操作。有可能接口的请求也会发生改变,比如之前 /api/v1 ,后来随着业务变更,需要使用 /api/v9,但有些接口根本就无需变更,一样沿用即可。

如何做到一个接口多个版本可以动态适用呢?

一、自定义一个注解

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.web.bind.annotation.Mapping;

@Target({ElementType.METHOD,ElementType.TYPE})//用于方法和类上
@Retention(RetentionPolicy.RUNTIME)//运行时有效
@Documented //标识这是个注解并应该被 javadoc工具记录
@Mapping //标识映射
public @interface ApiVersion {
	 /**
     * 标识版本号
     * @return
     */
    int value() default 1;//default 表示默认值
}

《@Mapping注解》

二、匹配方式

import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.servlet.http.HttpServletRequest;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.servlet.mvc.condition.RequestCondition;

public class ApiVersionCondition implements RequestCondition<ApiVersionCondition> {
	private static Logger log = LoggerFactory.getLogger(ApiVersionCondition.class);
	
	// 路径中版本的前缀, 这里用 /v[1-9]/的形式
    private final static Pattern VERSION_PREFIX_PATTERN = Pattern.compile("v(\\d+)/");
	
	//类中的成员变量,此处表示到时候接口中传递的参数接收
    private int apiVersion;
    
    //构造
    public ApiVersionCondition(int apiVersion){
        this.apiVersion = apiVersion;
    }
    
    public int getApiVersion() {
        return apiVersion;
    }
	
	//创建新的实例
    @Override
    public ApiVersionCondition combine(ApiVersionCondition other) {
        // 采用最后定义优先原则,则方法上的定义覆盖类上面的定义
        return new ApiVersionCondition(other.getApiVersion());
    }
	
	//校验请求url中是否包含指定的字段,如果存在则进行正则匹配
    @Override
    public ApiVersionCondition getMatchingCondition(HttpServletRequest request) {
    	log.info("---- getMatchingCondition ----");
        Matcher m = VERSION_PREFIX_PATTERN.matcher(request.getRequestURI());
        if(m.find()){
            Integer version = Integer.valueOf(m.group(1));
            log.info("---- getMatchingCondition ----version="+String.valueOf(version));
            //如果当前url中传递的版本信息高于(或等于)申明(或默认)版本,则用url的版本
            if(version >= this.apiVersion){
                return this;
            }
        }
        //不匹配,则不用管
        return null;
    }
	
	//重写比较方式
    @Override
    public int compareTo(ApiVersionCondition other, HttpServletRequest request) {
        // 优先匹配最新的版本号
        return other.getApiVersion() - this.apiVersion;
    }
}

三、编写配置类,加载至spring容器中

import org.springframework.boot.autoconfigure.web.servlet.WebMvcRegistrations;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;

@Configuration
public class ApiConfiguration implements WebMvcRegistrations {
	@Override
    public RequestMappingHandlerMapping getRequestMappingHandlerMapping() {
        return new CustomRequestMappingHandlerMapping();
    }
}

在父接口中,我们需要重写指定的一个方法。

JAVA 接口管理器 java接口版本管理_JAVA 接口管理器


但是,它本身返回的是一个org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping类,这个类中有很多处理请求映射的方法,但是我们需要使用几个指定的。

import java.lang.reflect.Method;

import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.web.servlet.mvc.condition.RequestCondition;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;

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());
    }
}

四、最后的操作

最后的操作当然是测试咯

4.1、编写一个测试类

@Controller
public class VersionControllerTest {
	
	@ApiVersion
	@GetMapping("/{version}/test1")
	@ResponseBody
	public String test1(){
		return "test 1";
	}
	
	@ApiVersion
	@GetMapping("/{ver}/test2")
	@ResponseBody
	public String test2(){
		return "test 2";
	}
	
	@ApiVersion(3)
	@GetMapping("/{version}/test3")
	@ResponseBody
	public String test3(){
		return "test 3";
	}
}

4.2、各项请求

4.2.1、test1()

JAVA 接口管理器 java接口版本管理_ide_02


JAVA 接口管理器 java接口版本管理_JAVA 接口管理器_03


多换几个测试

JAVA 接口管理器 java接口版本管理_ide_04


JAVA 接口管理器 java接口版本管理_ide_05

4.2.2、test3()

test3()和test1()相比,变更了一个注解。

@ApiVersion(3)

这个有什么用呢?

我们先请求 大于等于3的

JAVA 接口管理器 java接口版本管理_spring_06


JAVA 接口管理器 java接口版本管理_ide_07


换个小于3的

JAVA 接口管理器 java接口版本管理_ide_08


JAVA 接口管理器 java接口版本管理_ide_09


由此可见 @ApiVersion(3)自定义注解,传递参数信息表示默认最低的版本限制。

五、代码下载

springboot 自定义版本控制代码