<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
在上篇文章《初識GraphQL》中我們大致的瞭解了GraphQL作用,並通過簡單範例初步體驗了GraphQL的使用。下面我們從Hello World開始來進一步瞭解GraphQL背後的處理。
package com.graphqljava.tutorial.bookdetails; import graphql.ExecutionResult; import graphql.GraphQL; import graphql.schema.GraphQLSchema; import graphql.schema.StaticDataFetcher; import graphql.schema.idl.RuntimeWiring; import graphql.schema.idl.SchemaGenerator; import graphql.schema.idl.SchemaParser; import graphql.schema.idl.TypeDefinitionRegistry; public class HelloWorld { public static void main(String[] args) { // 從最簡單的schema字串開始,省去對graphqls檔案的讀取 String schema = "type Query{hello: String}"; // 用於獲得graphql schema定義,並解析放入TypeDefinitionRegistry中,以便放置在SchemaGenerator中使用 SchemaParser schemaParser = new SchemaParser(); // 解析schema定義字串,並建立包含一組型別定義的TypeDefinitionRegistry TypeDefinitionRegistry typeDefinitionRegistry = schemaParser.parse(schema); // runtime wiring 是data fetchers、type resolves和客製化標量的規範,這些都需要連線到GraphQLSchema中 RuntimeWiring runtimeWiring = RuntimeWiring.newRuntimeWiring() // 新增一個型別連線 .type("Query", builder -> builder.dataFetcher("hello", new StaticDataFetcher("world"))) .build(); //schemaGenerator物件可以使用typeDefinitionRegistry、runtimeWiring生成工作執行時schema SchemaGenerator schemaGenerator = new SchemaGenerator(); //graphQLSchema代表graphql引擎的組合型別系統。 GraphQLSchema graphQLSchema = schemaGenerator.makeExecutableSchema(typeDefinitionRegistry, runtimeWiring); //構建GraphQL用於執行查詢 GraphQL build = GraphQL.newGraphQL(graphQLSchema).build(); //執行並獲得結果 ExecutionResult executionResult = build.execute("{hello}"); System.out.println(executionResult.getData().toString()); } }
從上面的程式碼註釋可以看到GraphQL大致執行的過程:
從外層使用程式碼可以得出核心處理類為:SchemaParser、TypeDefinitionRegistry、RuntimeWiring、GraphQLSchema、GraphQL。
下面我們分配看看核心類是怎麼處理的。
解析schema字串定義並生成TypeDefinitionRegistry。
public TypeDefinitionRegistry parse(String schemaInput) throws SchemaProblem { try { Parser parser = new Parser(); Document document = parser.parseDocument(schemaInput); return buildRegistry(document); } catch (ParseCancellationException e) { throw handleParseException(e); } }
使用Document構建TypeDefinitionRegistry
public TypeDefinitionRegistry buildRegistry(Document document) { List<GraphQLError> errors = new ArrayList<>(); TypeDefinitionRegistry typeRegistry = new TypeDefinitionRegistry(); List<Definition> definitions = document.getDefinitions(); for (Definition definition : definitions) { if (definition instanceof SDLDefinition) { typeRegistry.add((SDLDefinition) definition).ifPresent(errors::add); } } if (errors.size() > 0) { throw new SchemaProblem(errors); } else { return typeRegistry; } }
可以看的出來TypeDefinitionRegistry只是對Document的定義提取,重點還是在於Document的生成,我們可以先通過debugger來先看看Document的大致內容。
可以看到就是把schema字串解析成了方便後續使用的Document物件,我們還是詳細看看這個物件裡面的屬性和大概的生成過程。
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()); //詞法分析從schema中解析出tokens(每個關鍵字、最後一個為EOF),documentContext包含children、start/stop字元等相當於結構。 GraphqlParser.DocumentContext documentContext = parser.document(); GraphqlAntlrToLanguage antlrToLanguage = new GraphqlAntlrToLanguage(tokens); // 生成document Document doc = antlrToLanguage.createDocument(documentContext); 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&documentContext
可以看到,主要是通過提取schema的關鍵字、識別結構最後生成Document主要內容為型別定義定義和型別定義中的欄位定義。
runtime wiring 是data fetchers、type resolves和客製化標量的規範,這些都需要連線到GraphQLSchema中。
這種形式允許使用lambda作為type wiring的構建器。
public Builder type(String typeName, UnaryOperator<TypeRuntimeWiring.Builder> builderFunction) { TypeRuntimeWiring.Builder builder = builderFunction.apply(TypeRuntimeWiring.newTypeWiring(typeName)); return type(builder.build()); }
新增type wiring。
public Builder type(TypeRuntimeWiring typeRuntimeWiring) { String typeName = typeRuntimeWiring.getTypeName(); Map<String, DataFetcher> typeDataFetchers = dataFetchers.computeIfAbsent(typeName, k -> new LinkedHashMap<>()); typeRuntimeWiring.getFieldDataFetchers().forEach(typeDataFetchers::put); defaultDataFetchers.put(typeName, typeRuntimeWiring.getDefaultDataFetcher()); TypeResolver typeResolver = typeRuntimeWiring.getTypeResolver(); if (typeResolver != null) { this.typeResolvers.put(typeName, typeResolver); } EnumValuesProvider enumValuesProvider = typeRuntimeWiring.getEnumValuesProvider(); if (enumValuesProvider != null) { this.enumValuesProviders.put(typeName, enumValuesProvider); } return this; }
可以看到主要就是網RuntimeWiring裡面新增了dataFetchers、defaultDataFetchers、typeResolvers、enumValuesProviders。下面分別介紹下各屬性的含義:
GraphQLScalarType:scalar type是graphql樹型別的葉節點。該型別允許你定義新的scalar type。
例子中通過傳入GraphQLSchema構建GraphQL。
public GraphQL build() { assertNotNull(graphQLSchema, "graphQLSchema must be non null"); assertNotNull(queryExecutionStrategy, "queryStrategy must be non null"); assertNotNull(idProvider, "idProvider must be non null"); return new GraphQL(graphQLSchema, queryExecutionStrategy, mutationExecutionStrategy, subscriptionExecutionStrategy, idProvider, instrumentation, preparsedDocumentProvider); }
除了graphQLSchema都是預設值,我們大概看看各個成員分別是用來幹嘛的:
下面我們還是來看看具體的執行:
public ExecutionResult execute(ExecutionInput executionInput) { try { return executeAsync(executionInput).join(); } catch (CompletionException e) { if (e.getCause() instanceof RuntimeException) { throw (RuntimeException) e.getCause(); } else { throw e; } } }
用提供的輸入物件執行graphql query。這將返回一個承諾(又名CompletableFuture),以提供一個ExecutionResult,這是執行所提供查詢的結果。
public CompletableFuture<ExecutionResult> executeAsync(ExecutionInput executionInput) { try { log.debug("Executing request. operation name: '{}'. query: '{}'. variables '{}'", executionInput.getOperationName(), executionInput.getQuery(), executionInput.getVariables()); // 建立InstrumentationState物件,這是一個跟蹤Instrumentation全生命週期的物件 InstrumentationState instrumentationState = instrumentation.createState(new InstrumentationCreateStateParameters(this.graphQLSchema, executionInput)); InstrumentationExecutionParameters inputInstrumentationParameters = new InstrumentationExecutionParameters(executionInput, this.graphQLSchema, instrumentationState); // 檢測輸入物件 executionInput = instrumentation.instrumentExecutionInput(executionInput, inputInstrumentationParameters); InstrumentationExecutionParameters instrumentationParameters = new InstrumentationExecutionParameters(executionInput, this.graphQLSchema, instrumentationState); // 在執行檢測 chain前呼叫 InstrumentationContext<ExecutionResult> executionInstrumentation = instrumentation.beginExecution(instrumentationParameters); // 檢測GraphQLSchema GraphQLSchema graphQLSchema = instrumentation.instrumentSchema(this.graphQLSchema, instrumentationParameters); // 對使用者端傳遞的query進行驗證並執行 CompletableFuture<ExecutionResult> executionResult = parseValidateAndExecute(executionInput, graphQLSchema, instrumentationState); // // finish up instrumentation executionResult = executionResult.whenComplete(executionInstrumentation::onCompleted); // // allow instrumentation to tweak the result executionResult = executionResult.thenCompose(result -> instrumentation.instrumentExecutionResult(result, instrumentationParameters)); return executionResult; } catch (AbortExecutionException abortException) { return CompletableFuture.completedFuture(abortException.toExecutionResult()); } }
parseValidateAndExecute(executionInput, graphQLSchema, instrumentationState)進行驗證並執行,驗證我們就不看了直接看執行:
private CompletableFuture<ExecutionResult> execute(ExecutionInput executionInput, Document document, GraphQLSchema graphQLSchema, InstrumentationState instrumentationState) { String query = executionInput.getQuery(); String operationName = executionInput.getOperationName(); Object context = executionInput.getContext(); Execution execution = new Execution(queryStrategy, mutationStrategy, subscriptionStrategy, instrumentation); ExecutionId executionId = idProvider.provide(query, operationName, context); log.debug("Executing '{}'. operation name: '{}'. query: '{}'. variables '{}'", executionId, executionInput.getOperationName(), executionInput.getQuery(), executionInput.getVariables()); CompletableFuture<ExecutionResult> future = execution.execute(document, graphQLSchema, executionId, executionInput, instrumentationState); future = future.whenComplete((result, throwable) -> { if (throwable != null) { log.error(String.format("Execution '%s' threw exception when executing : query : '%s'. variables '%s'", executionId, executionInput.getQuery(), executionInput.getVariables()), throwable); } else { int errorCount = result.getErrors().size(); if (errorCount > 0) { log.debug("Execution '{}' completed with '{}' errors", executionId, errorCount); } else { log.debug("Execution '{}' completed with zero errors", executionId); } } }); return future; }
這裡列印紀錄檔為
Executing '9c81e267-c55a-4ebd-9f9c-3a2270b28103'. operation name: 'null'. query: '{hello}'. variables '{}'
還要繼續往下看:
public CompletableFuture<ExecutionResult> execute(Document document, GraphQLSchema graphQLSchema, ExecutionId executionId, ExecutionInput executionInput, InstrumentationState instrumentationState) { // 獲得要執行的操作 NodeUtil.GetOperationResult getOperationResult = NodeUtil.getOperation(document, executionInput.getOperationName()); Map<String, FragmentDefinition> fragmentsByName = getOperationResult.fragmentsByName; OperationDefinition operationDefinition = getOperationResult.operationDefinition; ValuesResolver valuesResolver = new ValuesResolver(); // 獲得輸入的引數 Map<String, Object> inputVariables = executionInput.getVariables(); List<VariableDefinition> variableDefinitions = operationDefinition.getVariableDefinitions(); Map<String, Object> coercedVariables; try { coercedVariables = valuesResolver.coerceArgumentValues(graphQLSchema, variableDefinitions, inputVariables); } catch (RuntimeException rte) { if (rte instanceof GraphQLError) { return completedFuture(new ExecutionResultImpl((GraphQLError) rte)); } throw rte; } ExecutionContext executionContext = newExecutionContextBuilder() .instrumentation(instrumentation) .instrumentationState(instrumentationState) .executionId(executionId) .graphQLSchema(graphQLSchema) .queryStrategy(queryStrategy) .mutationStrategy(mutationStrategy) .subscriptionStrategy(subscriptionStrategy) .context(executionInput.getContext()) .root(executionInput.getRoot()) .fragmentsByName(fragmentsByName) .variables(coercedVariables) .document(document) .operationDefinition(operationDefinition) // 放入dataloder .dataLoaderRegistry(executionInput.getDataLoaderRegistry()) .build(); InstrumentationExecutionParameters parameters = new InstrumentationExecutionParameters( executionInput, graphQLSchema, instrumentationState ); // 獲得執行上下文 executionContext = instrumentation.instrumentExecutionContext(executionContext, parameters); return executeOperation(executionContext, parameters, executionInput.getRoot(), executionContext.getOperationDefinition()); }
獲得了執行上下文並執行,下面繼續看executeOperation
:
private CompletableFuture<ExecutionResult> executeOperation(ExecutionContext executionContext, InstrumentationExecutionParameters instrumentationExecutionParameters, Object root, OperationDefinition operationDefinition) { // ... ExecutionStrategyParameters parameters = newParameters() .executionStepInfo(executionStepInfo) .source(root) .fields(fields) .nonNullFieldValidator(nonNullableFieldValidator) .path(path) .build(); 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); }
紀錄檔輸出:
Executing '9c81e267-c55a-4ebd-9f9c-3a2270b28103' query operation: 'QUERY' using 'graphql.execution.AsyncExecutionStrategy' execution strategy
最終使用AsyncExecutionStrategy策略執行,繼續往下看:
public CompletableFuture<ExecutionResult> execute(ExecutionContext executionContext, ExecutionStrategyParameters parameters) throws NonNullableFieldWasNullException { Instrumentation instrumentation = executionContext.getInstrumentation(); InstrumentationExecutionStrategyParameters instrumentationParameters = new InstrumentationExecutionStrategyParameters(executionContext, parameters); ExecutionStrategyInstrumentationContext executionStrategyCtx = instrumentation.beginExecutionStrategy(instrumentationParameters); 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); // 處理欄位,這裡處理的是"hello" CompletableFuture<FieldValueInfo> future = resolveFieldWithInfo(executionContext, newParameters); futures.add(future); } CompletableFuture<ExecutionResult> overallResult = new CompletableFuture<>(); executionStrategyCtx.onDispatched(overallResult); //並行執行所有filed處理的futures 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; }
可以看到這裡會遍歷所有fileds拿到每個filed future,最後並行執行,下面具體看看:
呼叫該函數來獲取欄位的值及額外的執行時資訊,並根據graphql query內容進一步處理它。
protected CompletableFuture<FieldValueInfo> resolveFieldWithInfo(ExecutionContext executionContext, ExecutionStrategyParameters parameters) { GraphQLFieldDefinition fieldDef = getFieldDef(executionContext, parameters, parameters.getField().get(0)); Instrumentation instrumentation = executionContext.getInstrumentation(); InstrumentationContext<ExecutionResult> fieldCtx = instrumentation.beginField( new InstrumentationFieldParameters(executionContext, fieldDef, createExecutionStepInfo(executionContext, parameters, fieldDef)) ); CompletableFuture<Object> fetchFieldFuture = fetchField(executionContext, parameters); CompletableFuture<FieldValueInfo> result = fetchFieldFuture.thenApply((fetchedValue) -> completeField(executionContext, parameters, fetchedValue)); CompletableFuture<ExecutionResult> executionResultFuture = result.thenCompose(FieldValueInfo::getFieldValue); fieldCtx.onDispatched(executionResultFuture); executionResultFuture.whenComplete(fieldCtx::onCompleted); return result; }
呼叫該函數獲取filed值,使用從filed GraphQlFiledDefinition關聯的DataFetcher。
protected CompletableFuture<Object> fetchField(ExecutionContext executionContext, ExecutionStrategyParameters parameters) { Field field = parameters.getField().get(0); GraphQLObjectType parentType = (GraphQLObjectType) parameters.getExecutionStepInfo().getUnwrappedNonNullType(); GraphQLFieldDefinition fieldDef = getFieldDef(executionContext.getGraphQLSchema(), parentType, field); GraphqlFieldVisibility fieldVisibility = executionContext.getGraphQLSchema().getFieldVisibility(); Map<String, Object> argumentValues = valuesResolver.getArgumentValues(fieldVisibility, fieldDef.getArguments(), field.getArguments(), executionContext.getVariables()); GraphQLOutputType fieldType = fieldDef.getType(); DataFetchingFieldSelectionSet fieldCollector = DataFetchingFieldSelectionSetImpl.newCollector(executionContext, fieldType, parameters.getField()); // ... CompletableFuture<Object> fetchedValue; // 獲得dataFetcher,這裡為HelloWorld的`new StaticDataFetcher("world")` DataFetcher dataFetcher = fieldDef.getDataFetcher(); dataFetcher = instrumentation.instrumentDataFetcher(dataFetcher, instrumentationFieldFetchParams); ExecutionId executionId = executionContext.getExecutionId(); try { log.debug("'{}' fetching field '{}' using data fetcher '{}'...", executionId, executionStepInfo.getPath(), dataFetcher.getClass().getName()); // 執行dataFetcher獲取值,enviroment為上下文環境包含引數 Object fetchedValueRaw = dataFetcher.get(environment); log.debug("'{}' field '{}' fetch returned '{}'", executionId, executionStepInfo.getPath(), fetchedValueRaw == null ? "null" : fetchedValueRaw.getClass().getName()); // 如果是具體值就返回已經有值的CompletableFuture,如果是CompletionStage就直接返回 fetchedValue = Async.toCompletableFuture(fetchedValueRaw); } catch (Exception e) { log.debug(String.format("'%s', field '%s' fetch threw exception", executionId, executionStepInfo.getPath()), e); fetchedValue = new CompletableFuture<>(); fetchedValue.completeExceptionally(e); } fetchCtx.onDispatched(fetchedValue); // 對結果的後續處理 return fetchedValue .handle((result, exception) -> { fetchCtx.onCompleted(result, exception); if (exception != null) { handleFetchingException(executionContext, parameters, field, fieldDef, argumentValues, environment, exception); return null; } else { return result; } }) .thenApply(result -> unboxPossibleDataFetcherResult(executionContext, parameters, result)) .thenApply(this::unboxPossibleOptional); }
以上就是從Hello World開始理解GraphQL背後處理的詳細內容,更多關於GraphQL處理的資料請關注it145.com其它相關文章!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45