基于Spring Boot Starter的GraphQL SPQR响应扩展添加方法问询
解决方案:为GraphQL响应添加根节点与错误节点的
extensions字段 针对你的技术栈(Spring Boot + graphql-spqr-spring-boot-starter 0.0.7 + Apache Camel),可以通过以下两种独立的方案分别实现根节点和错误节点的extensions字段填充:
一、给响应根节点添加extensions
方案1:自定义Instrumentation(推荐)
利用graphql-java的Instrumentation扩展点,拦截查询执行结果并添加自定义extensions,无需替换核心执行器:
import graphql.ExecutionResult; import graphql.execution.instrumentation.Instrumentation; import graphql.execution.instrumentation.InstrumentationExecutionParameters; import graphql.execution.ExecutionResultImpl; import org.springframework.stereotype.Component; import java.util.HashMap; import java.util.Map; import java.util.UUID; @Component public class RootExtensionsInstrumentation implements Instrumentation { @Override public ExecutionResult instrumentExecutionResult(ExecutionResult executionResult, InstrumentationExecutionParameters parameters) { // 保留原始extensions(如果有),添加自定义信息 Map<String, Object> extensions = new HashMap<>(executionResult.getExtensions()); extensions.put("requestId", UUID.randomUUID().toString()); // 示例:请求唯一标识 extensions.put("serviceVersion", "v1.0.0"); // 示例:服务版本 extensions.put("executionDurationMs", System.currentTimeMillis() - parameters.getStartTime()); // 示例:执行耗时 // 包装成新的ExecutionResult返回 return new ExecutionResultImpl( executionResult.getData(), executionResult.getErrors(), extensions ); } }
Spring会自动检测并注册这个Instrumentation,无需额外配置。
方案2:自定义GraphQLMvcExecutor(修复你之前的实现)
你之前的自定义Executor问题在于直接调用super.execute后无法修改已序列化的响应对象,正确做法是先手动执行查询得到ExecutionResult,修改后再交给父类处理序列化:
import com.fasterxml.jackson.databind.ObjectMapper; import graphql.ExecutionResult; import graphql.GraphQL; import io.leangen.graphql.execution.ExecutorParams; import io.leangen.graphql.execution.context.ContextFactory; import io.leangen.graphql.execution.dataLoader.DataLoaderRegistryFactory; import io.leangen.graphql.server.execution.HttpExecutor; import io.leangen.graphql.server.execution.GraphQLMvcExecutor; import org.springframework.web.context.request.NativeWebRequest; import java.util.HashMap; import java.util.Map; import java.util.UUID; public class CustomGraphQLExecutor extends HttpExecutor<NativeWebRequest> implements GraphQLMvcExecutor { private final ObjectMapper objectMapper; public CustomGraphQLExecutor(ContextFactory<NativeWebRequest> contextFactory, DataLoaderRegistryFactory dataLoaderRegistryFactory, ObjectMapper objectMapper) { super(contextFactory, dataLoaderRegistryFactory); this.objectMapper = objectMapper; } @Override public Object execute(GraphQL graphQL, ExecutorParams<NativeWebRequest> executorParams) { // 1. 手动执行GraphQL查询,获取原始ExecutionResult ExecutionResult rawResult = graphQL.execute(executorParams.getExecutionInput()); // 2. 添加根节点extensions Map<String, Object> extensions = new HashMap<>(rawResult.getExtensions()); extensions.put("requestId", UUID.randomUUID().toString()); extensions.put("gatewayNode", "node-01"); // 3. 包装成带自定义extensions的结果 ExecutionResult customResult = new ExecutionResultImpl( rawResult.getData(), rawResult.getErrors(), extensions ); // 4. 交给父类处理成最终响应 return super.processExecutionResult(customResult, executorParams); } }
然后注册这个Executor为Bean,替换默认实现:
import com.fasterxml.jackson.databind.ObjectMapper; import io.leangen.graphql.execution.context.ContextFactory; import io.leangen.graphql.execution.dataLoader.DataLoaderRegistryFactory; import io.leangen.graphql.server.execution.GraphQLMvcExecutor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.context.request.NativeWebRequest; @Configuration public class GraphQLConfig { @Bean public GraphQLMvcExecutor graphQLMvcExecutor(ContextFactory<NativeWebRequest> contextFactory, DataLoaderRegistryFactory dataLoaderRegistryFactory, ObjectMapper objectMapper) { return new CustomGraphQLExecutor(contextFactory, dataLoaderRegistryFactory, objectMapper); } }
二、给错误节点添加extensions
通过自定义GraphQLErrorHandler和错误适配器,将业务异常或GraphQL原生错误转换为带extensions的格式:
1. 定义自定义业务异常
public class BackendServiceException extends RuntimeException { private final String errorCode; private final Map<String, Object> debugInfo; public BackendServiceException(String message, String errorCode, Map<String, Object> debugInfo) { super(message); this.errorCode = errorCode; this.debugInfo = debugInfo; } // Getter方法 public String getErrorCode() { return errorCode; } public Map<String, Object> getDebugInfo() { return debugInfo; } }
2. 自定义GraphQLError适配器
import graphql.ErrorClassification; import graphql.GraphQLError; import graphql.language.SourceLocation; import graphql.execution.ExceptionWhileDataFetching; import org.springframework.stereotype.Component; import java.util.HashMap; import java.util.List; import java.util.Map; @Component public class CustomGraphQLErrorAdapter implements GraphQLError { private final GraphQLError delegate; public CustomGraphQLErrorAdapter(GraphQLError delegate) { this.delegate = delegate; } @Override public String getMessage() { return delegate.getMessage(); } @Override public List<SourceLocation> getLocations() { return delegate.getLocations(); } @Override public ErrorClassification getErrorType() { return delegate.getErrorType(); } @Override public List<Object> getPath() { return delegate.getPath(); } @Override public Map<String, Object> getExtensions() { Map<String, Object> extensions = new HashMap<>(delegate.getExtensions()); // 处理业务异常 if (delegate instanceof ExceptionWhileDataFetching) { ExceptionWhileDataFetching dataFetchError = (ExceptionWhileDataFetching) delegate; Throwable cause = dataFetchError.getException(); if (cause instanceof BackendServiceException) { BackendServiceException serviceEx = (BackendServiceException) cause; extensions.put("errorCode", serviceEx.getErrorCode()); extensions.put("debugInfo", serviceEx.getDebugInfo()); } } // 给所有错误添加通用信息 extensions.put("timestamp", System.currentTimeMillis()); extensions.put("service", "graphql-rest-gateway"); return extensions; } @Override public Throwable getException() { return delegate.getException(); } }
3. 注册自定义GraphQLErrorHandler
import graphql.GraphQLError; import graphql.GraphQLErrorHandler; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import java.util.List; import java.util.stream.Collectors; @Configuration public class ErrorHandlerConfig { @Bean public GraphQLErrorHandler graphQLErrorHandler(CustomGraphQLErrorAdapter errorAdapter) { return new GraphQLErrorHandler() { @Override public List<GraphQLError> processErrors(List<GraphQLError> errors) { // 将所有错误转换为自定义格式 return errors.stream() .map(errorAdapter::new) .collect(Collectors.toList()); } }; } }
4. 在业务代码中抛出自定义异常
修改你的GraphQLAPIService,捕获Camel调用异常并转换为自定义业务异常:
@Service @GraphQLApi public class GraphQLAPIService { private final ProducerTemplate producerTemplate; public GraphQLAPIService(ProducerTemplate producerTemplate) { this.producerTemplate = producerTemplate; } @GraphQLQuery(name = "getInfo") public LoginInfo[] getInfo(@GraphQLArgument(name = "name") String name, @GraphQLArgument(name = "nick") String nick) { try { final Map<String, Object> headers = new HashMap<>(); headers.put("name", name); headers.put("nick", nick); return producerTemplate.requestBodyAndHeaders("direct:getInfo", null, headers, LoginInfo[].class); } catch (CamelExecutionException e) { // 包装Camel异常为自定义业务异常 Map<String, Object> debugInfo = new HashMap<>(); debugInfo.put("endpoint", "direct:getInfo"); debugInfo.put("requestParams", Map.of("name", name, "nick", nick)); debugInfo.put("backendError", e.getCause().getMessage()); throw new BackendServiceException("Failed to fetch data from backend", "BACKEND_CALL_FAILED", debugInfo); } } }
内容的提问来源于stack exchange,提问作者PastorPL
相关产品推荐
相关产品推荐

