Auto-configure DataLoaderRegistrar components

This commit configures all `DataLoaderRegistrar` application components
with the `GraphQlService`. This allows applications to contribute
`DataLoader` instances without using the annotated-controller
programming model.

Closes gh-189
This commit is contained in:
Brian Clozel
2021-11-24 16:18:40 +01:00
parent d0adceee4b
commit 36a84f58bd
6 changed files with 49 additions and 7 deletions

View File

@@ -18,6 +18,7 @@ package org.springframework.graphql.boot;
import graphql.GraphQL;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
@@ -28,14 +29,14 @@ import org.springframework.format.support.DefaultFormattingConversionService;
import org.springframework.graphql.GraphQlService;
import org.springframework.graphql.data.method.annotation.support.AnnotatedControllerConfigurer;
import org.springframework.graphql.execution.BatchLoaderRegistry;
import org.springframework.graphql.execution.DataLoaderRegistrar;
import org.springframework.graphql.execution.DefaultBatchLoaderRegistry;
import org.springframework.graphql.execution.ExecutionGraphQlService;
import org.springframework.graphql.execution.GraphQlSource;
import org.springframework.graphql.web.WebGraphQlHandler;
/**
* {@link EnableAutoConfiguration Auto-configuration} for creating a
* {@link WebGraphQlHandler}.
* {@link GraphQlService}.
*
* @author Brian Clozel
* @since 1.0.0
@@ -45,19 +46,17 @@ import org.springframework.graphql.web.WebGraphQlHandler;
@AutoConfigureAfter(GraphQlAutoConfiguration.class)
public class GraphQlServiceAutoConfiguration {
private final BatchLoaderRegistry batchLoaderRegistry = new DefaultBatchLoaderRegistry();
@Bean
@ConditionalOnMissingBean
public BatchLoaderRegistry batchLoaderRegistry() {
return this.batchLoaderRegistry;
return new DefaultBatchLoaderRegistry();
}
@Bean
@ConditionalOnMissingBean
public GraphQlService graphQlService(GraphQlSource graphQlSource) {
public GraphQlService graphQlService(GraphQlSource graphQlSource, ObjectProvider<DataLoaderRegistrar> dataLoaderRegistrars) {
ExecutionGraphQlService service = new ExecutionGraphQlService(graphQlSource);
service.addDataLoaderRegistrar(this.batchLoaderRegistry);
dataLoaderRegistrars.forEach(service::addDataLoaderRegistrar);
return service;
}

View File

@@ -49,6 +49,7 @@ import org.springframework.test.context.junit.jupiter.SpringExtension;
* <ul>
* <li>{@code @Controller}
* <li>{@code RuntimeWiringConfigurer}
* <li>{@code DataLoaderRegistrar}
* <li>{@code @JsonComponent}
* <li>{@code Converter}
* <li>{@code GenericConverter}

View File

@@ -26,6 +26,7 @@ import org.springframework.boot.jackson.JsonComponent;
import org.springframework.boot.test.autoconfigure.filter.StandardAnnotationCustomizableTypeExcludeFilter;
import org.springframework.core.convert.converter.Converter;
import org.springframework.core.convert.converter.GenericConverter;
import org.springframework.graphql.execution.DataLoaderRegistrar;
import org.springframework.graphql.execution.RuntimeWiringConfigurer;
import org.springframework.stereotype.Controller;
import org.springframework.util.ClassUtils;
@@ -49,6 +50,7 @@ public class GraphQlTypeExcludeFilter extends StandardAnnotationCustomizableType
Set<Class<?>> includes = new LinkedHashSet<>();
includes.add(JsonComponent.class);
includes.add(RuntimeWiringConfigurer.class);
includes.add(DataLoaderRegistrar.class);
includes.add(Converter.class);
includes.add(GenericConverter.class);
for (String optionalInclude : OPTIONAL_INCLUDES) {

View File

@@ -25,6 +25,7 @@ import org.springframework.context.annotation.Configuration;
import org.springframework.graphql.GraphQlService;
import org.springframework.graphql.data.method.annotation.support.AnnotatedControllerConfigurer;
import org.springframework.graphql.execution.BatchLoaderRegistry;
import org.springframework.graphql.execution.DataLoaderRegistrar;
import org.springframework.graphql.execution.GraphQlSource;
import static org.assertj.core.api.Assertions.assertThat;
@@ -50,6 +51,16 @@ class GraphQlServiceAutoConfigurationTests {
});
}
@Test
void shouldConfigureDataLoaderRegistrars() {
this.contextRunner.withUserConfiguration(CustomDataLoaderRegistrar.class)
.run((context) -> {
assertThat(context).hasSingleBean(GraphQlService.class);
assertThat(context).getBeanNames(DataLoaderRegistrar.class).contains("batchLoaderRegistry", "customDataLoaderRegistrar");
assertThat(context).getBean(GraphQlService.class).extracting("dataLoaderRegistrars").asList().hasSize(2);
});
}
@Configuration(proxyBeanMethods = false)
static class GraphQlSourceConfiguration {
@@ -59,4 +70,13 @@ class GraphQlServiceAutoConfigurationTests {
}
}
@Configuration(proxyBeanMethods = false)
static class CustomDataLoaderRegistrar {
@Bean
DataLoaderRegistrar customDataLoaderRegistrar() {
return mock(DataLoaderRegistrar.class);
}
}
}

View File

@@ -19,7 +19,9 @@ package org.springframework.graphql.boot.test;
import java.io.IOException;
import com.fasterxml.jackson.databind.module.SimpleModule;
import graphql.GraphQLContext;
import graphql.schema.idl.RuntimeWiring;
import org.dataloader.DataLoaderRegistry;
import org.junit.jupiter.api.Test;
import reactor.core.publisher.Mono;
@@ -28,6 +30,7 @@ import org.springframework.context.annotation.FilterType;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.MetadataReaderFactory;
import org.springframework.core.type.classreading.SimpleMetadataReaderFactory;
import org.springframework.graphql.execution.DataLoaderRegistrar;
import org.springframework.graphql.execution.RuntimeWiringConfigurer;
import org.springframework.graphql.web.WebInput;
import org.springframework.graphql.web.WebInterceptor;
@@ -55,6 +58,7 @@ class GraphQlTypeExcludeFilterTests {
assertThat(excludes(filter, Controller1.class)).isFalse();
assertThat(excludes(filter, Controller2.class)).isFalse();
assertThat(excludes(filter, ExampleRuntimeWiringConfigurer.class)).isFalse();
assertThat(excludes(filter, ExampleDataLoaderRegistrar.class)).isFalse();
assertThat(excludes(filter, ExampleService.class)).isTrue();
assertThat(excludes(filter, ExampleRepository.class)).isTrue();
assertThat(excludes(filter, ExampleWebInterceptor.class)).isTrue();
@@ -67,6 +71,7 @@ class GraphQlTypeExcludeFilterTests {
assertThat(excludes(filter, Controller1.class)).isFalse();
assertThat(excludes(filter, Controller2.class)).isTrue();
assertThat(excludes(filter, ExampleRuntimeWiringConfigurer.class)).isFalse();
assertThat(excludes(filter, ExampleDataLoaderRegistrar.class)).isFalse();
assertThat(excludes(filter, ExampleService.class)).isTrue();
assertThat(excludes(filter, ExampleRepository.class)).isTrue();
assertThat(excludes(filter, ExampleWebInterceptor.class)).isTrue();
@@ -79,6 +84,7 @@ class GraphQlTypeExcludeFilterTests {
assertThat(excludes(filter, Controller1.class)).isTrue();
assertThat(excludes(filter, Controller2.class)).isTrue();
assertThat(excludes(filter, ExampleRuntimeWiringConfigurer.class)).isTrue();
assertThat(excludes(filter, ExampleDataLoaderRegistrar.class)).isTrue();
assertThat(excludes(filter, ExampleService.class)).isTrue();
assertThat(excludes(filter, ExampleRepository.class)).isTrue();
assertThat(excludes(filter, ExampleWebInterceptor.class)).isTrue();
@@ -91,6 +97,7 @@ class GraphQlTypeExcludeFilterTests {
assertThat(excludes(filter, Controller1.class)).isFalse();
assertThat(excludes(filter, Controller2.class)).isFalse();
assertThat(excludes(filter, ExampleRuntimeWiringConfigurer.class)).isFalse();
assertThat(excludes(filter, ExampleDataLoaderRegistrar.class)).isFalse();
assertThat(excludes(filter, ExampleService.class)).isTrue();
assertThat(excludes(filter, ExampleRepository.class)).isFalse();
assertThat(excludes(filter, ExampleWebInterceptor.class)).isTrue();
@@ -103,6 +110,7 @@ class GraphQlTypeExcludeFilterTests {
assertThat(excludes(filter, Controller1.class)).isTrue();
assertThat(excludes(filter, Controller2.class)).isFalse();
assertThat(excludes(filter, ExampleRuntimeWiringConfigurer.class)).isFalse();
assertThat(excludes(filter, ExampleDataLoaderRegistrar.class)).isFalse();
assertThat(excludes(filter, ExampleService.class)).isTrue();
assertThat(excludes(filter, ExampleRepository.class)).isTrue();
assertThat(excludes(filter, ExampleWebInterceptor.class)).isTrue();
@@ -166,6 +174,13 @@ class GraphQlTypeExcludeFilterTests {
}
}
static class ExampleDataLoaderRegistrar implements DataLoaderRegistrar {
@Override
public void registerDataLoaders(DataLoaderRegistry registry, GraphQLContext context) {
}
}
static class ExampleWebInterceptor implements WebInterceptor {
@Override

View File

@@ -258,6 +258,10 @@ public class BookController {
}
----
`BatchLoaderRegistry` is itself a `DataLoaderRegistrar`, as it will register `DataLoader` instances for each request.
Applications can also contribute other `DataLoaderRegistrar` instances as components - they will be configured with
the `ExecutionGraphQlService` automatically.
@@ -451,6 +455,7 @@ By default, `@GraphQlTest` limits scanning to the following beans:
- `@Controller`
- `RuntimeWiringConfigurer`
- `DataLoaderRegistrar`
- `JsonComponent`
- `Converter`
- `GenericConverter`