spring mvc 中对API接口进行版本管理

场景

使用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;
		}

	}

}

转载于:https://my.oschina.net/yqz/blog/847944

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值