GraphQL(六):GraphQL源码解读 - 概览

基于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<Token> 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是这样的:

image

最终得到的分析树是这样的:


image

其中 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 构造器的参数,不同类型的组装逻辑大同小异,这里就不啰嗦了。

启动过程简单图例:


image

三、执行query

query的执行是在运行时,我们通过统一的入口 GraphQL 执行客户端传过来的schema,GraphQL提供了两个类 ExecutionInputExecutionResult 用于包装输入和输出,GraphQL在执行查询时通过 CompletableFuture 来实现异步,其执行的核心代码如下:

public CompletableFuture<ExecutionResult> 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<ExecutionResult> executionInstrumentation = instrumentation.beginExecution(instrumentationParameters);

        // 执行前拦截
        GraphQLSchema graphQLSchema = instrumentation.instrumentSchema(this.graphQLSchema, instrumentationParameters);

        // 对客户端传递的query进行验证并执行
        CompletableFuture<ExecutionResult> 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<ExecutionResult> executeOperation(ExecutionContext executionContext, InstrumentationExecutionParameters instrumentationExecutionParameters, Object root, OperationDefinition operationDefinition) {
        // ...
        CompletableFuture<ExecutionResult> 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); 这句,这里有一个执行策略,这是我们在开发时可能会用到的。除了已废弃的几种执行策略,当前版本提供了两种执行策略: AsyncExecutionStrategyAsyncSerialExecutionStrategy。前者是并行执行属性值获取,后者是串行执行属性值获取。

AsyncExecutionStrategy 关键代码:

 public CompletableFuture<ExecutionResult> execute(ExecutionContext executionContext, ExecutionStrategyParameters parameters) throws NonNullableFieldWasNullException {
        // ...

        Map<String, List<Field>> fields = parameters.getFields();
        List<String> fieldNames = new ArrayList<>(fields.keySet());
        List<CompletableFuture<FieldValueInfo>> futures = new ArrayList<>();
        List<String> resolvedFields = new ArrayList<>();
        for (String fieldName : fieldNames) {
            List<Field> 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<FieldValueInfo> future = resolveFieldWithInfo(executionContext, newParameters);
            futures.add(future);
        }
        CompletableFuture<ExecutionResult> overallResult = new CompletableFuture<>();
        executionStrategyCtx.onDispatched(overallResult);

        // 并行执行所有future
        Async.each(futures).whenComplete((completeValueInfos, throwable) -> {
            BiConsumer<List<ExecutionResult>, Throwable> handleResultsConsumer = handleResults(executionContext, resolvedFields, overallResult);
            if (throwable != null) {
                handleResultsConsumer.accept(null, throwable.getCause());
                return;
            }
            List<CompletableFuture<ExecutionResult>> 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<ExecutionResult> execute(ExecutionContext executionContext, ExecutionStrategyParameters parameters) throws NonNullableFieldWasNullException {
    CompletableFuture<List<ExecutionResult>> resultsFuture = Async.eachSequentially(fieldNames, (fieldName, index, prevResults) -> {
        List<Field> 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<ExecutionResult> overallResult = new CompletableFuture<>();
    executionStrategyCtx.onDispatched(overallResult);

    resultsFuture.whenComplete(handleResults(executionContext, fieldNames, overallResult));
    overallResult.whenComplete(executionStrategyCtx::onCompleted);
    return overallResult;
}

// eachSequentially最终会调用下面的方法排队执行属性值获取
private static <T, U> void eachSequentiallyImpl(Iterator<T> iterator, CFFactory<T, U> cfFactory, int index, List<U> tmpResult, CompletableFuture<List<U>> overallResult) {
    if (!iterator.hasNext()) {
        overallResult.complete(tmpResult);
        return;
    }
    CompletableFuture<U> 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);
    });
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,980评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,178评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,868评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,498评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,492评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,521评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,910评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,569评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,793评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,559评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,639评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,342评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,931评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,904评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,144评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,833评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,350评论 2 342

推荐阅读更多精彩内容