Skip to content

Commit 01bc66a

Browse files
committed
Add support for GraphQlExceptionHandler in AOT mode
This commit adds the relevant reflection metadata for supporting `@GraphQlExceptionHandler` annotated methods in controllers and `@ControllerAdvice` beans. Closes gh-677
1 parent b21e91f commit 01bc66a

File tree

3 files changed

+160
-37
lines changed

3 files changed

+160
-37
lines changed

spring-graphql/src/main/java/org/springframework/graphql/data/method/annotation/support/SchemaMappingBeanFactoryInitializationAotProcessor.java

Lines changed: 46 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,9 @@
2020
import java.lang.reflect.Method;
2121
import java.lang.reflect.Parameter;
2222
import java.lang.reflect.Type;
23+
import java.util.ArrayList;
2324
import java.util.Arrays;
25+
import java.util.List;
2426

2527
import org.springframework.aop.SpringProxy;
2628
import org.springframework.aot.generate.GenerationContext;
@@ -45,10 +47,12 @@
4547
import org.springframework.graphql.data.method.HandlerMethodArgumentResolver;
4648
import org.springframework.graphql.data.method.HandlerMethodArgumentResolverComposite;
4749
import org.springframework.graphql.data.method.annotation.BatchMapping;
50+
import org.springframework.graphql.data.method.annotation.GraphQlExceptionHandler;
4851
import org.springframework.graphql.data.method.annotation.SchemaMapping;
4952
import org.springframework.stereotype.Controller;
5053
import org.springframework.util.ClassUtils;
5154
import org.springframework.util.ReflectionUtils;
55+
import org.springframework.web.bind.annotation.ControllerAdvice;
5256

5357
import static org.springframework.core.annotation.MergedAnnotations.SearchStrategy.TYPE_HIERARCHY;
5458

@@ -58,6 +62,8 @@
5862
* <ul>
5963
* <li>invocation reflection on {@code @SchemaMapping} and {@code @BatchMapping}
6064
* annotated controllers methods
65+
* <li>invocation reflection on {@code @GraphQlExceptionHandler} methods
66+
* in {@code @Controller} and {@code @ControllerAdvice} beans
6167
* <li>binding reflection on controller method arguments, needed for binding or
6268
* by the GraphQL Java engine itself
6369
* <li>reflection for SpEL support and JDK proxy creation for
@@ -85,27 +91,42 @@ class SchemaMappingBeanFactoryInitializationAotProcessor implements BeanFactoryI
8591

8692
@Override
8793
public BeanFactoryInitializationAotContribution processAheadOfTime(ConfigurableListableBeanFactory beanFactory) {
88-
Class<?>[] controllerTypes = Arrays.stream(beanFactory.getBeanDefinitionNames())
94+
List<Class<?>> controllers = new ArrayList<>();
95+
List<Class<?>> controllerAdvices = new ArrayList<>();
96+
Arrays.stream(beanFactory.getBeanDefinitionNames())
8997
.map(beanName -> RegisteredBean.of(beanFactory, beanName).getBeanClass())
90-
.filter(this::isController)
91-
.toArray(Class<?>[]::new);
92-
return new SchemaMappingBeanFactoryInitializationAotContribution(controllerTypes);
98+
.forEach(beanClass -> {
99+
if (isController(beanClass)) {
100+
controllers.add(beanClass);
101+
}
102+
else if (isControllerAdvice(beanClass)) {
103+
controllerAdvices.add(beanClass);
104+
}
105+
});
106+
return new SchemaMappingBeanFactoryInitializationAotContribution(controllers, controllerAdvices);
93107
}
94108

95109
private boolean isController(AnnotatedElement element) {
96110
return MergedAnnotations.from(element, TYPE_HIERARCHY).isPresent(Controller.class);
97111
}
98112

113+
private boolean isControllerAdvice(AnnotatedElement element) {
114+
return MergedAnnotations.from(element, TYPE_HIERARCHY).isPresent(ControllerAdvice.class);
115+
}
116+
99117

