From 378e56f1d30f1a10d50de7f340236fb0af842b1c Mon Sep 17 00:00:00 2001 From: Phillip Webb Date: Mon, 13 Jun 2022 16:44:20 -0700 Subject: [PATCH] Add Order annotation to GraphQL RouterFunction beans Update GraphQL auto-configuration so that `RouterFunction` beans have and `@Order` of 0. Fixes gh-31314 --- .../GraphQlWebFluxAutoConfiguration.java | 6 ++-- .../GraphQlWebMvcAutoConfiguration.java | 2 ++ .../GraphQlWebFluxAutoConfigurationTests.java | 31 ++++++++++++++++++ .../GraphQlWebMvcAutoConfigurationTests.java | 32 +++++++++++++++++++ .../src/docs/asciidoc/web/spring-graphql.adoc | 6 +++- 5 files changed, 74 insertions(+), 3 deletions(-) diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/graphql/reactive/GraphQlWebFluxAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/graphql/reactive/GraphQlWebFluxAutoConfiguration.java index a9261117d0f8..70178c801634 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/graphql/reactive/GraphQlWebFluxAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/graphql/reactive/GraphQlWebFluxAutoConfiguration.java @@ -38,6 +38,7 @@ import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.core.annotation.Order; import org.springframework.core.log.LogMessage; import org.springframework.graphql.ExecutionGraphQlService; import org.springframework.graphql.execution.GraphQlSource; @@ -101,8 +102,9 @@ public WebGraphQlHandler webGraphQlHandler(ExecutionGraphQlService service, } @Bean - public RouterFunction graphQlEndpoint(GraphQlHttpHandler httpHandler, GraphQlSource graphQlSource, - GraphQlProperties properties) { + @Order(0) + public RouterFunction graphQlRouterFunction(GraphQlHttpHandler httpHandler, + GraphQlSource graphQlSource, GraphQlProperties properties) { String path = properties.getPath(); logger.info(LogMessage.format("GraphQL endpoint HTTP POST %s", path)); RouterFunctions.Builder builder = RouterFunctions.route(); diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/graphql/servlet/GraphQlWebMvcAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/graphql/servlet/GraphQlWebMvcAutoConfiguration.java index 8a9fd350b04b..15a3d46ac765 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/graphql/servlet/GraphQlWebMvcAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/graphql/servlet/GraphQlWebMvcAutoConfiguration.java @@ -41,6 +41,7 @@ import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.core.annotation.Order; import org.springframework.core.log.LogMessage; import org.springframework.graphql.ExecutionGraphQlService; import org.springframework.graphql.execution.GraphQlSource; @@ -106,6 +107,7 @@ public WebGraphQlHandler webGraphQlHandler(ExecutionGraphQlService service, } @Bean + @Order(0) public RouterFunction graphQlRouterFunction(GraphQlHttpHandler httpHandler, GraphQlSource graphQlSource, GraphQlProperties properties) { String path = properties.getPath(); diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/graphql/reactive/GraphQlWebFluxAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/graphql/reactive/GraphQlWebFluxAutoConfigurationTests.java index 797ec5f2abf7..2c498b56f90e 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/graphql/reactive/GraphQlWebFluxAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/graphql/reactive/GraphQlWebFluxAutoConfigurationTests.java @@ -17,6 +17,7 @@ package org.springframework.boot.autoconfigure.graphql.reactive; import java.util.Collections; +import java.util.Map; import java.util.function.Consumer; import graphql.schema.idl.TypeRuntimeWiring; @@ -32,6 +33,7 @@ import org.springframework.boot.test.context.runner.ReactiveWebApplicationContextRunner; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.core.annotation.Order; import org.springframework.graphql.execution.RuntimeWiringConfigurer; import org.springframework.graphql.server.WebGraphQlHandler; import org.springframework.graphql.server.WebGraphQlInterceptor; @@ -41,6 +43,7 @@ import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.test.web.reactive.server.WebTestClient; +import org.springframework.web.reactive.function.server.RouterFunction; import static org.assertj.core.api.Assertions.assertThat; import static org.hamcrest.Matchers.containsString; @@ -147,6 +150,17 @@ void shouldConfigureWebSocketBeans() { .run((context) -> assertThat(context).hasSingleBean(GraphQlWebSocketHandler.class)); } + @Test + void routerFunctionShouldHaveOrderZero() throws Exception { + this.contextRunner.withUserConfiguration(CustomRouterFunctions.class).run((context) -> { + Map beans = context.getBeansOfType(RouterFunction.class); + Object[] ordered = context.getBeanProvider(RouterFunction.class).orderedStream().toArray(); + assertThat(beans.get("before")).isSameAs(ordered[0]); + assertThat(beans.get("graphQlRouterFunction")).isSameAs(ordered[1]); + assertThat(beans.get("after")).isSameAs(ordered[2]); + }); + } + private void testWithWebClient(Consumer consumer) { this.contextRunner.run((context) -> { WebTestClient client = WebTestClient.bindToApplicationContext(context).configureClient() @@ -180,4 +194,21 @@ WebGraphQlInterceptor customWebGraphQlInterceptor() { } + @Configuration + static class CustomRouterFunctions { + + @Bean + @Order(-1) + RouterFunction before() { + return (r) -> null; + } + + @Bean + @Order(1) + RouterFunction after() { + return (r) -> null; + } + + } + } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/graphql/servlet/GraphQlWebMvcAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/graphql/servlet/GraphQlWebMvcAutoConfigurationTests.java index 268bbee1d105..285b5e2fd2e2 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/graphql/servlet/GraphQlWebMvcAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/graphql/servlet/GraphQlWebMvcAutoConfigurationTests.java @@ -16,6 +16,8 @@ package org.springframework.boot.autoconfigure.graphql.servlet; +import java.util.Map; + import graphql.schema.idl.TypeRuntimeWiring; import org.hamcrest.Matchers; import org.junit.jupiter.api.Test; @@ -30,6 +32,7 @@ import org.springframework.boot.test.context.runner.WebApplicationContextRunner; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.core.annotation.Order; import org.springframework.graphql.execution.RuntimeWiringConfigurer; import org.springframework.graphql.server.WebGraphQlHandler; import org.springframework.graphql.server.WebGraphQlInterceptor; @@ -40,6 +43,7 @@ import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MvcResult; import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.web.servlet.function.RouterFunction; import static org.assertj.core.api.Assertions.assertThat; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.asyncDispatch; @@ -153,6 +157,17 @@ void shouldConfigureWebSocketBeans() { .run((context) -> assertThat(context).hasSingleBean(GraphQlWebSocketHandler.class)); } + @Test + void routerFunctionShouldHaveOrderZero() throws Exception { + this.contextRunner.withUserConfiguration(CustomRouterFunctions.class).run((context) -> { + Map beans = context.getBeansOfType(RouterFunction.class); + Object[] ordered = context.getBeanProvider(RouterFunction.class).orderedStream().toArray(); + assertThat(beans.get("before")).isSameAs(ordered[0]); + assertThat(beans.get("graphQlRouterFunction")).isSameAs(ordered[1]); + assertThat(beans.get("after")).isSameAs(ordered[2]); + }); + } + private void testWith(MockMvcConsumer mockMvcConsumer) { this.contextRunner.run((context) -> { MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(context).defaultRequest( @@ -190,4 +205,21 @@ WebGraphQlInterceptor customWebGraphQlInterceptor() { } + @Configuration + static class CustomRouterFunctions { + + @Bean + @Order(-1) + RouterFunction before() { + return (r) -> null; + } + + @Bean + @Order(1) + RouterFunction after() { + return (r) -> null; + } + + } + } diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/web/spring-graphql.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/web/spring-graphql.adoc index 16df04673188..080c7a3a738d 100644 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/web/spring-graphql.adoc +++ b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/web/spring-graphql.adoc @@ -83,7 +83,11 @@ are detected by Spring Boot and considered as candidates for `DataFetcher` for m [[web.graphql.transports.http-websocket]] ==== HTTP and WebSocket -The GraphQL HTTP endpoint is at HTTP POST "/graphql" by default. The path can be customized with configprop:spring.graphql.path[]. +The GraphQL HTTP endpoint is at HTTP POST "/graphql" by default. +The path can be customized with configprop:spring.graphql.path[]. + +TIP: The HTTP endpoint for both Spring MVC and Spring WebFlux is provided by a `RouterFunction` bean with an `@Order` or `0`. +If you define your own `RouterFunction` beans, you may want to add appropriate `@Order` annotations to ensure that they are sorted correctly. The GraphQL WebSocket endpoint is off by default. To enable it: