Browse Source

Consider Jackson 2 in GraphQL RSocket auto-configuration

See gh-47688
pull/47694/head
Andy Wilkinson 2 months ago committed by Phillip Webb
parent
commit
2264b77c6f
  1. 3
      module/spring-boot-graphql/build.gradle
  2. 65
      module/spring-boot-graphql/src/main/java/org/springframework/boot/graphql/autoconfigure/rsocket/GraphQlRSocketAutoConfiguration.java
  3. 22
      module/spring-boot-graphql/src/main/resources/META-INF/additional-spring-configuration-metadata.json
  4. 31
      module/spring-boot-graphql/src/test/java/org/springframework/boot/graphql/autoconfigure/rsocket/GraphQlRSocketAutoConfigurationTests.java

3
module/spring-boot-graphql/build.gradle

@ -28,10 +28,11 @@ dependencies { @@ -28,10 +28,11 @@ dependencies {
api(project(":core:spring-boot"))
api("org.springframework.graphql:spring-graphql")
implementation(project(":module:spring-boot-jackson"))
optional(project(":core:spring-boot-autoconfigure"))
optional(project(":module:spring-boot-http-converter"))
optional(project(":module:spring-boot-jackson"))
optional(project(":module:spring-boot-jackson2"))
optional(project(":module:spring-boot-micrometer-observation"))
optional(project(":module:spring-boot-rsocket"))
optional(project(":module:spring-boot-security"))

65
module/spring-boot-graphql/src/main/java/org/springframework/boot/graphql/autoconfigure/rsocket/GraphQlRSocketAutoConfiguration.java

@ -16,6 +16,7 @@ @@ -16,6 +16,7 @@
package org.springframework.boot.graphql.autoconfigure.rsocket;
import com.fasterxml.jackson.databind.ObjectMapper;
import graphql.GraphQL;
import io.rsocket.core.RSocketServer;
import reactor.netty.http.server.HttpServer;
@ -24,12 +25,17 @@ import tools.jackson.databind.json.JsonMapper; @@ -24,12 +25,17 @@ import tools.jackson.databind.json.JsonMapper;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.AnyNestedCondition;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.graphql.autoconfigure.GraphQlAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.codec.Encoder;
import org.springframework.graphql.ExecutionGraphQlService;
import org.springframework.graphql.data.method.annotation.support.AnnotatedControllerConfigurer;
import org.springframework.graphql.execution.GraphQlSource;
@ -46,7 +52,9 @@ import org.springframework.messaging.rsocket.annotation.support.RSocketMessageHa @@ -46,7 +52,9 @@ import org.springframework.messaging.rsocket.annotation.support.RSocketMessageHa
* @since 4.0.0
*/
@AutoConfiguration(after = GraphQlAutoConfiguration.class,
afterName = "org.springframework.boot.rsocket.autoconfigure.RSocketMessagingAutoConfiguration")
afterName = { "org.springframework.boot.rsocket.autoconfigure.RSocketMessagingAutoConfiguration",
"org.springframework.boot.jackson.autoconfigure.JacksonAutoConfiguration",
"org.springframework.boot.jackson2.autoconfigure.Jackson2AutoConfiguration" })
@ConditionalOnClass({ GraphQL.class, GraphQlSource.class, RSocketServer.class, HttpServer.class })
@ConditionalOnBean({ RSocketMessageHandler.class, AnnotatedControllerConfigurer.class })
@ConditionalOnProperty("spring.graphql.rsocket.mapping")
@ -55,9 +63,9 @@ public final class GraphQlRSocketAutoConfiguration { @@ -55,9 +63,9 @@ public final class GraphQlRSocketAutoConfiguration {
@Bean
@ConditionalOnMissingBean
GraphQlRSocketHandler graphQlRSocketHandler(ExecutionGraphQlService graphQlService,
ObjectProvider<RSocketGraphQlInterceptor> interceptors, JsonMapper jsonMapper) {
ObjectProvider<RSocketGraphQlInterceptor> interceptors, JsonEncoderSupplier jsonEncoderSupplier) {
return new GraphQlRSocketHandler(graphQlService, interceptors.orderedStream().toList(),
new JacksonJsonEncoder(jsonMapper));
jsonEncoderSupplier.jsonEncoder());
}
@Bean
@ -66,4 +74,55 @@ public final class GraphQlRSocketAutoConfiguration { @@ -66,4 +74,55 @@ public final class GraphQlRSocketAutoConfiguration {
return new GraphQlRSocketController(handler);
}
interface JsonEncoderSupplier {
Encoder<?> jsonEncoder();
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnBean(JsonMapper.class)
@ConditionalOnProperty(name = "spring.graphql.rsocket.preferred-json-mapper", havingValue = "jackson",
matchIfMissing = true)
static class JacksonJsonEncoderSupplierConfiguration {
@Bean
JsonEncoderSupplier jacksonJsonEncoderSupplier(JsonMapper jsonMapper) {
return () -> new JacksonJsonEncoder(jsonMapper);
}
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnBean(ObjectMapper.class)
@Conditional(NoJacksonOrJackson2Preferred.class)
@Deprecated(since = "4.0.0", forRemoval = true)
@SuppressWarnings("removal")
static class Jackson2JsonEncoderSupplierConfiguration {
@Bean
JsonEncoderSupplier jackson2JsonEncoderSupplier(ObjectMapper objectMapper) {
return () -> new org.springframework.http.codec.json.Jackson2JsonEncoder(objectMapper);
}
}
static class NoJacksonOrJackson2Preferred extends AnyNestedCondition {
NoJacksonOrJackson2Preferred() {
super(ConfigurationPhase.PARSE_CONFIGURATION);
}
@ConditionalOnMissingClass("tools.jackson.databind.json.JsonMapper")
static class NoJackson {
}
@ConditionalOnProperty(name = "spring.graphql.rsocket.preferred-json-mapper", havingValue = "jackson2")
static class Jackson2Preferred {
}
}
}

22
module/spring-boot-graphql/src/main/resources/META-INF/additional-spring-configuration-metadata.json

@ -37,6 +37,12 @@ @@ -37,6 +37,12 @@
"since": "3.5.0"
}
},
{
"name": "spring.graphql.rsocket.preferred-json-mapper",
"type": "java.lang.String",
"defaultValue": "jackson",
"description": "Preferred JSON mapper to use. By default, auto-detected according to the environment. Supported values are 'jackson' and 'jackson2' (deprecated)."
},
{
"name": "spring.graphql.schema.file-extensions",
"defaultValue": ".graphqls,.gqls"
@ -95,6 +101,22 @@ @@ -95,6 +101,22 @@
"name": "any"
}
]
},
{
"name": "spring.graphql.rsocket.preferred-json-mapper",
"values": [
{
"value": "jackson"
},
{
"value": "jackson2"
}
],
"providers": [
{
"name": "any"
}
]
}
]
}

