1. 程式人生 > >SpingMVC框架實現restfull介面的版本控制

SpingMVC框架實現restfull介面的版本控制

網際網路發展到今天,基於restfull開發的系統也越來越多,不再侷限於jsp等指令碼語言來實現動態資料的展示,而是通過後臺提供的http介面給前端呼叫,但是當系統越做越大,同一個介面可能會不斷的修改,一旦呼叫方式發生改變,後果是非常嚴重的,客戶端將無法正常呼叫,除非強制客戶端升級到最新版本,這個也是不太現實的,那怎麼辦呢?我們想到的就是通過版本來控制同一個介面,類似這樣的一個地址:http://localhost:8080/api/v1,其中v1就是當前所用到的版本,那怎麼來實現呢?直接上程式碼:

首先自定義一個註解:

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
@Mapping
public @interface ApiVersion {

	/**
	 * 標識版本號
	 * @return
	 */
	int value();
}

這個註解標識當前介面所對應的版本
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import cn.sunsharp.platform.annotation.ApiVersion;
import cn.sunsharp.platform.web.BaseController;


@RequestMapping("{version}")
@RestController
public class HelloController extends BaseController {

	@ApiVersion(1)
	@RequestMapping("hello")
	public String hello1(){
		return "hello1";
	}
	
	@ApiVersion(2)
	@RequestMapping("hello")
	public String hello2(){
		return "hello2";
	}
}

這樣我們通過v1,v2這樣的地址就能訪問到對應的版本,但是這樣會有一個問題,如果客戶端傳入v3,但是所定義的版本並沒有定義v3,此時客戶端肯定會報404錯誤,也就是沒有這個頁面,怎麼辦呢,相當的就是攔截器,如果傳入的版本沒有,則預設訪問最新的版本,請看程式碼:
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.servlet.http.HttpServletRequest;

import org.springframework.web.servlet.mvc.condition.RequestCondition;

public class ApiVersionCondition implements RequestCondition<ApiVersionCondition> {

	// 路徑中版本的字首, 這裡用 /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 ApiVersionCondition combine(ApiVersionCondition other) {
        // 採用最後定義優先原則,則方法上的定義覆蓋類上面的定義
        return new ApiVersionCondition(other.getApiVersion());
    }
 
    public ApiVersionCondition 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;
    }
 
    public int compareTo(ApiVersionCondition other, HttpServletRequest request) {
        // 優先匹配最新的版本號
        return other.getApiVersion() - this.apiVersion;
    }
 
    public int getApiVersion() {
        return apiVersion;
    }
}

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;

import cn.sunsharp.platform.annotation.ApiVersion;

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

這樣,當傳入的版本沒有則預設會訪問最新的版本,還沒有完,還需要註冊這個攔截器:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;

import cn.sunsharp.platform.version.CustomRequestMappingHandlerMapping;

@Configuration
public class WebConfig extends WebMvcConfigurationSupport {

	@Override
    @Bean
    public RequestMappingHandlerMapping requestMappingHandlerMapping() {
        RequestMappingHandlerMapping handlerMapping = new CustomRequestMappingHandlerMapping();
        handlerMapping.setOrder(0);
        handlerMapping.setInterceptors(getInterceptors());
        return handlerMapping;
    }
}

如此就實現了最基本的對api的版本控制。