RequestCondition详解
1.为什么要对API进行版本管理
因为软件有一个不断升级迭代的过程,而在升级中我们业务需求可能不断在更新,所以我们需要对以前的API接口不断的更新以满足变化的需求,但是在更新升级的过程中我们势必又要保证原来功能的可用性,所以我们就必须要对同一个API接口进行多版本的管理。
2.RequestCondition 介绍
主要案例:
- 版本切换
- 灰度发布
在 spring mvc 中,通过DispatchServlet
接收客户端发起的一个请求之后,会通过 HanderMapping 来获取对应的请求处理器;而 HanderMapping 如何找到可以处理这个请求的处理器呢,这就需要 RequestCondition 来决定了。
接口定义如下,主要有三个方法:
public interface RequestCondition<T> {
// 一个http接口上有多个条件规则时,用于合并
T combine(T other);
// 这个是重点,用于判断当前匹配条件和请求是否匹配;如果不匹配返回null
// 如果匹配,生成一个新的请求匹配条件,该新的请求匹配条件是当前请求匹配条件针对指定请求request的剪裁
// 举个例子来讲,如果当前请求匹配条件是一个路径匹配条件,包含多个路径匹配模板,
// 并且其中有些模板和指定请求request匹配,那么返回的新建的请求匹配条件将仅仅
// 包含和指定请求request匹配的那些路径模板。
@Nullable
T getMatchingCondition(HttpServletRequest request);
// 针对指定的请求对象request发现有多个满足条件的,用来排序指定优先级,使用最优的进行响应
// 针对指定的请求对象request比较两个请求匹配条件。
// 该方法假定被比较的两个请求匹配条件都是针对该请求对象request调用了
// #getMatchingCondition方法得到的,这样才能确保对它们的比较
// 是针对同一个请求对象request,这样的比较才有意义(最终用来确定谁是
// 更匹配的条件)。
int compareTo(T other, HttpServletRequest request);
}
-
combine
: 某个接口有多个规则时,进行合并 - 比如类上指定了
@RequestMapping的 url 为
root- 而方法上指定的
@RequestMapping的 url 为
method- 那么在获取这个接口的 url 匹配规则时,类上扫描一次,方法上扫描一次,这个时候就需要把这两个合并成一个,表示这个接口匹配
root/method -
getMatchingCondition
: - 判断是否成功,失败返回 null;否则,则返回匹配成功的条件 -
compareTo
: - 多个都满足条件时,用来指定具体选择哪一个
由接口源代码可以看出,接口RequestCondition
是一个泛型接口。事实上,它的泛型参数T
通常也会是一个RequestCondition
对象。
3.详细例子:
一个接口出现 url 相同且请求方法也相同的,这样的情况通常发生在接口迭代过程中,如果出现问题,可以无感切到原接口。所以可以利用RequestCondition来做接口的版本控制。
3.1.定义@ApiVersion注解:
package com.kami.mall.config;
import java.lang.annotation.*;
/**
* @Auther xzh
* @Date 2023-11-23 15:48
*/
@Target(value = ElementType.METHOD)
@Retention(value = RetentionPolicy.RUNTIME)
@Documented
public @interface ApiVersion {
String value();
}
3.2.定义匹配规则
继承自RequestCondition
类,如下ApiVersionCondition类:
package com.kami.mall.config;
import com.kami.mall.entity.DO.User;
import com.kami.mall.mapper.UserMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.mvc.condition.RequestCondition;
import javax.servlet.http.HttpServletRequest;
/**
* @Auther xzh
* @Date 2023-11-23 15:54
*/
public class ApiVersionCondition implements RequestCondition<ApiVersionCondition> {
private static ApiVersionMapper apiVersionMapper;
public static void setUserMapper(ApiVersionMapper apiVersionMapper) {
ApiVersionCondition.apiVersionMapper = apiVersionMapper;
}
public static ApiVersionMapper getApiVersionMapper() {
return apiVersionMapper;
}
String apiVersion;
public String getApiVersion() {
return apiVersion;
}
public void setApiVersion(String apiVersion) {
this.apiVersion = apiVersion;
}
public ApiVersionCondition(String apiVersion){
this.apiVersion = apiVersion;
}
@Override
public ApiVersionCondition combine(ApiVersionCondition apiVersionCondition) {
System.out.println("combine ==apiVersionCondition.getApiVersion()=>"+apiVersionCondition.getApiVersion());
return new ApiVersionCondition(apiVersionCondition.getApiVersion());
}
@Override
public ApiVersionCondition getMatchingCondition(HttpServletRequest request) {
ApiVersion apiVersion = apiVersionMapper.getApiVersion(this.apiVersion);
String requestURI = request.getRequestURI();
System.out.println("getMatchingCondition ==requestURI=>"+requestURI);
if(apiVersion.getApiVersion.compareTo(this.apiVersion)>=0){
return this;
}
return null;
}
@Override
public int compareTo(ApiVersionCondition other, HttpServletRequest httpServletRequest) {
System.out.println("compareTo ==other.getApiVersion()=>"+other.getApiVersion());
System.out.println("compareTo ==this.apiVersion=>"+this.apiVersion);
System.out.println(other.getApiVersion().compareTo(this.apiVersion));
return other.getApiVersion().compareTo(this.apiVersion);
}
}
3.3.注册到 HandlerMapping 上
匹配规则指定完毕之后,需要注册到 HandlerMapping 上才能生效,如下ApiVersionHandleMapping映射器:
package com.kami.mall.config;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.web.servlet.mvc.condition.RequestCondition;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
import java.lang.reflect.Method;
/**
* 支持使用多版本的控制器
* @Auther xzh
* @Date 2023-11-23 16:16
*/
public class ApiVersionHandleMapping extends RequestMappingHandlerMapping {
public ApiVersionHandleMapping(){
System.out.println("====ApiVersionHandleMapping===========init========");
}
/**
* 容器初始化执行
* 所有controller都会使用该方法
* @param handlerType
* @return
*/
@Override
protected RequestCondition<?> getCustomTypeCondition(Class<?> handlerType) {
ApiVersion apiVersion = AnnotationUtils.findAnnotation(handlerType, ApiVersion.class);
return apiVersion == null ? null : new ApiVersionCondition(apiVersion.value());
}
/**
* 容器初始化时执行
* @param method
* @return
*/
@Override
protected RequestCondition<?> getCustomMethodCondition(Method method) {
ApiVersion apiVersion = AnnotationUtils.findAnnotation(method, ApiVersion.class);
return apiVersion == null ? null : new ApiVersionCondition(apiVersion.value());
}
}
3.4.注册到 Spring MVC 容器中
最后则是需要将我们的 HandlerMapping 注册到 Spring MVC 容器,在这里我们借助WebMvcConfigurationSupport
来手动注册,如下ApiVersionMappingRegister类:
package com.kami.mall.config;
import com.kami.mall.mapper.UserMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.web.servlet.WebMvcRegistrations;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
import javax.annotation.PostConstruct;
/**
* @Auther xzh
* @Date 2023-11-23 16:21
*/
@Configuration
public class ApiVersionMappingRegister implements WebMvcRegistrations {
@Autowired
private ApiversionMapper apiversionMapper;
public RequestMappingHandlerMapping getRequestMappingHandlerMapping(){
ApiVersionHandleMapping handleMapping = new ApiVersionHandleMapping();
// handleMapping.setOrder(0);
return handleMapping;
}
@PostConstruct
public void initMethod(){
ApiVersionCondition.setApiversionMapper(apiversionMapper);
}
}