Skip to content

Commit c644b92

Browse files
committed
Support custom HandlerMethodArgumentResolver
See gh-295
1 parent afb0b64 commit c644b92

File tree

4 files changed

+114
-18
lines changed

4 files changed

+114
-18
lines changed

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

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2021 the original author or authors.
2+
* Copyright 2002-2022 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -32,6 +32,7 @@
3232
* Previously resolved method parameters are cached for faster lookups.
3333
*
3434
* @author Rossen Stoyanchev
35+
* @author Genkui Du
3536
* @since 1.0.0
3637
*/
3738
public class HandlerMethodArgumentResolverComposite implements HandlerMethodArgumentResolver {
@@ -49,6 +50,18 @@ public void addResolver(HandlerMethodArgumentResolver resolver) {
4950
this.argumentResolvers.add(resolver);
5051
}
5152

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

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

Lines changed: 58 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;
@@ -74,6 +76,7 @@
7476
*
7577
* @author Rossen Stoyanchev
7678
* @author Brian Clozel
79+
* @author Genkui Du
7780
* @since 1.0.0
7881
*/
7982
public class AnnotatedControllerConfigurer
@@ -108,6 +111,9 @@ public class AnnotatedControllerConfigurer
108111
@Nullable
109112
private ApplicationContext applicationContext;
110113

114+
@Nullable
115+
private List<HandlerMethodArgumentResolver> customArgumentResolvers;
116+
111117
@Nullable
112118
private HandlerMethodArgumentResolverComposite argumentResolvers;
113119

@@ -143,6 +149,22 @@ public void setApplicationContext(ApplicationContext applicationContext) {
143149
this.applicationContext = applicationContext;
144150
}
145151

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

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

156196
// Annotation based
157197
if (springDataPresent) {
158198
// Must be ahead of ArgumentMethodArgumentResolver
159-
this.argumentResolvers.addResolver(new ProjectedPayloadMethodArgumentResolver());
199+
resolvers.add(new ProjectedPayloadMethodArgumentResolver());
160200
}
161-
this.argumentResolvers.addResolver(new ArgumentMapMethodArgumentResolver());
201+
resolvers.add(new ArgumentMapMethodArgumentResolver());
162202
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());
203+
resolvers.add(new ArgumentMethodArgumentResolver(initializer));
204+
resolvers.add(new ArgumentsMethodArgumentResolver(initializer));
205+
resolvers.add(new ContextValueMethodArgumentResolver());
166206

167207
// Type based
168-
this.argumentResolvers.addResolver(new DataFetchingEnvironmentMethodArgumentResolver());
169-
this.argumentResolvers.addResolver(new DataLoaderMethodArgumentResolver());
208+
resolvers.add(new DataFetchingEnvironmentMethodArgumentResolver());
209+
resolvers.add(new DataLoaderMethodArgumentResolver());
170210
if (springSecurityPresent) {
171-
this.argumentResolvers.addResolver(new PrincipalMethodArgumentResolver());
211+
resolvers.add(new PrincipalMethodArgumentResolver());
172212
BeanResolver beanResolver = new BeanFactoryResolver(obtainApplicationContext());
173-
this.argumentResolvers.addResolver(new AuthenticationPrincipalArgumentResolver(beanResolver));
213+
resolvers.add(new AuthenticationPrincipalArgumentResolver(beanResolver));
174214
}
175215
if (KotlinDetector.isKotlinPresent()) {
176-
this.argumentResolvers.addResolver(new ContinuationHandlerMethodArgumentResolver());
216+
resolvers.add(new ContinuationHandlerMethodArgumentResolver());
177217
}
178218

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

182-
if (beanValidationPresent) {
183-
this.validator = HandlerMethodInputValidatorFactory.create(obtainApplicationContext());
222+
// Custom argument resolvers
223+
if (getCustomArgumentResolvers() != null) {
224+
resolvers.addAll(getCustomArgumentResolvers());
184225
}
226+
227+
return resolvers;
185228
}
186229

187230
@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)