dubbo有一套自己的版本控制策略,消费者根据服务提供者的version进行配置,最终实现版本控制,在springboot中又如何实现?
springboot中控制版本,其实很简单粗暴,不同版本的接口,可以直接写两个接口,接口uri不一样,不就OK了吗?但是为了让同一个项目组的同学都能按照一定的版本规则写接口,我们需要稍微做点加工。
简单粗暴版:
/api/query/studentInfo1
/api/query/studentInfo2
规范版:
/v1/api/query/studentInfo
/v2/api/query/studentInfo
具体实现:
需要继承RequestMappingHandlerMapping类,重写org.springframework.web.servlet.handler.AbstractHandlerMethodMapping#registerHandlerMethod方法。RequestMappingHandlerMapping是根据类或方法上的 @RequestMapping 来生成 RequestMappingInfo 的实例,负责根据用户请求(uri)匹配找到Handler即处理器(controller层加了RequestMapping注解的方法)。
package com.cn.dl.springbootdemo.annotation; import org.springframework.core.annotation.AliasFor; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * @author yanshao * @date 2022/8/11 10:31 上午 */ @Target({ElementType.METHOD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) public @interface ApiVersion { @AliasFor("path") String[] value() default {}; @AliasFor("value") String[] path() default {}; }
这块代码有较详细的注释,最后自己debug跟一下spring源码
package com.cn.dl.springbootdemo.handler; import com.cn.dl.springbootdemo.annotation.ApiVersion; import org.springframework.core.annotation.AnnotationUtils; import org.springframework.web.servlet.mvc.condition.PatternsRequestCondition; import org.springframework.web.servlet.mvc.method.RequestMappingInfo; import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping; import java.lang.reflect.Method; import java.util.Objects; /** * @author yanshao * @date 2022/8/11 10:32 上午 */ public class ApiVersionHandlerMapping extends RequestMappingHandlerMapping { @Override protected void registerHandlerMethod(Object handler, Method method, RequestMappingInfo mappingInfo) { //类上加了ApiVersion注解 ApiVersion apiVersion = AnnotationUtils.findAnnotation(method.getDeclaringClass(), ApiVersion.class); //可同时指定多个版本,类似于org.springframework.web.bind.annotation.RequestMapping.value的paths String[] urlPatterns = Objects.isNull(apiVersion) ? new String[0] : apiVersion.value(); //api版本urlPatterns PatternsRequestCondition apiVersionPattern = new PatternsRequestCondition(urlPatterns); //当前未增加版本的paths,例如:/api/query/studentInfo PatternsRequestCondition curtPattern = mappingInfo.getPatternsCondition(); //加版本号增加到curtPath之前,例如:/v1 + /api/query/studentInfo -> /v1/api/query/studentInfo PatternsRequestCondition updatedFinalPattern = apiVersionPattern.combine(curtPattern); //构建新的RequestMappingInfo mappingInfo = new RequestMappingInfo( mappingInfo.getName(), updatedFinalPattern, mappingInfo.getMethodsCondition(), mappingInfo.getParamsCondition(), mappingInfo.getHeadersCondition(), mappingInfo.getConsumesCondition(), mappingInfo.getProducesCondition(), mappingInfo.getCustomCondition() ); super.registerHandlerMethod(handler, method, mappingInfo); } }
package com.cn.dl.springbootdemo.handler; import org.springframework.boot.SpringBootConfiguration; import org.springframework.boot.autoconfigure.web.servlet.WebMvcRegistrations; import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping; /** * @author yanshao * @date 2022/8/11 10:36 上午 */ @SpringBootConfiguration public class MvcMappingConfig implements WebMvcRegistrations { //注册ApiVersionHandlerMapping组件 @Override public RequestMappingHandlerMapping getRequestMappingHandlerMapping() { return new ApiVersionHandlerMapping(); } }
package com.cn.dl.springbootdemo.controller; import com.alibaba.fastjson.JSONObject; import com.cn.dl.springbootdemo.annotation.ApiVersion; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; /** * @author yanshao * @date 2022/8/11 10:37 上午 */ @RestController @ApiVersion("v1") @RequestMapping("/api/") public class StudentController { @GetMapping(value = "/query/studentInfo") public JSONObject studentInfo(){ JSONObject result = new JSONObject(); result.put("name","yanshao"); result.put("age",27); result.put("apiVersion","v1"); return result; } }
package com.cn.dl.springbootdemo.controller; import com.alibaba.fastjson.JSONObject; import com.cn.dl.springbootdemo.annotation.ApiVersion; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; /** * @author yanshao * @date 2022/8/11 10:37 上午 */ @RestController @ApiVersion("v2") @RequestMapping("/api/") public class StudentControllerV2 { @GetMapping(value = "/query/studentInfo") public JSONObject studentInfo(){ JSONObject result = new JSONObject(); result.put("name","yanshao"); result.put("age",27); result.put("apiVersion","v2"); return result; } }
效果:
不知道大家发现一个缺陷了吗?为了搞一个不同版本的接口,还得重新写一个类,是不是ApiVersion注解可以放在方法上?这需要修改一下ApiVersionHandlerMapping,就可以支持同一个Controller中不同版本的接口了。
package com.cn.dl.springbootdemo.handler; import com.cn.dl.springbootdemo.annotation.ApiVersion; import org.springframework.core.annotation.AnnotationUtils; import org.springframework.web.servlet.mvc.condition.PatternsRequestCondition; import org.springframework.web.servlet.mvc.method.RequestMappingInfo; import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping; import java.lang.reflect.Method; import java.util.Objects; /** * @author yanshao * @date 2022/8/11 10:32 上午 */ public class ApiVersionHandlerMapping extends RequestMappingHandlerMapping { @Override protected void registerHandlerMethod(Object handler, Method method, RequestMappingInfo mappingInfo) { //类上加了ApiVersion注解 // ApiVersion apiVersionClass = AnnotationUtils.findAnnotation(method.getDeclaringClass(), ApiVersion.class); //方法上加了ApiVersion注解 ApiVersion apiVersionMethod = AnnotationUtils.findAnnotation(method, ApiVersion.class); //可同时指定多个版本,类似于org.springframework.web.bind.annotation.RequestMapping.value的paths String[] urlPatterns = Objects.isNull(apiVersionMethod) ? new String[0] : apiVersionMethod.value(); //api版本urlPatterns PatternsRequestCondition apiVersionPattern = new PatternsRequestCondition(urlPatterns); //当前未增加版本的paths,例如:/api/query/studentInfo // PatternsRequestCondition curtPattern = mappingInfo.getPatternsCondition(); //加版本号增加到curtPath之前,例如:/v1 + /api/query/studentInfo -> /v1/api/query/studentInfo PatternsRequestCondition updatedFinalPattern = apiVersionPattern.combine(mappingInfo.getPatternsCondition()); //构建新的RequestMappingInfo mappingInfo = new RequestMappingInfo( mappingInfo.getName(), updatedFinalPattern, mappingInfo.getMethodsCondition(), mappingInfo.getParamsCondition(), mappingInfo.getHeadersCondition(), mappingInfo.getConsumesCondition(), mappingInfo.getProducesCondition(), mappingInfo.getCustomCondition() ); super.registerHandlerMethod(handler, method, mappingInfo); } }
package com.cn.dl.springbootdemo.controller; import com.alibaba.fastjson.JSONObject; import com.cn.dl.springbootdemo.annotation.ApiVersion; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; /** * @author yanshao * @date 2022/8/11 10:37 上午 */ @RestController @RequestMapping("/api/") public class StudentController { @ApiVersion("v1") @GetMapping(value = "/query/studentInfo") public JSONObject studentInfoV1(){ JSONObject result = new JSONObject(); result.put("name","yanshao"); result.put("age",27); result.put("apiVersion","v1"); return result; } @ApiVersion("v2") @GetMapping(value = "/query/studentInfo") public JSONObject studentInfoV2(){ JSONObject result = new JSONObject(); result.put("name","yanshao"); result.put("age",27); result.put("apiVersion","v2"); return result; } }