HandlerMapping通过继承InitializingBean接口在完成实例后,扫描所有的Controller和标识RequestMapping的方法,缓存这个映射对应关系。然后在应用运行的时候,根据请求的request来找到相应的handler来处理这个请求。在这里,我们添加扩展类:
- ApiVersion
- ApiVesrsionCondition
- CustomRequestMappingHandlerMapping
- WebConfig
现分别来看下这个类,首先看下ApiVersion这个注解:
@Target({ElementType.METHOD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Mapping public @interface ApiVersion { /** * 版本号 * @return */ int value(); }
这个注解用来标识某个类或者方法要处理的对应版本号,使用如下:
@Controller @RequestMapping("/{version}/") public class HelloController { @RequestMapping("hello/") @ApiVersion(1) @ResponseBody public String hello(HttpServletRequest request){ System.out.println("haha1.........."); return "hello"; } @RequestMapping("hello/") @ApiVersion(2) @ResponseBody public String hello2(HttpServletRequest request){ System.out.println("haha2........."); return "hello"; } @RequestMapping("hello/") @ApiVersion(5) @ResponseBody public String hello5(HttpServletRequest request){ System.out.println("haha5........."); return "hello"; } }
现在我们就可以通过 /v1/hello/, /v2/hello/, /v5/hello来分别调用版本1,2,5的管理。当然我们也要解决刚才说的两点问题,如果用户通过 /v4/hello/来访问接口,则要自动适配到 /v2/hello/,因为 v2是比v4低的版本中最新的版本。
再来看下 ApiVersionCondition 这个类。这个类就是我们自定义一个条件筛选器,让SpringMVC在原有逻辑的基本上添加一个版本号匹配的规则:
public class ApiVesrsionCondition implements RequestCondition<ApiVesrsionCondition> { // 路径中版本的前缀, 这里用 /v[1-9]/的形式 private final static Pattern VERSION_PREFIX_PATTERN = Pattern.compile("v(\d+)/"); private int apiVersion; public ApiVesrsionCondition(int apiVersion){ this.apiVersion = apiVersion; } public ApiVesrsionCondition combine(ApiVesrsionCondition other) { // 采用最后定义优先原则,则方法上的定义覆盖类上面的定义 return new ApiVesrsionCondition(other.getApiVersion()); } public ApiVesrsionCondition getMatchingCondition(HttpServletRequest request) { Matcher m = VERSION_PREFIX_PATTERN.matcher(request.getPathInfo()); if(m.find()){ Integer version = Integer.valueOf(m.group(1)); if(version >= this.apiVersion) // 如果请求的版本号大于配置版本号, 则满足 return this; } return null; } public int compareTo(ApiVesrsionCondition other, HttpServletRequest request) { // 优先匹配最新的版本号 return other.getApiVersion() - this.apiVersion; } public int getApiVersion() { return apiVersion; } }
要把这个筛选规则生效的话,要扩展原胡的HandlerMapping,把这个规则设置进去生效,看下CustomRequestMappingHandlerMapping的代码:
public class CustomRequestMappingHandlerMapping extends RequestMappingHandlerMapping { @Override protected RequestCondition<ApiVesrsionCondition> getCustomTypeCondition(Class<?> handlerType) { ApiVersion apiVersion = AnnotationUtils.findAnnotation(handlerType, ApiVersion.class); return createCondition(apiVersion); } @Override protected RequestCondition<ApiVesrsionCondition> getCustomMethodCondition(Method method) { ApiVersion apiVersion = AnnotationUtils.findAnnotation(method, ApiVersion.class); return createCondition(apiVersion); } private RequestCondition<ApiVesrsionCondition> createCondition(ApiVersion apiVersion) { return apiVersion == null ? null : new ApiVesrsionCondition(apiVersion.value()); } }
最后,得让SpringMVC加载我们定义的CustomRequestMappingHandlerMapping以覆盖原先的RequestMappingHandlerMapping, 所以要去掉前面说的<mvc:annotation-driven/>这个配置,我们通过JavaConfig的方式注入:
@Configuration public class WebConfig extends WebMvcConfigurationSupport{ @Override @Bean public RequestMappingHandlerMapping requestMappingHandlerMapping() { RequestMappingHandlerMapping handlerMapping = new CustomRequestMappingHandlerMapping(); handlerMapping.setOrder(0); handlerMapping.setInterceptors(getInterceptors()); return handlerMapping; } }