之前有文章讲述在Spring MVC 中扩展 RequestMappingHandlerMapping 实现对版本的控制。
但是在真正使用过程中不是很理想化,因为其需要替换掉WebMvcConfigurationSupport,替换后后,会将其提供的一系列默认组件全部移除。如我们注册拦截器使用的(RequestMappingHandlerAdapter)、全局异常拦截(ExceptionHandlerExceptionResolver)等。
本文以Spring Boot 2.x为例,解决这个问题。
配置WebMvcRegistrationsConfig
@Configuration
public class WebMvcRegistrationsConfig implements WebMvcRegistrations {
@Override
public RequestMappingHandlerMapping getRequestMappingHandlerMapping() {
return new ApiRequestMappingHandlerMapping();
}
}
创建ApiRequestMappingHandlerMapping
public class ApiRequestMappingHandlerMapping extends RequestMappingHandlerMapping {
private static final String VERSION_FLAG = "{version}";
private static RequestCondition<ApiVersionCondition> createCondition(Class<?> clazz) {
RequestMapping classRequestMapping = clazz.getAnnotation(RequestMapping.class);
if (classRequestMapping == null) {
return null;
}
StringBuilder mappingUrlBuilder = new StringBuilder();
if (classRequestMapping.value().length > 0) {
mappingUrlBuilder.append(classRequestMapping.value()[0]);
}
String mappingUrl = mappingUrlBuilder.toString();
if (!mappingUrl.contains(VERSION_FLAG)) {
return null;
}
ApiVersion apiVersion = clazz.getAnnotation(ApiVersion.class);
return apiVersion == null ? new ApiVersionCondition(1) : new ApiVersionCondition(apiVersion.value());
}
@Override
protected RequestCondition<?> getCustomMethodCondition(Method method) {
return createCondition(method.getClass());
}
@Override
protected RequestCondition<?> getCustomTypeCondition(Class<?> handlerType) {
return createCondition(handlerType);
}
}
@ApiVersion自定义注解
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface ApiVersion {
/**
* @return 版本号
*/
int value() default 1;
}
ApiVersionCondition请求映射条件
RequestCondition相当针对请求与映射之间创建了判断条件。以此判断某个请求应落在哪个controller上。
public class ApiVersionCondition implements RequestCondition<ApiVersionCondition> {
private final static Pattern VERSION_PREFIX_PATTERN = Pattern.compile(".*v(\\d+).*");
private int apiVersion;
ApiVersionCondition(int apiVersion) {
this.apiVersion = apiVersion;
}
private int getApiVersion() {
return apiVersion;
}
@Override
public ApiVersionCondition combine(ApiVersionCondition apiVersionCondition) {
return new ApiVersionCondition(apiVersionCondition.getApiVersion());
}
@Override
public ApiVersionCondition getMatchingCondition(HttpServletRequest httpServletRequest) {
Matcher m = VERSION_PREFIX_PATTERN.matcher(httpServletRequest.getRequestURI());
if (m.find()) {
Integer version = Integer.valueOf(m.group(1));
if (version >= this.apiVersion) {
return this;
}
}
return null;
}
@Override
public int compareTo(ApiVersionCondition apiVersionCondition, HttpServletRequest httpServletRequest) {
return apiVersionCondition.getApiVersion() - this.apiVersion;
}
}
使用
V1Controller
@RequestMapping("/{version}/version")
@RestController
public class V1Controller {
@GetMapping
public String test() {
return "version1";
}
@GetMapping("/extend")
public String extendTest() {
return "extend";
}
}
V2Controller
@RequestMapping("/{version}/version")
@RestController
@ApiVersion(2)
public class V2Controller {
@GetMapping
public String test() {
return "version2";
}
}
版本测试
当请求http://localhost:8080/v1/version时会返回version1
当请求http://localhost:8080/v2/version时返回version2
实现了版本控制效果
版本继承
当请求http://localhost:8080/v2/version/extend时,会返回V1Controller中的extend。
实现版本继承效果。
版本分包
当引入版本控制后,为了不让版本号污染我们的类名。
我们一般不希望命名出现类似V1、V2的字眼。使用包的形式进行区分会更直观一些。如com.test.v1.UserController、com.test.v2.UserController。
但此时spring以及扩展spring的一些组件(如mybatis)会抛出bean名称重复的错误。
此时我们需要覆盖或手动指定spring的默认名称生成器AnnotationBeanNameGenerator,该类默认使用class的名称而非全类名作为bean的名称,故会出现名称冲突。抛出ConflictingBeanDefinitionException。
Bean名称重复ConflictingBeanDefinitionException冲突解决
Bean名称重复ConflictingBeanDefinitionException冲突解决
Spring Boot 1.x
Spring Boot 1.x中由于使用jdk1.8以下版本,没有default方法,故无法直接实现WebMvcRegistrations,其提供了WebMvcRegistrationsAdapter供我们继承。
@Configuration
public class WebMvcRegistrationsConfig extends WebMvcRegistrationsAdapter {
@Override
public RequestMappingHandlerMapping getRequestMappingHandlerMapping() {
return new ApiRequestMappingHandlerMapping();
}
}
网友评论