原文:http://blog.mygraphql.com/wordpress/?p=112
拦截器Instrumentation
通过实现 graphql.execution.instrumentation.Instrumentation
接口,你可以在执行查询的过程中注入定制代码。并可以修改运行期的行为。
它的主要用途是性能监控和定制日志,但也可以完成其它任务。
当创建 `Graphql
对象时,可以绑定相关的 Instrumentation
。
GraphQL.newGraphQL(schema)
.instrumentation(new TracingInstrumentation())
.build();
定制拦截器(Custom Instrumentation)
要实现 Instrumentation
,需要实现多个 “begin”
开头的方法。这方法会在查询执行过程中,每一步骤开始前被调用。
所有回调方法,都应该返回
graphql.execution.instrumentation.InstrumentationContext
对象,这个对象会在本步骤完成时被回调用,回调用时会告知数据的获取结果,如果出错,可以获取
Throwable 对象。.
下面是一个定制的 Instrumentation
。作用是测量执行时间。
class CustomInstrumentationState implements InstrumentationState {
private Map<String, Object> anyStateYouLike = new HashMap<>();
void recordTiming(String key, long time) {
anyStateYouLike.put(key, time);
}
}
class CustomInstrumentation implements Instrumentation {
@Override
public InstrumentationState createState() {
//
// instrumentation state is passed during each invocation of an Instrumentation method
// and allows you to put stateful data away and reference it during the query execution
//
return new CustomInstrumentationState();
}
@Override
public InstrumentationContext<ExecutionResult> beginExecution(InstrumentationExecutionParameters parameters) {
long startNanos = System.nanoTime();
return (result, throwable) -> {
CustomInstrumentationState state = parameters.getInstrumentationState();
state.recordTiming(parameters.getQuery(), System.nanoTime() - startNanos);
};
}
@Override
public InstrumentationContext<Document> beginParse(InstrumentationExecutionParameters parameters) {
//
// You MUST return a non null object but it does not have to do anything and hence
// you use this class to return a no-op object
//
return new NoOpInstrumentation.NoOpInstrumentationContext<>();
}
@Override
public InstrumentationContext<List<ValidationError>> beginValidation(InstrumentationValidationParameters parameters) {
return new NoOpInstrumentation.NoOpInstrumentationContext<>();
}
@Override
public InstrumentationContext<ExecutionResult> beginDataFetch(InstrumentationDataFetchParameters parameters) {
return new NoOpInstrumentation.NoOpInstrumentationContext<>();
}
@Override
public InstrumentationContext<CompletableFuture<ExecutionResult>> beginExecutionStrategy(InstrumentationExecutionStrategyParameters parameters) {
return new NoOpInstrumentation.NoOpInstrumentationContext<>();
}
@Override
public InstrumentationContext<ExecutionResult> beginField(InstrumentationFieldParameters parameters) {
return new NoOpInstrumentation.NoOpInstrumentationContext<>();
}
@Override
public InstrumentationContext<Object> beginFieldFetch(InstrumentationFieldFetchParameters parameters) {
return new NoOpInstrumentation.NoOpInstrumentationContext<>();
}
@Override
public DataFetcher<?> instrumentDataFetcher(DataFetcher<?> dataFetcher, InstrumentationFieldFetchParameters parameters) {
//
// this allows you to intercept the data fetcher used ot fetch a field and provide another one, perhaps
// that enforces certain behaviours or has certain side effects on the data
//
return dataFetcher;
}
@Override
public CompletableFuture<ExecutionResult> instrumentExecutionResult(ExecutionResult executionResult, InstrumentationExecutionParameters parameters) {
//
// this allows you to instrument the execution result some how. For example the Tracing support uses this to put
// the `extensions` map of data in place
//
return CompletableFuture.completedFuture(executionResult);
}
}
链式拦截(Chaining Instrumentation)
你可以用 graphql.execution.instrumentation.ChainedInstrumentation
把多个 Instrumentation
连接起来。这些 Instrumentation
对象会按顺序被调用。
List<Instrumentation> chainedList = new ArrayList<>();
chainedList.add(new FooInstrumentation());
chainedList.add(new BarInstrumentation());
ChainedInstrumentation chainedInstrumentation = new ChainedInstrumentation(chainedList);
GraphQL.newGraphQL(schema)
.instrumentation(chainedInstrumentation)
.build();
Apollo跟踪与拦截( Tracing Instrumentation)
graphql.execution.instrumentation.tracing.TracingInstrumentation
是一个可以收集跟踪信息的拦截器。
它按照 Apollo 跟踪格式 https://github.com/apollographql/apollo-tracing
来收集跟踪信息。
详细的跟踪信息( tracing map)会放在查询结果的 extensions(扩展)
部分。
如以下的查询:
query {
hero {
name
friends {
name
}
}
}
会返回如下的结果:
{
"data": {
"hero": {
"name": "R2-D2",
"friends": [
{
"name": "Luke Skywalker"
},
{
"name": "Han Solo"
},
{
"name": "Leia Organa"
}
]
}
},
"extensions": {
"tracing": {
"version": 1,
"startTime": "2017-08-14T23:13:39.362Z",
"endTime": "2017-08-14T23:13:39.497Z",
"duration": 135589186,
"execution": {
"resolvers": [
{
"path": [
"hero"
],
"parentType": "Query",
"returnType": "Character",
"fieldName": "hero",
"startOffset": 105697585,
"duration": 79111240
},
{
"path": [
"hero",
"name"
],
"parentType": "Droid",
"returnType": "String",
"fieldName": "name",
"startOffset": 125010028,
"duration": 20213
},
{
"path": [
"hero",
"friends"
],
"parentType": "Droid",
"returnType": "[Character]",
"fieldName": "friends",
"startOffset": 133352819,
"duration": 7927560
},
{
"path": [
"hero",
"friends",
0,
"name"
],
"parentType": "Human",
"returnType": "String",
"fieldName": "name",
"startOffset": 134105887,
"duration": 6783
},
{
"path": [
"hero",
"friends",
1,
"name"
],
"parentType": "Human",
"returnType": "String",
"fieldName": "name",
"startOffset": 134725922,
"duration": 7016
},
{
"path": [
"hero",
"friends",
2,
"name"
],
"parentType": "Human",
"returnType": "String",
"fieldName": "name",
"startOffset": 134875089,
"duration": 6342
}
]
}
}
}
}
字段校验拦截器(Field Validation Instrumentation)
graphql.execution.instrumentation.fieldvalidation.FieldValidationInstrumentation
拦截器,可以在执行查询前校验字段和字段参数。如果校验失败,查询将停止,并返回错误信息。
你可以编写自己的FieldValidation
实现,或者直接用
SimpleFieldValidation
去为每个field定义校验逻辑。
ExecutionPath fieldPath = ExecutionPath.parse("/user");
FieldValidation fieldValidation = new SimpleFieldValidation()
.addRule(fieldPath, new BiFunction<FieldAndArguments, FieldValidationEnvironment, Optional<GraphQLError>>() {
@Override
public Optional<GraphQLError> apply(FieldAndArguments fieldAndArguments, FieldValidationEnvironment environment) {
String nameArg = fieldAndArguments.getFieldArgument("name");
if (nameArg.length() > 255) {
return Optional.of(environment.mkError("Invalid user name", fieldAndArguments));
}
return Optional.empty();
}
});
FieldValidationInstrumentation instrumentation = new FieldValidationInstrumentation(
fieldValidation
);
GraphQL.newGraphQL(schema)
.instrumentation(instrumentation)
.build();