基于GraphQl-JAVA-TOOLS 5.5.2
把GraphQL的完整流程分为三个部分:schema解析、GraphlQLSchema装载和执行query(这里用query代指query、mutation、subscription)。其中的schema解析和GraphlQLSchema装载属于启动阶段,执行query属于运行阶段。
一、schema解析
我们在构建 GraphQLSchema 时会通过 SchemaParserBuilder 去build一个 SchemaParser :
/**
* Build the parser with the supplied schema and dictionary.
*/
fun build() = SchemaParser(scan(), options, runtimeWiringBuilder.build())
这里的 scan() 方法最终会去执行 parseDocuments() 。GraphQL的schema解析使用了antlr,这是一个语法分析工具,它先根据预先定义的规则进行词法分析,把传入的文本解析成一个个token,然后将这些token构建成一个树。
public Document parseDocument(String input, String sourceName) {
CharStream charStream;
if(sourceName == null) {
charStream = CharStreams.fromString(input);
} else{
charStream = CharStreams.fromString(input, sourceName);
}
// 构建词法分析器
GraphqlLexer lexer = new GraphqlLexer(charStream);
CommonTokenStream tokens = new CommonTokenStream(lexer);
GraphqlParser parser = new GraphqlParser(tokens);
parser.removeErrorListeners();
parser.getInterpreter().setPredictionMode(PredictionMode.SLL);
parser.setErrorHandler(new BailErrorStrategy());
// 用定义的词法分析规则进行解析,得到tokens
GraphqlParser.DocumentContext documentContext = parser.document();
GraphqlAntlrToLanguage antlrToLanguage = new GraphqlAntlrToLanguage(tokens);
// 将tokens构建成一棵分析树
Document doc = antlrToLanguage.createDocument(documentContext);
// 校验tokens
Token stop = documentContext.getStop();
List allTokens = tokens.getTokens();
if (stop != null && allTokens != null && !allTokens.isEmpty()) {
Token last = allTokens.get(allTokens.size() - 1);
//
// do we have more tokens in the stream than we consumed in the parse?
// if yes then its invalid. We make sure its the same channel
boolean notEOF = last.getType() != Token.EOF;
boolean lastGreaterThanDocument = last.getTokenIndex() > stop.getTokenIndex();
boolean sameChannel = last.getChannel() == stop.getChannel();
if (notEOF && lastGreaterThanDocument && sameChannel) {
throw new ParseCancellationException("There are more tokens in the query that have not been consumed");
}
}
return doc;
}
其中词法分析的结果tokens是这样的:
最终得到的分析树是这样的:
其中 FieldDefinition 就是schema解析后的内存模型。
二、GraphlQLSchema装载
上一步完成了schema的解析,第二步是把分析树按照GraphQL的规则进行组装。组装的操作在上一篇GraphQL(五):GraphQL身份认证中已有介绍 GraphQLObjectType 的组装,在 SchemaParser 中还有其他类型(GraphQLInputObjectType、GraphQLEnumType、GraphQLInterfaceType、GraphQLUnionType等 )的组装,组装逻辑的入口在 parseSchemaObjects():
fun parseSchemaObjects(): SchemaObjects {
// Create GraphQL objects
val interfaces = interfaceDefinitions.map { createInterfaceObject(it) }
val objects = objectDefinitions.map { createObject(it, interfaces) }
val unions = unionDefinitions.map { createUnionObject(it, objects) }
val inputObjects = inputObjectDefinitions.map { createInputObject(it) }
val enums = enumDefinitions.map { createEnumObject(it) }
// Assign type resolver to interfaces now that we know all of the object types
interfaces.forEach { (it.typeResolver as TypeResolverProxy).typeResolver = InterfaceTypeResolver(dictionary.inverse(), it, objects) }
unions.forEach { (it.typeResolver as TypeResolverProxy).typeResolver = UnionTypeResolver(dictionary.inverse(), it, objects) }
// Find query type and mutation/subscription type (if mutation/subscription type exists)
val queryName = rootInfo.getQueryName()
val mutationName = rootInfo.getMutationName()
val subscriptionName = rootInfo.getSubscriptionName()
val query = objects.find { it.name == queryName }
?: throw SchemaError("Expected a Query object with name '$queryName' but found none!")
val mutation = objects.find { it.name == mutationName }
?: if (rootInfo.isMutationRequired()) throw SchemaError("Expected a Mutation object with name '$mutationName' but found none!") else null
val subscription = objects.find { it.name == subscriptionName }
?: if (rootInfo.isSubscriptionRequired()) throw SchemaError("Expected a Subscription object with name '$subscriptionName' but found none!") else null
return SchemaObjects(query, mutation, subscription, (objects + inputObjects + enums + interfaces + unions).toSet())
}
其中的 SchemaObjects 就是 GraphQLSchema 构造器的参数,不同类型的组装逻辑大同小异,这里就不啰嗦了。
启动过程简单图例:
三、执行query
query的执行是在运行时,我们通过统一的入口 GraphQL 执行客户端传过来的schema,GraphQL提供了两个类 ExecutionInput 和 ExecutionResult 用于包装输入和输出,GraphQL在执行查询时通过 CompletableFuture 来实现异步,其执行的核心代码如下:
public CompletableFuture executeAsync(ExecutionInput executionInput) {
try {
// 创建InstrumentationState对象,这是一个跟踪Instrumentation全生命周期的对象
InstrumentationState instrumentationState = instrumentation.createState(new InstrumentationCreateStateParameters(this.graphQLSchema, executionInput));
// 对ExecutionInput进行拦截
InstrumentationExecutionParameters inputInstrumentationParameters = new InstrumentationExecutionParameters(executionInput, this.graphQLSchema, instrumentationState);
executionInput = instrumentation.instrumentExecutionInput(executionInput, inputInstrumentationParameters);
// 执行前拦截
InstrumentationExecutionParameters instrumentationParameters = new InstrumentationExecutionParameters(executionInput, this.graphQLSchema, instrumentationState);
InstrumentationContext executionInstrumentation = instrumentation.beginExecution(instrumentationParameters);
// 执行前拦截
GraphQLSchema graphQLSchema = instrumentation.instrumentSchema(this.graphQLSchema, instrumentationParameters);
// 对客户端传递的query进行验证并执行
CompletableFuture executionResult = parseValidateAndExecute(executionInput, graphQLSchema, instrumentationState);
executionResult = executionResult.whenComplete(executionInstrumentation::onCompleted);
// 对执行结果进行拦截
executionResult = executionResult.thenCompose(result -> instrumentation.instrumentExecutionResult(result, instrumentationParameters));
return executionResult;
} catch (AbortExecutionException abortException) {
return CompletableFuture.completedFuture(abortException.toExecutionResult());
}
}
我们跳过省略掉验证部分直接看最终的执行,执行最终在 Execution 类中完成:
private CompletableFuture executeOperation(ExecutionContext executionContext, InstrumentationExecutionParameters instrumentationExecutionParameters, Object root, OperationDefinition operationDefinition) {
// ...
CompletableFuture result;
try {
ExecutionStrategy executionStrategy;
if (operation == OperationDefinition.Operation.MUTATION) {
executionStrategy = mutationStrategy;
} else if (operation == SUBSCRIPTION) {
executionStrategy = subscriptionStrategy;
} else {
executionStrategy = queryStrategy;
}
log.debug("Executing '{}' query operation: '{}' using '{}' execution strategy", executionContext.getExecutionId(), operation, executionStrategy.getClass().getName());
result = executionStrategy.execute(executionContext, parameters);
} catch (NonNullableFieldWasNullException e) {
// ...
}
// ...
return deferSupport(executionContext, result);
}
省略掉若干代码后我们关注 executionStrategy.execute(executionContext, parameters); 这句,这里有一个执行策略,这是我们在开发时可能会用到的。除了已废弃的几种执行策略,当前版本提供了两种执行策略: AsyncExecutionStrategy 和 AsyncSerialExecutionStrategy。前者是并行执行属性值获取,后者是串行执行属性值获取。
AsyncExecutionStrategy 关键代码:
public CompletableFuture execute(ExecutionContext executionContext, ExecutionStrategyParameters parameters) throws NonNullableFieldWasNullException {
// ...
Map> fields = parameters.getFields();
List fieldNames = new ArrayList<>(fields.keySet());
List> futures = new ArrayList<>();
List resolvedFields = new ArrayList<>();
for (String fieldName : fieldNames) {
List currentField = fields.get(fieldName);
ExecutionPath fieldPath = parameters.getPath().segment(mkNameForPath(currentField));
ExecutionStrategyParameters newParameters = parameters
.transform(builder -> builder.field(currentField).path(fieldPath).parent(parameters));
if (isDeferred(executionContext, newParameters, currentField)) {
executionStrategyCtx.onDeferredField(currentField);
continue;
}
resolvedFields.add(fieldName);
CompletableFuture future = resolveFieldWithInfo(executionContext, newParameters);
futures.add(future);
}
CompletableFuture overallResult = new CompletableFuture<>();
executionStrategyCtx.onDispatched(overallResult);
// 并行执行所有future
Async.each(futures).whenComplete((completeValueInfos, throwable) -> {
BiConsumer, Throwable> handleResultsConsumer = handleResults(executionContext, resolvedFields, overallResult);
if (throwable != null) {
handleResultsConsumer.accept(null, throwable.getCause());
return;
}
List> executionResultFuture = completeValueInfos.stream().map(FieldValueInfo::getFieldValue).collect(Collectors.toList());
executionStrategyCtx.onFieldValuesInfo(completeValueInfos);
Async.each(executionResultFuture).whenComplete(handleResultsConsumer);
}).exceptionally((ex) -> {
// if there are any issues with combining/handling the field results,
// complete the future at all costs and bubble up any thrown exception so
// the execution does not hang.
overallResult.completeExceptionally(ex);
return null;
});
overallResult.whenComplete(executionStrategyCtx::onCompleted);
return overallResult;
}
AsyncSerialExecutionStrategy 关键代码:
public CompletableFuture execute(ExecutionContext executionContext, ExecutionStrategyParameters parameters) throws NonNullableFieldWasNullException {
CompletableFuture> resultsFuture = Async.eachSequentially(fieldNames, (fieldName, index, prevResults) -> {
List currentField = fields.get(fieldName);
ExecutionPath fieldPath = parameters.getPath().segment(mkNameForPath(currentField));
ExecutionStrategyParameters newParameters = parameters
.transform(builder -> builder.field(currentField).path(fieldPath));
return resolveField(executionContext, newParameters);
});
CompletableFuture overallResult = new CompletableFuture<>();
executionStrategyCtx.onDispatched(overallResult);
resultsFuture.whenComplete(handleResults(executionContext, fieldNames, overallResult));
overallResult.whenComplete(executionStrategyCtx::onCompleted);
return overallResult;
}
// eachSequentially最终会调用下面的方法排队执行属性值获取
private static void eachSequentiallyImpl(Iterator iterator, CFFactory cfFactory, int index, List tmpResult, CompletableFuture> overallResult) {
if (!iterator.hasNext()) {
overallResult.complete(tmpResult);
return;
}
CompletableFuture cf;
try {
cf = cfFactory.apply(iterator.next(), index, tmpResult);
Assert.assertNotNull(cf, "cfFactory must return a non null value");
} catch (Exception e) {
cf = new CompletableFuture<>();
cf.completeExceptionally(new CompletionException(e));
}
cf.whenComplete((cfResult, exception) -> {
if (exception != null) {
overallResult.completeExceptionally(exception);
return;
}
// 上一个属性值获取完后再执行下一个属性值的获取
tmpResult.add(cfResult);
eachSequentiallyImpl(iterator, cfFactory, index + 1, tmpResult, overallResult);
});
}