SpringMvc多版本API实现
需求背景
A系统通过RestAPI提供多方APP调用,要求API升级不影响现有API的使用,客户可以根据需要灵活的选择是否需要升级。客户升级代价尽可能减少。
参考方案
URL路径控制
通过不同路径来区分版本信息如:
http://xxxxxx/v1/hello(版本1)
http://xxxxxx/v2/hello(版本2)
这种方式的主要通过建立多个文件的方式来实现,优点是结构清晰,代码简单。缺点是大量重复的工作量导致代码臃肿。且版本号显示的显示在路径上,非法客户容易获取我们的版本信息。
通过调用参数控制版本
通过参数来控制版本信息路径如:
http://xxxxxx/hello?version=1(版本1)
http://xxxxxx/hello?version=1(版本2)
相对第一种方式而言,通过参数控制增加了多种变化,如参数可以放在header中、可以放在body中,为我们多样化的API提供支持,而且可以有效的将版本信息透明化。
通过对比选择,我们决定使用参数控制的方式实现,本文也主要讲解如何实现通过参数控制API的版本。
代码明细
- 自定义一个注解来声明版本控制
@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Mapping
public @interface ApiVersion {
/**
* version
* @return
*/
int value();
}
- 重写RequestCondition
/**
* api version 条件过滤
*/
@Setter
@Getter
public class ApiVersionCondition implements RequestCondition<ApiVersionCondition> {
// extract the version part from url. example [v0-9]
private final static Pattern VERSION_PREFIX_PATTERN = Pattern.compile("v(\\d+)/");
public ApiVersionCondition(int apiVersion) {
this.apiVersion = apiVersion;
}
private int apiVersion;
@Override
public ApiVersionCondition combine(ApiVersionCondition apiVersionCondition) {
return new ApiVersionCondition(apiVersionCondition.getApiVersion());
}
@Override
public ApiVersionCondition getMatchingCondition(HttpServletRequest request) {
Integer version=null;
try{
//获取请求参数中的version信息
version= HttpContextUtils.getRequestVersion(request);
if(version!=null && version >= this.apiVersion ){
//when applying version number bigger than configuration, then it will take
return this;
}
}catch (Exception e){
Logs.error("api版本转换异常:"+e.getMessage(),e);
}
return null;
}
@Override
public int compareTo(ApiVersionCondition other, HttpServletRequest request) {
return other.getApiVersion() - this.apiVersion;
}
}
- 重写RequestMappingHandlerMapping
public class MyRequestMappingHandlerMapping extends RequestMappingHandlerMapping {
public MyRequestMappingHandlerMapping(){
System.out.println("----------------ApiVersioningRequestMappingHandlerMapping--------------------");
}
@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());
}
}
- 重写WebMvcConfigurationSupport
@Configuration
public class MyWebMvcConfigurationSupport extends WebMvcConfigurationSupport {
@Autowired
private TokenInterceptor tokenInterceptor;
@Override
protected RequestMappingHandlerMapping createRequestMappingHandlerMapping() {
MyRequestMappingHandlerMapping handlerMapping = new MyRequestMappingHandlerMapping();
handlerMapping.setOrder(0);
handlerMapping.setInterceptors(getInterceptors());
// handlerMapping.registerApiVersionCodeDiscoverer(new DefaultApiVersionCodeDiscoverer());
return handlerMapping;
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 多个拦截器组成一个拦截器链
// addPathPatterns 用于添加拦截规则
// excludePathPatterns 用户排除拦截
registry.addInterceptor(tokenInterceptor).addPathPatterns("/**/*");
// registry.addInterceptor(new ValidationInterceptor()).addPathPatterns("/api/**");
// registry.addInterceptor(new InterfaceAuthCheckInterceptor()).addPathPatterns("/api/**");
// 如果interceptor中不注入redis或其他项目可以直接new,否则请使用上面这种方式
super.addInterceptors(registry);
}
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(jacksonConverter());
}
/**
* 格式转换
* @return
*/
@Bean
public FastJsonHttpMessageConverter jacksonConverter() {
List<MediaType> mediaTypes = new ArrayList<>();
mediaTypes.add(MediaType.APPLICATION_XML);
mediaTypes.add(MediaType.APPLICATION_FORM_URLENCODED);
mediaTypes.add(MediaType.APPLICATION_JSON);
FastJsonHttpMessageConverter converter =
new AppMsgConverter();
converter.setSupportedMediaTypes(mediaTypes);
return converter;
}
```