100118
private static class SchemaMappingBeanFactoryInitializationAotContribution
101119
implements BeanFactoryInitializationAotContribution {
102120

103-
private final Class<?>[] controllers;
121+
private final List<Class<?>> controllers;
122+
123+
private final List<Class<?>> controllerAdvices;
104124

105125
private final HandlerMethodArgumentResolverComposite argumentResolvers;
106126

107-
public SchemaMappingBeanFactoryInitializationAotContribution(Class<?>[] controllers) {
127+
public SchemaMappingBeanFactoryInitializationAotContribution(List<Class<?>> controllers, List<Class<?>> controllerAdvices) {
108128
this.controllers = controllers;
129+
this.controllerAdvices = controllerAdvices;
109130
this.argumentResolvers = createArgumentResolvers();
110131
}
111132

@@ -120,11 +141,20 @@ private HandlerMethodArgumentResolverComposite createArgumentResolvers() {
120141
public void applyTo(GenerationContext context, BeanFactoryInitializationCode initializationCode) {
121142
RuntimeHints runtimeHints = context.getRuntimeHints();
122143
registerSpringDataSpelSupport(runtimeHints);
123-
Arrays.stream(this.controllers).forEach(controller -> {
124-
runtimeHints.reflection().registerType(controller);
144+
this.controllers.forEach(controller -> {
145+
runtimeHints.reflection().registerType(controller, MemberCategory.INTROSPECT_DECLARED_METHODS);
125146
ReflectionUtils.doWithMethods(controller,
126147
method -> processSchemaMappingMethod(runtimeHints, method),
127148
this::isGraphQlHandlerMethod);
149+
ReflectionUtils.doWithMethods(controller,
150+
method -> processExceptionHandlerMethod(runtimeHints, method),
151+
this::isExceptionHandlerMethod);
152+
});
153+
this.controllerAdvices.forEach(controllerAdvice -> {
154+
runtimeHints.reflection().registerType(controllerAdvice, MemberCategory.INTROSPECT_DECLARED_METHODS);
155+
ReflectionUtils.doWithMethods(controllerAdvice,
156+
method -> processExceptionHandlerMethod(runtimeHints, method),
157+
this::isExceptionHandlerMethod);
128158
});
129159
}
130160

@@ -143,6 +173,10 @@ private boolean isGraphQlHandlerMethod(AnnotatedElement element) {
143173
return annotations.isPresent(SchemaMapping.class) || annotations.isPresent(BatchMapping.class);
144174
}
145175

176+
private boolean isExceptionHandlerMethod(AnnotatedElement element) {
177+
return MergedAnnotations.from(element, TYPE_HIERARCHY).isPresent(GraphQlExceptionHandler.class);
178+
}
179+
146180
private void processSchemaMappingMethod(RuntimeHints runtimeHints, Method method) {
147181
runtimeHints.reflection().registerMethod(method, ExecutableMode.INVOKE);
148182
for (Parameter parameter : method.getParameters()) {
@@ -151,6 +185,10 @@ private void processSchemaMappingMethod(RuntimeHints runtimeHints, Method method
151185
processReturnType(runtimeHints, MethodParameter.forExecutable(method, -1));
152186
}
153187

188+
private void processExceptionHandlerMethod(RuntimeHints runtimeHints, Method method) {
189+
runtimeHints.reflection().registerMethod(method, ExecutableMode.INVOKE);
190+
}
191+
154192
private void processMethodParameter(RuntimeHints hints, MethodParameter parameter) {
155193
MethodParameterRuntimeHintsRegistrar.fromMethodParameter(this.argumentResolvers, parameter).apply(hints);
156194
}

spring-graphql/src/main/resources/META-INF/native-image/org.springframework.graphql/spring-graphql/reflect-config.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,11 @@
11
[
2+
{
3+
"name": "org.springframework.graphql.data.method.annotation.support.AnnotatedControllerExceptionResolver$MethodResolver",
4+
"allDeclaredMethods": true,
5+
"condition": {
6+
"typeReachable": "org.springframework.graphql.data.method.annotation.support.AnnotatedControllerExceptionResolver"
7+
}
8+
},
29
{
310
"name":"org.springframework.graphql.server.support.GraphQlWebSocketMessage",
411
"allDeclaredFields":true,

0 commit comments

Comments
 (0)