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;
}
}