@Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) public @interface ApiVersion { /** * @return版本号 */ int value( ) default 1; }
package com.yangjunbo.helloword.properties; import org.springframework.web.servlet.mvc.condition.RequestCondition; import javax.servlet.http.HttpServletRequest; import java.util.regex.Matcher; import java.util.regex.Pattern; /*接下来定义URL匹配逻辑,创建ApiVersionCondition类并继承RequestCondition接口, 其作用是进行版本号筛选,将提取请求URL中的版本号与注解上定义的版本号进行对比,以此来判断某个请求应落在哪个控制器上。*/ 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) { //apiVersion = version; return this; } } return null; } @Override public int compareTo(ApiVersionCondition apiVersionCondition, HttpServletRequest httpServletRequest) { return apiVersionCondition.getApiVersion() - this.apiVersion; } }
package com.yangjunbo.helloword.properties; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.servlet.mvc.condition.RequestCondition; import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping; import java.lang.reflect.Method; 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); } }
package com.yangjunbo.helloword.properties; import org.springframework.boot.autoconfigure.web.servlet.WebMvcRegistrations; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping; @Configuration public class WebMvcRegistrationsConfig implements WebMvcRegistrations { @Override public RequestMappingHandlerMapping getRequestMappingHandlerMapping() { return new ApiRequestMappingHandlerMapping(); } }
package com.yangjunbo.helloword.controller.v1; import com.yangjunbo.helloword.common.JSONResult; import com.yangjunbo.helloword.properties.ApiVersion; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; // V1 版本的接口定义 @RestController @RequestMapping("api/{version}/order") public class OrderV1Controller { @GetMapping("/delete/{orderId}") public JSONResult deleteOrderById(@PathVariable String orderId) { System.out.println("V1删除订单成功:"+orderId); return JSONResult.ok("V1删除订单成功"); } @GetMapping("/detail/{orderId}") public JSONResult queryOrderById(@PathVariable String orderId) { System.out.println("V1获取订单详情成功:"+orderId); return JSONResult.ok("V1获取订单详情成功"); } }
package com.yangjunbo.helloword.controller.v2; import com.yangjunbo.helloword.common.JSONResult; import com.yangjunbo.helloword.properties.ApiVersion; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; // V2 版本的接口定义 @ApiVersion(2) @RestController @RequestMapping("api/{version}/order") public class OrderV2Controller { @GetMapping("/detail/{orderId}") public JSONResult queryOrdearById(@PathVariable String orderId) { System.out.println("V2获取订单详情成功:"+orderId); return JSONResult.ok("V2获取订单详情成功"); } @GetMapping("/list") public JSONResult list() { System.out.println("V2,新增list订单列表接口"); return JSONResult.ok(200,"V2,新增list订单列表接口"); } }
以上验证情况说明Web API的版本控制配置成功,实现了旧版本的稳定和新版本的更新。
1)当请求正确的版本地址时,会自动匹配版本的对应接口。
2)当请求的版本大于当前版本时,默认匹配最新的版本。
3)高版本会默认继承低版本的所有接口。实现版本升级只关注变化的部分,没有变化的部分会自动平滑升级,这就是所谓的版本继承。
4)高版本的接口的新增和修改不会影响低版本。
这些特性使得在升级接口时,原有接口不受影响,只关注变化的部分,没有变化的部分自动平滑升级。这样使得Web API更加简洁,这就是实现Web API版本控制的意义所在。
参考书籍 《springboot从入门到实战-章为忠著》