场景
使用spring mvc定义http api接口,通过在接口URL中指定版本号以达到使相同的接口支持不同的业务的目的。
比如,有一个index接口用于获取index信息,希望该接口可以通过URL中包含的版本号提供不同的返回结果。接口URL格式为 /{version:\\d{1,5}}/api/hello/index,如
- /1/api/hello/index
- /2/api/hello/index
实现
通过在controller类和方法上添加注解@RequestMapping和@APIVersion,分别指定接口URL和版本号要求。DispatcherServlet的内部逻辑解析request,查找匹配的method handler。首先会查找@RequestMapping注解过的method handler是否与request匹配(检查request的path、method、header等),然后检查method handler的@ApiVersion注解的版本号是否与request url中的版本号匹配,最终选择符合条件的method handler。
- HelloController类
定义接口URL和版本号
@RestController
@RequestMapping(value = "/{version:\\d{1,2}}/api/hello")
@ApiVersion(2)
public class HelloController {
@RequestMapping(value="/index",method = RequestMethod.GET)
public String index() {
return "hello,this is v2";
}
@ApiVersion(value = 1, weight = 1)
@RequestMapping(value="/index",method = RequestMethod.GET)
public String index_2() {
return "hello,this is v1";
}
}
- ApiVersion类
定义版本号结构,包括版本号和权重。当class和method都有版本号注解时,以weight值最大的版本号为准。
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ApiVersion {
int value() default 1;
int weight() default 0;
}
- RequestMappingHandlerMapping类
重写RequestMappingHandlerMapping的获取自定义Condition方法(包括方法级别和类级别的),当方法和类上有ApiVersion注解时,提取版本号信息。
RequestMappingHandlerMapping m = new RequestMappingHandlerMapping() {
@Override
protected RequestCondition<?> getCustomMethodCondition(Method method) {
ApiVersion v = AnnotationUtils.findAnnotation(method, ApiVersion.class);
if (v == null)
return null;
return new ApiVersionRequestCondition(new ApiVersionExpression(v.value(), v.weight()));
}
@Override
protected RequestCondition<?> getCustomTypeCondition(Class<?> handlerType) {
ApiVersion v = AnnotationUtils.findAnnotation(handlerType, ApiVersion.class);
if (v == null)
return null;
return new ApiVersionRequestCondition(new ApiVersionExpression(v.value(), v.weight()));
}
};
- ApiVersionRequestCondition类
RequestCondition类的子类,用于本次重写的自定义condition方法的返回结果定义。当方法级别和类级别都有ApiVersion注解时,二者将进行合并(ApiVersionRequestCondition.combine)。最终将提取请求URL中版本号,与注解上定义的版本号进行比对,判断url是否符合版本要求。
public final class ApiVersionRequestCondition extends AbstractRequestCondition<ApiVersionRequestCondition> {
private Set<ApiVersionExpression> versions;
public ApiVersionRequestCondition(ApiVersionExpression... version) {
this.versions = Collections.unmodifiableSet(new LinkedHashSet<ApiVersionExpression>(Arrays.asList(version)));
}
public ApiVersionRequestCondition(Collection<ApiVersionExpression> versions) {
if (versions == null)
this.versions = Collections.unmodifiableSet(new LinkedHashSet<ApiVersionExpression>());
this.versions = Collections.unmodifiableSet(new LinkedHashSet<ApiVersionExpression>(versions));
}
@Override
public ApiVersionRequestCondition combine(ApiVersionRequestCondition other) {
Set<ApiVersionExpression> set = new LinkedHashSet<ApiVersionExpression>(this.versions);
set.addAll(other.versions);
return new ApiVersionRequestCondition(set);
}
@Override
public ApiVersionRequestCondition getMatchingCondition(HttpServletRequest request) {
UrlPathHelper urlPathHelper = new UrlPathHelper();
String url = urlPathHelper.getLookupPathForRequest(request);
Pattern regex = Pattern.compile("^\\/?(\\d{1,2})");
Matcher m = regex.matcher(url);
int version = 0;
if (m.find()) {
if (m.groupCount() > 0) {
version = Integer.parseInt(m.group(1));
}
}
ApiVersionExpression annotationVersion = getMaxApiVersionExpression(this.versions);
if (version == annotationVersion.version)
return new ApiVersionRequestCondition(annotationVersion);
return null;
}
private ApiVersionExpression getMaxApiVersionExpression(Collection<ApiVersionExpression> collection) {
Optional<ApiVersionExpression> m = this.versions.stream().max(ApiVersionExpression::compareTo);
if (!m.isPresent())
return null;
return m.get();
}
@Override
public int compareTo(ApiVersionRequestCondition other, HttpServletRequest request) {
ApiVersionExpression m = getMaxApiVersionExpression(this.versions);
ApiVersionExpression n = getMaxApiVersionExpression(other.versions);
if (m != null && n != null) {
return m.compareTo(n);
} else if (m == null)
return 1;
else if (n == null)
return -1;
else
return 0;
}
@Override
protected Collection<?> getContent() {
return this.versions;
}
@Override
protected String getToStringInfix() {
return "||";
}
public static class ApiVersionExpression implements Comparable<ApiVersionExpression> {
private int version;
private int weight;
public ApiVersionExpression(int version, int order) {
this.version = version;
this.weight = order;
}
public int getVersion() {
return version;
}
public void setVersion(int version) {
this.version = version;
}
public int getWeight() {
return weight;
}
public void setWeight(int order) {
this.weight = order;
}
@Override
public int compareTo(ApiVersionExpression o) {
if (this.weight == o.weight)
return this.weight - o.weight;
else if(this.weight > o.weight)
return 1;
return -1;
}
}
}