Skip to content

Commit 5adce28

Browse files
committed
support custom HandlerMethodArgumentResolver
1 parent afb0b64 commit 5adce28

File tree

4 files changed

+111
-17
lines changed

4 files changed

+111
-17
lines changed

spring-graphql/src/main/java/org/springframework/graphql/data/method/HandlerMethodArgumentResolverComposite.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,18 @@ public void addResolver(HandlerMethodArgumentResolver resolver) {
4949
this.argumentResolvers.add(resolver);
5050
}
5151

52+
/**
53+
* Add the given {@link HandlerMethodArgumentResolver HandlerMethodArgumentResolvers}.
54+
*/
55+
public HandlerMethodArgumentResolverComposite addResolvers(
56+
@Nullable List<? extends HandlerMethodArgumentResolver> resolvers) {
57+
58+
if (resolvers != null) {
59+
this.argumentResolvers.addAll(resolvers);
60+
}
61+
return this;
62+
}
63+
5264
/**
5365
* Return a read-only list with the contained resolvers, or an empty list.
5466
*/

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

Lines changed: 57 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,13 @@
1818
import java.lang.annotation.Annotation;
1919
import java.lang.reflect.Method;
2020
import java.lang.reflect.Type;
21+
import java.util.ArrayList;
2122
import java.util.Arrays;
2223
import java.util.Collection;
2324
import java.util.Collections;
2425
import java.util.HashMap;
2526
import java.util.LinkedHashSet;
27+
import java.util.List;
2628
import java.util.Map;
2729
import java.util.Set;
2830
import java.util.stream.Collectors;
@@ -108,6 +110,9 @@ public class AnnotatedControllerConfigurer
108110
@Nullable
109111
private ApplicationContext applicationContext;
110112

113+
@Nullable
114+
private List<HandlerMethodArgumentResolver> customArgumentResolvers;
115+
111116
@Nullable
112117
private HandlerMethodArgumentResolverComposite argumentResolvers;
113118

@@ -143,6 +148,22 @@ public void setApplicationContext(ApplicationContext applicationContext) {
143148
this.applicationContext = applicationContext;
144149
}
145150

