diff --git a/spring-graphql/src/main/java/org/springframework/graphql/data/method/annotation/support/AnnotatedControllerConfigurer.java b/spring-graphql/src/main/java/org/springframework/graphql/data/method/annotation/support/AnnotatedControllerConfigurer.java index 53e807de..774578fc 100644 --- a/spring-graphql/src/main/java/org/springframework/graphql/data/method/annotation/support/AnnotatedControllerConfigurer.java +++ b/spring-graphql/src/main/java/org/springframework/graphql/data/method/annotation/support/AnnotatedControllerConfigurer.java @@ -18,6 +18,7 @@ package org.springframework.graphql.data.method.annotation.support; import java.lang.annotation.Annotation; import java.lang.reflect.Method; import java.lang.reflect.Type; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; @@ -121,6 +122,8 @@ public class AnnotatedControllerConfigurer private final FormattingConversionService conversionService = new DefaultFormattingConversionService(); + private final List customArgumentResolvers = new ArrayList<>(8); + @Nullable private HandlerMethodArgumentResolverComposite argumentResolvers; @@ -147,6 +150,18 @@ public class AnnotatedControllerConfigurer registrar.registerFormatters(this.conversionService); } + /** + * Add {@link HandlerMethodArgumentResolver}'s for custom controller method + * arguments. Such custom resolvers are ordered after built-in resolvers + * except for {@link SourceMethodArgumentResolver}, which is always last. + * + * @param resolvers the resolvers to add. + * @since 1.2 + */ + public void setCustomArgumentResolver(List resolvers) { + this.customArgumentResolvers.addAll(resolvers); + } + HandlerMethodArgumentResolverComposite getArgumentResolvers() { Assert.notNull(this.argumentResolvers, "HandlerMethodArgumentResolverComposite is not yet initialized, was afterPropertiesSet called?"); @@ -243,6 +258,8 @@ public class AnnotatedControllerConfigurer resolvers.addResolver(new ContinuationHandlerMethodArgumentResolver()); } + this.customArgumentResolvers.forEach(resolvers::addResolver); + // This works as a fallback, after all other resolvers resolvers.addResolver(new SourceMethodArgumentResolver()); diff --git a/spring-graphql/src/test/java/org/springframework/graphql/data/method/annotation/support/AnnotatedControllerConfigurerTests.java b/spring-graphql/src/test/java/org/springframework/graphql/data/method/annotation/support/AnnotatedControllerConfigurerTests.java new file mode 100644 index 00000000..1678b1ad --- /dev/null +++ b/spring-graphql/src/test/java/org/springframework/graphql/data/method/annotation/support/AnnotatedControllerConfigurerTests.java @@ -0,0 +1,56 @@ +/* + * Copyright 2002-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.graphql.data.method.annotation.support; + +import java.util.Collections; +import java.util.List; + +import org.junit.jupiter.api.Test; + +import org.springframework.context.support.StaticApplicationContext; +import org.springframework.graphql.data.method.HandlerMethodArgumentResolver; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; + +/** + * Unit tests for {@link AnnotatedControllerConfigurer}. + * + * @author Rossen Stoyanchev + * @since 1.2 + */ +public class AnnotatedControllerConfigurerTests { + + + @Test + void customArgumentResolvers() { + HandlerMethodArgumentResolver customResolver1 = mock(HandlerMethodArgumentResolver.class); + HandlerMethodArgumentResolver customResolver2 = mock(HandlerMethodArgumentResolver.class); + + AnnotatedControllerConfigurer configurer = new AnnotatedControllerConfigurer(); + configurer.setCustomArgumentResolver(Collections.singletonList(customResolver1)); + configurer.setCustomArgumentResolver(Collections.singletonList(customResolver2)); + configurer.setApplicationContext(new StaticApplicationContext()); + configurer.afterPropertiesSet(); + + List resolvers = configurer.getArgumentResolvers().getResolvers(); + int size = resolvers.size(); + assertThat(resolvers).element(size -1).isInstanceOf(SourceMethodArgumentResolver.class); + assertThat(resolvers).element(size -2).isSameAs(customResolver2); + assertThat(resolvers).element(size -3).isSameAs(customResolver1); + } + +}