31
module/spring-boot-graphql/src/test/java/org/springframework/boot/graphql/autoconfigure/rsocket/GraphQlRSocketAutoConfigurationTests.java

@ -22,18 +22,22 @@ import java.util.function.Consumer; @@ -22,18 +22,22 @@ import java.util.function.Consumer;
import graphql.schema.idl.TypeRuntimeWiring;
import org.junit.jupiter.api.Test;
import tools.jackson.databind.json.JsonMapper;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration;
import org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener;
import org.springframework.boot.graphql.autoconfigure.GraphQlAutoConfiguration;
import org.springframework.boot.graphql.autoconfigure.GraphQlTestDataFetchers;
import org.springframework.boot.jackson.autoconfigure.JacksonAutoConfiguration;
import org.springframework.boot.logging.LogLevel;
import org.springframework.boot.reactor.netty.NettyReactiveWebServerFactory;
import org.springframework.boot.reactor.netty.NettyRouteProvider;
import org.springframework.boot.rsocket.autoconfigure.RSocketMessagingAutoConfiguration;
import org.springframework.boot.rsocket.autoconfigure.RSocketServerAutoConfiguration;
import org.springframework.boot.rsocket.autoconfigure.RSocketStrategiesAutoConfiguration;
import org.springframework.boot.rsocket.context.RSocketPortInfoApplicationContextInitializer;
import org.springframework.boot.test.context.FilteredClassLoader;
import org.springframework.boot.test.context.runner.ReactiveWebApplicationContextRunner;
import org.springframework.boot.testsupport.classpath.resources.WithResource;
import org.springframework.boot.web.server.context.ServerPortInfoApplicationContextInitializer;
@ -99,6 +103,33 @@ class GraphQlRSocketAutoConfigurationTests { @@ -99,6 +103,33 @@ class GraphQlRSocketAutoConfigurationTests {
testWithRSocketWebSocket(this::assertThatSimpleQueryWorks);
}
@Test
void usesJacksonByDefault() {
this.contextRunner.run((context) -> assertThat(context).hasBean("jacksonJsonEncoderSupplier"));
}
@Test
@Deprecated(since = "4.0.0", forRemoval = true)
@SuppressWarnings("removal")
void usesJackson2WhenItIsPreferred() {
this.contextRunner.withPropertyValues("spring.graphql.rsocket.preferred-json-mapper=jackson2")
.withConfiguration(AutoConfigurations
.of(org.springframework.boot.jackson2.autoconfigure.Jackson2AutoConfiguration.class))
.run((context) -> assertThat(context).hasBean("jackson2JsonEncoderSupplier"));
}
@Test
@Deprecated(since = "4.0.0", forRemoval = true)
@SuppressWarnings("removal")
void usesJackson2WhenJacksonIsAbsent() {
this.contextRunner
.withClassLoader(new FilteredClassLoader(Thread.currentThread().getContextClassLoader(), JsonMapper.class))
.withConfiguration(AutoConfigurations
.of(org.springframework.boot.jackson2.autoconfigure.Jackson2AutoConfiguration.class))
.withInitializer(ConditionEvaluationReportLoggingListener.forLogLevel(LogLevel.INFO))
.run((context) -> assertThat(context).hasBean("jackson2JsonEncoderSupplier"));
}
private void assertThatSimpleQueryWorks(RSocketGraphQlClient client) {
String document = "{ bookById(id: \"book-1\"){ id name pageCount author } }";
String bookName = client.document(document)

Loading…
Cancel
Save