soul网关源码学习05-dubbo插件流程解析
目标:
- 梳理dubbo插件的调用过程
- 理解dubbo的泛化调用
一、启动项目
- 启动本地mysql数据库和zookeeper
- 启动soul-admin、soul-bootstrap、soul-example-apache-dubbo-service
二、流程解析
首先,还是按上一次讲的,在SoulWebHandler
的handle
方法打上断点。
public Mono<Void> handle(@NonNull final ServerWebExchange exchange) {
MetricsTrackerFacade.getInstance().counterInc(MetricsLabelEnum.REQUEST_TOTAL.getName());
Optional<HistogramMetricsTrackerDelegate> startTimer = MetricsTrackerFacade.getInstance().histogramStartTimer(MetricsLabelEnum.REQUEST_LATENCY.getName());
return new DefaultSoulPluginChain(plugins).execute(exchange).subscribeOn(scheduler)
.doOnSuccess(t -> startTimer.ifPresent(time -> MetricsTrackerFacade.getInstance().histogramObserveDuration(time)));
}
用postman发起一次http请求,然后依次debug各个插件。
http://localhost:9195/dubbo/findById?id=1
第一个插件,还是这个GlobalPlugin
,封装接口调用需要用到的上下文。
public Mono<Void> execute(final ServerWebExchange exchange, final SoulPluginChain chain) {
final ServerHttpRequest request = exchange.getRequest();
final HttpHeaders headers = request.getHeaders();
final String upgrade = headers.getFirst("Upgrade");
SoulContext soulContext;
if (StringUtils.isBlank(upgrade) || !"websocket".equals(upgrade)) {
soulContext = builder.build(exchange);
} else {
final MultiValueMap<String, String> queryParams = request.getQueryParams();
soulContext = transformMap(queryParams);
}
exchange.getAttributes().put(Constants.CONTEXT, soulContext);
return chain.execute(exchange);
}
执行完之后进入第二个插件,还是AbstractSoulPlugin
,执行过程跟之前几乎一样,唯一不同的是多了个dubbo插件。
public Mono<Void> execute(final ServerWebExchange exchange, final SoulPluginChain chain) {
String pluginName = named();
final PluginData pluginData = BaseDataCache.getInstance().obtainPluginData(pluginName);
if (pluginData != null && pluginData.getEnabled()) {
final Collection<SelectorData> selectors = BaseDataCache.getInstance().obtainSelectorData(pluginName);
if (CollectionUtils.isEmpty(selectors)) {
return handleSelectorIsNull(pluginName, exchange, chain);
}
final SelectorData selectorData = matchSelector(exchange, selectors);
if (Objects.isNull(selectorData)) {
return handleSelectorIsNull(pluginName, exchange, chain);
}
selectorLog(selectorData, pluginName);
final List<RuleData> rules = BaseDataCache.getInstance().obtainRuleData(selectorData.getId());
if (CollectionUtils.isEmpty(rules)) {
return handleRuleIsNull(pluginName, exchange, chain);
}
RuleData rule;
if (selectorData.getType() == SelectorTypeEnum.FULL_FLOW.getCode()) {
//get last
rule = rules.get(rules.size() - 1);
} else {
rule = matchRule(exchange, rules);
}
if (Objects.isNull(rule)) {
return handleRuleIsNull(pluginName, exchange, chain);
}
ruleLog(rule, pluginName);
return doExecute(exchange, chain, selectorData, rule);
}
return chain.execute(exchange);
}
没有执行逻辑的插件直接debug跳过,然后debug进入到BodyParamPlugin
插件,这里是对参数做了适配,无论参数是json还是formdata,或者是跟在url后面的参数,都能正常接收到参数,并做了统一的处理。
public Mono<Void> execute(final ServerWebExchange exchange, final SoulPluginChain chain) {
final ServerHttpRequest request = exchange.getRequest();
final SoulContext soulContext = exchange.getAttribute(Constants.CONTEXT);
if (Objects.nonNull(soulContext) && RpcTypeEnum.DUBBO.getName().equals(soulContext.getRpcType())) {
MediaType mediaType = request.getHeaders().getContentType();
ServerRequest serverRequest = ServerRequest.create(exchange, messageReaders);
if (MediaType.APPLICATION_JSON.isCompatibleWith(mediaType)) {
return body(exchange, serverRequest, chain);
}
if (MediaType.APPLICATION_FORM_URLENCODED.isCompatibleWith(mediaType)) {
return formData(exchange, serverRequest, chain);
}
return query(exchange, serverRequest, chain);
}
return chain.execute(exchange);
}
private Mono<Void> body(final ServerWebExchange exchange, final ServerRequest serverRequest, final SoulPluginChain chain) {
return serverRequest.bodyToMono(String.class)
.switchIfEmpty(Mono.defer(() -> Mono.just("")))
.flatMap(body -> {
exchange.getAttributes().put(Constants.DUBBO_PARAMS, body);
return chain.execute(exchange);
});
}
private Mono<Void> formData(final ServerWebExchange exchange, final ServerRequest serverRequest, final SoulPluginChain chain) {
return serverRequest.formData()
.switchIfEmpty(Mono.defer(() -> Mono.just(new LinkedMultiValueMap<>())))
.flatMap(map -> {
exchange.getAttributes().put(Constants.DUBBO_PARAMS, HttpParamConverter.toMap(() -> map));
return chain.execute(exchange);
});
}
private Mono<Void> query(final ServerWebExchange exchange, final ServerRequest serverRequest, final SoulPluginChain chain) {
exchange.getAttributes().put(Constants.DUBBO_PARAMS,
HttpParamConverter.ofString(() -> serverRequest.uri().getQuery()));
return chain.execute(exchange);
}
处理完请求参数之后,接着往下执行,来到了ApacheDubboPlugin
插件,最关键的地方。
protected Mono<Void> doExecute(final ServerWebExchange exchange, final SoulPluginChain chain, final SelectorData selector, final RuleData rule) {
//拿到BodyParamPlugin插件处理好的参数
String body = exchange.getAttribute(Constants.DUBBO_PARAMS);
//拿到GlobalPlugin插件封装好的供接口调用的上下文
SoulContext soulContext = exchange.getAttribute(Constants.CONTEXT);
assert soulContext != null;
//拿到元数据
MetaData metaData = exchange.getAttribute(Constants.META_DATA);
//若元数据为空,error
if (!checkMetaData(metaData)) {
assert metaData != null;
log.error(" path is :{}, meta data have error.... {}", soulContext.getPath(), metaData.toString());
exchange.getResponse().setStatusCode(HttpStatus.INTERNAL_SERVER_ERROR);
Object error = SoulResultWrap.error(SoulResultEnum.META_DATA_ERROR.getCode(), SoulResultEnum.META_DATA_ERROR.getMsg(), null);
return WebFluxResultUtils.result(exchange, error);
}
//判断参数不为空,这里注意,就算是没有参数的接口,body也还是{}
if (StringUtils.isNoneBlank(metaData.getParameterTypes()) && StringUtils.isBlank(body)) {
exchange.getResponse().setStatusCode(HttpStatus.INTERNAL_SERVER_ERROR);
Object error = SoulResultWrap.error(SoulResultEnum.DUBBO_HAVE_BODY_PARAM.getCode(), SoulResultEnum.DUBBO_HAVE_BODY_PARAM.getMsg(), null);
return WebFluxResultUtils.result(exchange, error);
}
//进入dubbo代理类的genericInvoker方法
final Mono<Object> result = dubboProxyService.genericInvoker(body, metaData, exchange);
return result.then(chain.execute(exchange));
}
进入dubbo代理类,看看里面具体如何实现的。
public Mono<Object> genericInvoker(final String body, final MetaData metaData, final ServerWebExchange exchange) throws SoulException {
// issue(https://github.com/dromara/soul/issues/471), add dubbo tag route
//其实不知道这个dubboTagRouteFromHttpHeaders有啥用,尽管看过了上面那个issue
String dubboTagRouteFromHttpHeaders = exchange.getRequest().getHeaders().getFirst(Constants.DUBBO_TAG_ROUTE);
if (StringUtils.isNotBlank(dubboTagRouteFromHttpHeaders)) {
RpcContext.getContext().setAttachment(CommonConstants.TAG_KEY, dubboTagRouteFromHttpHeaders);
}
//这里可以看出来用了泛化调用,现在在走的是consumer,那provider呢,应该就是在ApplicationConfigCache里面了
//先继续走完,拿到reference,如果为空的话会有一个initRef的操作
ReferenceConfig<GenericService> reference = ApplicationConfigCache.getInstance().get(metaData.getPath());
if (Objects.isNull(reference) || StringUtils.isEmpty(reference.getInterface())) {
ApplicationConfigCache.getInstance().invalidate(metaData.getPath());
reference = ApplicationConfigCache.getInstance().initRef(metaData);
}
//声明为泛化接口
GenericService genericService = reference.get();
//这里的Pair感觉类似Map,参数类型是key,参数的值是value
Pair<String[], Object[]> pair;
if (ParamCheckUtils.dubboBodyIsEmpty(body)) {
pair = new ImmutablePair<>(new String[]{}, new Object[]{});
} else {
//封装参数
pair = dubboParamResolveService.buildParameter(body, metaData.getParameterTypes());
}
//调用泛化接口
CompletableFuture<Object> future = genericService.$invokeAsync(metaData.getMethodName(), pair.getLeft(), pair.getRight());
return Mono.fromFuture(future.thenApply(ret -> {
if (Objects.isNull(ret)) {
ret = Constants.DUBBO_RPC_RESULT_EMPTY;
}
//这里把结果放到exchange应该是后面有个response插件需要用到
exchange.getAttributes().put(Constants.DUBBO_RPC_RESULT, ret);
exchange.getAttributes().put(Constants.CLIENT_RESPONSE_RESULT_TYPE, ResultEnum.SUCCESS.getName());
return ret;
})).onErrorMap(exception -> exception instanceof GenericException ? new SoulException(((GenericException) exception).getExceptionMessage()) : new SoulException(exception));
}
看一下泛化接口,这个接口只有一个 $invoke 方法,三个参数,第一个是方法名,第二个是参数类型,第三个是参数值。我们需要在 $invoke 方法中,自行判断调用的是哪一个方法,这样就不需要在接口中声明方法,这就是泛化实现的核心。
主要用于服务器端没有API接口及模型类元的情况,参数及返回值中的所有POJO均用Map表示,通常用于框架集成,比如:实现一个通用的远程服务Mock框架,可通过实现GenericService接口处理所有服务请求。
public interface GenericService {
/**
* Generic invocation
*
* @param method Method name, e.g. findPerson. If there are overridden methods, parameter info is
* required, e.g. findPerson(java.lang.String)
* @param parameterTypes Parameter types
* @param args Arguments
* @return invocation return value
* @throws GenericException potential exception thrown from the invocation
*/
Object $invoke(String method, String[] parameterTypes, Object[] args) throws GenericException;
default CompletableFuture<Object> $invokeAsync(String method, String[] parameterTypes, Object[] args) throws GenericException {
Object object = $invoke(method, parameterTypes, args);
if (object instanceof CompletableFuture) {
return (CompletableFuture<Object>) object;
}
return CompletableFuture.completedFuture(object);
}
}
这里执行完,最后执行到DubboResponsePlugin
插件,封装一下返回的结果。
public Mono<Void> execute(final ServerWebExchange exchange, final SoulPluginChain chain) {
return chain.execute(exchange).then(Mono.defer(() -> {
final Object result = exchange.getAttribute(Constants.DUBBO_RPC_RESULT);
if (Objects.isNull(result)) {
Object error = SoulResultWrap.error(SoulResultEnum.SERVICE_RESULT_ERROR.getCode(), SoulResultEnum.SERVICE_RESULT_ERROR.getMsg(), null);
return WebFluxResultUtils.result(exchange, error);
}
Object success = SoulResultWrap.success(SoulResultEnum.SUCCESS.getCode(), SoulResultEnum.SUCCESS.getMsg(), JsonUtils.removeClass(result));
return WebFluxResultUtils.result(exchange, success);
}));
}
执行到这里整个请求的流程就结束了,然后看一下ApplicationConfigCache
这个类到底做了什么。
首先,项目启动时会有一个初始化的操作,注册dubbo服务。
public void init(final DubboRegisterConfig dubboRegisterConfig) {
if (applicationConfig == null) {
applicationConfig = new ApplicationConfig("soul_proxy");
}
if (registryConfig == null) {
registryConfig = new RegistryConfig();
registryConfig.setProtocol(dubboRegisterConfig.getProtocol());
registryConfig.setId("soul_proxy");
registryConfig.setRegister(false);
registryConfig.setAddress(dubboRegisterConfig.getRegister());
Optional.ofNullable(dubboRegisterConfig.getGroup()).ifPresent(registryConfig::setGroup);
}
}
然后这些reference都会放在缓存里面。
public ReferenceConfig<GenericService> build(final MetaData metaData) {
ReferenceConfig<GenericService> reference = new ReferenceConfig<>();
reference.setGeneric(true);
reference.setApplication(applicationConfig);
reference.setRegistry(registryConfig);
reference.setInterface(metaData.getServiceName());
reference.setProtocol("dubbo");
String rpcExt = metaData.getRpcExt();
DubboParamExtInfo dubboParamExtInfo = GsonUtils.getInstance().fromJson(rpcExt, DubboParamExtInfo.class);
//省略
Object obj = reference.get();
if (obj != null) {
log.info("init apache dubbo reference success there meteData is :{}", metaData.toString());
cache.put(metaData.getPath(), reference);
}
return reference;
}
看一下上面说的那个initRef操作,就是初始化reference,然后放进缓存。
public ReferenceConfig<GenericService> initRef(final MetaData metaData) {
try {
ReferenceConfig<GenericService> referenceConfig = cache.get(metaData.getPath());
if (StringUtils.isNoneBlank(referenceConfig.getInterface())) {
return referenceConfig;
}
} catch (ExecutionException e) {
log.error("init dubbo ref ex:{}", e.getMessage());
}
return build(metaData);
}
三、总结
把整个dubbo插件从请求开始到结束走了一遍,梳理了整个流程,了解了泛化调用的基本用法,还是要再花多点时间深入了解一下。