一、背景
在微服务重构时,我们常遇到这个业务场景:同样是/api/test,我们实现了新逻辑和老逻辑,然后根据定制的灰度策略,通常灰度API和老API两者都需要支持用户使用。
那么是否有比较好解决方案,协助我们完成同名同方法同参数列表的API灰度动态路由的方案呢?
我们就基于SpringMVC,通过对底层RequestMappingInfo的参数定制化,实现了methodHandler的动态路由决策,从而达到API灰度动态路由目的。
二、实现原理
我们总的来说,干了两件事情:
-
第一件事
服务启动时,在initMethodHandler执行时,对RequestMappingInfo初始化时,就将灰度决策器RouterDecisionMaker,以@PathRouterDecisionMaker决策器注解的形式,预加载到customInfo里
-
第二件事
服务运行期,在路由匹配器PathMatcher里,会解析RequestMappingInfo,最终执行灰度决策器RouterDecisionMaker,并挑选最合适的RequestMappingInfo映射的methodHandler去执行响应逻辑
三、实现方案
1、定义决策器注解:@PathRouterDecisionMaker
@Target({ElementType.TYPE, ElementType.PACKAGE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface PathRouterDecisionMaker {
Class<? extends RouterDecisionMaker> decision();
String resourceCondition() default "";
int order() default 0;
}
分析:
-
decision():决策属性,指向路由决策器类,即RouterDecisionMaker接口
注解(Annotation):仅提供附加元数据支持,并 不能实现任何操作,需要另外的 Scanner 根据元数据执行相应操作。
2、路由决策器:RouterDecisionMaker接口
RouterDecisionMaker接口,定义了决策器的匹配策略方法
public interface RouterDecisionMaker {
/**
* 路由决策器的最终决策方法
* @param pathPartRequest
* @return 匹配返回的资源类型
*/
boolean matches(PathPartRequest pathPartRequest);
}
2.1 灰度决策器-ApiGrayDecisionMaker(返回true)
@Component(value = "ApiGrayDecisionMaker")
public class ApiGrayDecisionMaker implements RouterDecisionMaker {
@Override
public boolean matches(PathPartRequest pathPartRequest) {
return Boolean.TRUE;
}
}
代码分析:
-
为了方便实践,我们写死一个决策匹配策略为TRUE
2.2 灰度决策器-ApiNotGrayDecisionMaker(返回false)
@Component("ApiNotGrayDecisionMaker")
public class ApiNotGrayDecisionMaker implements RouterDecisionMaker {
/**
* 取反,跟 ApiGrayDecision#matches 互斥
* @param pathPartRequest
* @return
*/
@Override
public boolean matches(PathPartRequest pathPartRequest) {
return Boolean.FALSE;
}
}
代码分析:
-
为了方便实践,我们写死一个决策匹配策略为FALSE
2.3 决策信息
RouterPathRequest,提供数据给决策器
public class RouterPathRequest {
private final String pattern;
private final String url;
private final Map<String, String> pathVariables;
private final RouterPatternKey routerPatternKey;
private final String routeCondition;
private final HttpServletRequest request;
public RouterPathRequest(HttpServletRequest request, String pattern, String url, Map<String, String> pathVariables,
RouterPatternKey routerPatternKey, String routeCondition) {
this.request = request;
this.pattern = pattern;
this.pathVariables = pathVariables;
this.url = url;
this.routerPatternKey = routerPatternKey;
this.routeCondition = routeCondition;
}
public static RouterPathRequest build(HttpServletRequest request, String pattern, String url,
Map<String, String> pathVariables, RouterPatternKey routerPatternKey, String routeCondition) {
return new RouterPathRequest(request, pattern, url, pathVariables, routerPatternKey, routeCondition);
}
//...getter&setter
}
代码分析:
-
实际是对HttpServletRequest的二次封装,并提取了一些常用上下文数据到属性
2.4 决策注解探测器
WebRouterDecisionMakerDetection
/**
* PathRouterDecisionMaker注解提取器
* - 从方法注释提取注解
*/
public class WebRouterDecisionMakerDetection {
public PathRouterDecisionMaker detect(Method handlerMethod) {
if (Objects.isNull(handlerMethod)) {
return null;
}
return AnnotatedElementUtils.findMergedAnnotation(handlerMethod, PathRouterDecisionMaker.class);
}
}
2.5 自定义-路由匹配策略
WebRouterDecisionCondition
继承了 AbstractRequestCondition,会在创建RequestMappingInfo的填入customCondition条件时,被回调使用。
-
抽象类AbstractRequestCondition实现了 RequestConditi