151+
/**
152+
* Provide resolvers for custom argument types. Custom resolvers are ordered
153+
* after built-in ones.
154+
*/
155+
public void setCustomArgumentResolvers(@Nullable List<HandlerMethodArgumentResolver> argumentResolvers) {
156+
this.customArgumentResolvers = argumentResolvers;
157+
}
158+
159+
/**
160+
* Return the custom argument resolvers, or {@code null}.
161+
*/
162+
@Nullable
163+
public List<HandlerMethodArgumentResolver> getCustomArgumentResolvers() {
164+
return this.customArgumentResolvers;
165+
}
166+
146167
protected final ApplicationContext obtainApplicationContext() {
147168
Assert.state(this.applicationContext != null, "No ApplicationContext");
148169
return this.applicationContext;
@@ -151,37 +172,58 @@ protected final ApplicationContext obtainApplicationContext() {
151172

152173
@Override
153174
public void afterPropertiesSet() {
154-
this.argumentResolvers = new HandlerMethodArgumentResolverComposite();
175+
List<HandlerMethodArgumentResolver> resolvers = getDefaultArgumentResolvers();
176+
this.argumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);
177+
178+
if (beanValidationPresent) {
179+
this.validator = HandlerMethodInputValidatorFactory.create(obtainApplicationContext());
180+
}
181+
182+
if (beanValidationPresent) {
183+
this.validator = HandlerMethodInputValidatorFactory.create(obtainApplicationContext());
184+
}
185+
}
186+
187+
/**
188+
* Return the list of argument resolvers to use including built-in resolvers
189+
* and custom resolvers provided via {@link #setCustomArgumentResolvers}.
190+
*/
191+
private List<HandlerMethodArgumentResolver> getDefaultArgumentResolvers() {
192+
List<HandlerMethodArgumentResolver> resolvers = new ArrayList<>();
193+
155194

156195
// Annotation based
157196
if (springDataPresent) {
158197
// Must be ahead of ArgumentMethodArgumentResolver
159-
this.argumentResolvers.addResolver(new ProjectedPayloadMethodArgumentResolver());
198+
resolvers.add(new ProjectedPayloadMethodArgumentResolver());
160199
}
161-
this.argumentResolvers.addResolver(new ArgumentMapMethodArgumentResolver());
200+
resolvers.add(new ArgumentMapMethodArgumentResolver());
162201
GraphQlArgumentInitializer initializer = new GraphQlArgumentInitializer(this.conversionService);
163-
this.argumentResolvers.addResolver(new ArgumentMethodArgumentResolver(initializer));
164-
this.argumentResolvers.addResolver(new ArgumentsMethodArgumentResolver(initializer));
165-
this.argumentResolvers.addResolver(new ContextValueMethodArgumentResolver());
202+
resolvers.add(new ArgumentMethodArgumentResolver(initializer));
203+
resolvers.add(new ArgumentsMethodArgumentResolver(initializer));
204+
resolvers.add(new ContextValueMethodArgumentResolver());
166205

167206
// Type based
168-
this.argumentResolvers.addResolver(new DataFetchingEnvironmentMethodArgumentResolver());
169-
this.argumentResolvers.addResolver(new DataLoaderMethodArgumentResolver());
207+
resolvers.add(new DataFetchingEnvironmentMethodArgumentResolver());
208+
resolvers.add(new DataLoaderMethodArgumentResolver());
170209
if (springSecurityPresent) {
171-
this.argumentResolvers.addResolver(new PrincipalMethodArgumentResolver());
210+
resolvers.add(new PrincipalMethodArgumentResolver());
172211
BeanResolver beanResolver = new BeanFactoryResolver(obtainApplicationContext());
173-
this.argumentResolvers.addResolver(new AuthenticationPrincipalArgumentResolver(beanResolver));
212+
resolvers.add(new AuthenticationPrincipalArgumentResolver(beanResolver));
174213
}
175214
if (KotlinDetector.isKotlinPresent()) {
176-
this.argumentResolvers.addResolver(new ContinuationHandlerMethodArgumentResolver());
215+
resolvers.add(new ContinuationHandlerMethodArgumentResolver());
177216
}
178217

179-
// This works as a fallback, after other resolvers
180-
this.argumentResolvers.addResolver(new SourceMethodArgumentResolver());
218+
// This works as a fallback, after other resolvers, but before custom resolvers
219+
resolvers.add(new SourceMethodArgumentResolver());
181220

182-
if (beanValidationPresent) {
183-
this.validator = HandlerMethodInputValidatorFactory.create(obtainApplicationContext());
221+
// Custom argument resolvers
222+
if (getCustomArgumentResolvers() != null) {
223+
resolvers.addAll(getCustomArgumentResolvers());
184224
}
225+
226+
return resolvers;
185227
}
186228

187229
@Override

spring-graphql/src/test/java/org/springframework/graphql/data/method/annotation/support/SchemaMappingInvocationTests.java

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,12 @@
1515
*/
1616
package org.springframework.graphql.data.method.annotation.support;
1717

18+
import java.lang.annotation.Documented;
19+
import java.lang.annotation.ElementType;
20+
import java.lang.annotation.Retention;
21+
import java.lang.annotation.RetentionPolicy;
22+
import java.lang.annotation.Target;
23+
import java.util.Collections;
1824
import java.util.List;
1925
import java.util.concurrent.CompletableFuture;
2026
import java.util.concurrent.atomic.AtomicReference;
@@ -23,6 +29,9 @@
2329
import graphql.schema.DataFetchingEnvironment;
2430
import org.dataloader.DataLoader;
2531
import org.junit.jupiter.api.Test;
32+
import org.springframework.core.MethodParameter;
33+
import org.springframework.graphql.data.method.HandlerMethodArgumentResolver;
34+
import org.springframework.util.Assert;
2635
import reactor.core.publisher.Flux;
2736
import reactor.core.publisher.Mono;
2837
import reactor.test.StepVerifier;
@@ -54,6 +63,7 @@
5463
*
5564
* @author Rossen Stoyanchev
5665
* @author Mark Paluch
66+
* @author Genkui Du
5767
*/
5868
public class SchemaMappingInvocationTests {
5969

@@ -215,7 +225,7 @@ private GraphQlService graphQlService() {
215225
context.refresh();
216226

217227
return GraphQlSetup.schemaResource(BookSource.schema)
218-
.runtimeWiringForAnnotatedControllers(context)
228+
.runtimeWiringForAnnotatedControllers(context, Collections.singletonList(new DefaultValueArgumentResolver()))
219229
.dataLoaders(registry)
220230
.toGraphQlService();
221231
}
@@ -231,7 +241,8 @@ public BookController(BatchLoaderRegistry batchLoaderRegistry) {
231241
}
232242

233243
@QueryMapping
234-
public Book bookById(@Argument Long id) {
244+
public Book bookById(@Argument Long id, @DefaultValueAnnotation(value = 1) long defaultValue) {
245+
Assert.isTrue(defaultValue == 1,"custom HandlerMethodArgumentResolver error.");
235246
return BookSource.getBookWithoutAuthor(id);
236247
}
237248

@@ -280,4 +291,27 @@ interface BookProjection {
280291

281292
}
282293

294+
private static class DefaultValueArgumentResolver implements HandlerMethodArgumentResolver {
295+
@Override
296+
public boolean supportsParameter(MethodParameter parameter) {
297+
return parameter.hasParameterAnnotation(DefaultValueAnnotation.class);
298+
}
299+
300+
@Override
301+
public Object resolveArgument(MethodParameter parameter, DataFetchingEnvironment environment) {
302+
DefaultValueAnnotation annotation = parameter.getParameterAnnotation(DefaultValueAnnotation.class);
303+
Assert.state(annotation != null, "Expected @CustomArgumentResolverAnnotation annotation");
304+
return annotation.value();
305+
}
306+
}
307+
308+
@Target(ElementType.PARAMETER)
309+
@Retention(RetentionPolicy.RUNTIME)
310+
@Documented
311+
private @interface DefaultValueAnnotation {
312+
313+
long value() default 0;
314+
315+
}
316+
283317
}

spring-graphql/src/testFixtures/java/org/springframework/graphql/GraphQlSetup.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import org.springframework.context.ApplicationContext;
2929
import org.springframework.core.io.ByteArrayResource;
3030
import org.springframework.core.io.Resource;
31+
import org.springframework.graphql.data.method.HandlerMethodArgumentResolver;
3132
import org.springframework.graphql.data.method.annotation.support.AnnotatedControllerConfigurer;
3233
import org.springframework.graphql.execution.DataFetcherExceptionResolver;
3334
import org.springframework.graphql.execution.DataLoaderRegistrar;
@@ -85,8 +86,13 @@ public GraphQlSetup runtimeWiring(RuntimeWiringConfigurer configurer) {
8586
}
8687

8788
public GraphQlSetup runtimeWiringForAnnotatedControllers(ApplicationContext context) {
89+
return this.runtimeWiringForAnnotatedControllers(context, new ArrayList<>());
90+
}
91+
92+
public GraphQlSetup runtimeWiringForAnnotatedControllers(ApplicationContext context, List<HandlerMethodArgumentResolver> argumentResolverList) {
8893
AnnotatedControllerConfigurer configurer = new AnnotatedControllerConfigurer();
8994
configurer.setApplicationContext(context);
95+
configurer.setCustomArgumentResolvers(argumentResolverList);
9096
configurer.afterPropertiesSet();
9197
return runtimeWiring(configurer);
9298
}

0 commit comments

Comments
 (0)