Add reflection hint for GraphQlWebSocketMessage

Prior to this commit, GraphQL applications using the WebSocket transport
would fail with an `java.lang.IllegalArgumentException: No JSON Encoder`
as the JSON codec was reflecting on the `GraphQlWebSocketMessage` to
check that it can handle this type, during startup time.

`GraphQlWebSocketMessage` is the message type being used for sending and
receiving GraphQL requests. This commit registers binding reflection on
this type as soon as a `GraphQlWebSocketHandler` (mvc or webflux) is
registered as a bean in the application.

This type is also used on the client side in the client
`WebSocketGraphQlTransport`. Because clients cannot be reliably detected
here in the application context or via classpath checks, this commit
also adds a JSON metadata file that adds reflection on the type, if and
only if the `WebSocketGraphQlTransport` is reachable by the static
analysis performed by GraalVM.

Fixes gh-560
This commit is contained in:
Brian Clozel
2022-12-02 15:32:21 +01:00
parent a43c92854f
commit eb9b1f308d
5 changed files with 57 additions and 0 deletions

View File

@@ -35,6 +35,7 @@ import org.reactivestreams.Subscription;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import org.springframework.aot.hint.annotation.RegisterReflectionForBinding;
import org.springframework.graphql.server.WebGraphQlHandler;
import org.springframework.graphql.server.WebGraphQlResponse;
import org.springframework.graphql.server.WebSocketGraphQlInterceptor;
@@ -59,6 +60,7 @@ import org.springframework.web.reactive.socket.WebSocketSession;
* @author Rossen Stoyanchev
* @since 1.0.0
*/
@RegisterReflectionForBinding(GraphQlWebSocketMessage.class)
public class GraphQlWebSocketHandler implements WebSocketHandler {
private static final Log logger = LogFactory.getLog(GraphQlWebSocketHandler.class);

View File

@@ -46,6 +46,7 @@ import reactor.core.publisher.Mono;
import reactor.core.scheduler.Scheduler;
import reactor.core.scheduler.Schedulers;
import org.springframework.aot.hint.annotation.RegisterReflectionForBinding;
import org.springframework.graphql.execution.ErrorType;
import org.springframework.graphql.execution.SubscriptionPublisherException;
import org.springframework.graphql.server.WebGraphQlHandler;
@@ -84,6 +85,7 @@ import org.springframework.web.socket.server.support.WebSocketHttpRequestHandler
* @author Rossen Stoyanchev
* @since 1.0.0
*/
@RegisterReflectionForBinding(GraphQlWebSocketMessage.class)
public class GraphQlWebSocketHandler extends TextWebSocketHandler implements SubProtocolCapable {
private static final Log logger = LogFactory.getLog(GraphQlWebSocketHandler.class);

View File

@@ -0,0 +1,11 @@
[
{
"name":"org.springframework.graphql.server.support.GraphQlWebSocketMessage",
"allDeclaredFields":true,
"allDeclaredMethods":true,
"allDeclaredConstructors":true,
"condition": {
"typeReachable": "org.springframework.graphql.client.WebSocketGraphQlTransport"
}
}
]

View File

@@ -32,6 +32,10 @@ import reactor.core.publisher.Mono;
import reactor.core.publisher.Sinks;
import reactor.test.StepVerifier;
import org.springframework.aot.hint.RuntimeHints;
import org.springframework.aot.hint.annotation.ReflectiveRuntimeHintsRegistrar;
import org.springframework.aot.hint.predicate.ReflectionHintsPredicates;
import org.springframework.aot.hint.predicate.RuntimeHintsPredicates;
import org.springframework.core.ResolvableType;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferUtils;
@@ -353,6 +357,23 @@ public class GraphQlWebSocketHandlerTests extends WebSocketHandlerTestSupport {
.verify(TIMEOUT);
}
@Test
void registerBindingReflectionOnWebSocketMessage() {
RuntimeHints runtimeHints = new RuntimeHints();
new ReflectiveRuntimeHintsRegistrar().registerRuntimeHints(runtimeHints, GraphQlWebSocketHandler.class);
ReflectionHintsPredicates reflection = RuntimeHintsPredicates.reflection();
assertThat(reflection.onType(GraphQlWebSocketMessage.class)).accepts(runtimeHints);
assertThat(reflection.onField(GraphQlWebSocketMessage.class, "id")).accepts(runtimeHints);
assertThat(reflection.onMethod(GraphQlWebSocketMessage.class, "getId")).accepts(runtimeHints);
assertThat(reflection.onMethod(GraphQlWebSocketMessage.class, "setId")).accepts(runtimeHints);
assertThat(reflection.onField(GraphQlWebSocketMessage.class, "type")).accepts(runtimeHints);
assertThat(reflection.onMethod(GraphQlWebSocketMessage.class, "getType")).accepts(runtimeHints);
assertThat(reflection.onMethod(GraphQlWebSocketMessage.class, "setType")).accepts(runtimeHints);
assertThat(reflection.onField(GraphQlWebSocketMessage.class, "payload")).accepts(runtimeHints);
assertThat(reflection.onMethod(GraphQlWebSocketMessage.class, "getPayload")).accepts(runtimeHints);
assertThat(reflection.onMethod(GraphQlWebSocketMessage.class, "setPayload")).accepts(runtimeHints);
}
private TestWebSocketSession handle(Flux<WebSocketMessage> input, WebGraphQlInterceptor... interceptors) {
GraphQlWebSocketHandler handler = new GraphQlWebSocketHandler(
initHandler(interceptors),

View File

@@ -36,6 +36,10 @@ import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
import org.springframework.aot.hint.RuntimeHints;
import org.springframework.aot.hint.annotation.ReflectiveRuntimeHintsRegistrar;
import org.springframework.aot.hint.predicate.ReflectionHintsPredicates;
import org.springframework.aot.hint.predicate.RuntimeHintsPredicates;
import org.springframework.graphql.GraphQlSetup;
import org.springframework.graphql.TestThreadLocalAccessor;
import org.springframework.graphql.execution.ErrorType;
@@ -406,6 +410,23 @@ public class GraphQlWebSocketHandlerTests extends WebSocketHandlerTestSupport {
}
}
@Test
void registerBindingReflectionOnWebSocketMessage() {
RuntimeHints runtimeHints = new RuntimeHints();
new ReflectiveRuntimeHintsRegistrar().registerRuntimeHints(runtimeHints, GraphQlWebSocketHandler.class);
ReflectionHintsPredicates reflection = RuntimeHintsPredicates.reflection();
assertThat(reflection.onType(GraphQlWebSocketMessage.class)).accepts(runtimeHints);
assertThat(reflection.onField(GraphQlWebSocketMessage.class, "id")).accepts(runtimeHints);
assertThat(reflection.onMethod(GraphQlWebSocketMessage.class, "getId")).accepts(runtimeHints);
assertThat(reflection.onMethod(GraphQlWebSocketMessage.class, "setId")).accepts(runtimeHints);
assertThat(reflection.onField(GraphQlWebSocketMessage.class, "type")).accepts(runtimeHints);
assertThat(reflection.onMethod(GraphQlWebSocketMessage.class, "getType")).accepts(runtimeHints);
assertThat(reflection.onMethod(GraphQlWebSocketMessage.class, "setType")).accepts(runtimeHints);
assertThat(reflection.onField(GraphQlWebSocketMessage.class, "payload")).accepts(runtimeHints);
assertThat(reflection.onMethod(GraphQlWebSocketMessage.class, "getPayload")).accepts(runtimeHints);
assertThat(reflection.onMethod(GraphQlWebSocketMessage.class, "setPayload")).accepts(runtimeHints);
}
private void handle(GraphQlWebSocketHandler handler, TextMessage... textMessages) throws Exception {
handler.afterConnectionEstablished(this.session);