diff --git a/docs/modules/ROOT/pages/spring-cloud-openfeign.adoc b/docs/modules/ROOT/pages/spring-cloud-openfeign.adoc index b59fbd1c6..dab2df5fa 100644 --- a/docs/modules/ROOT/pages/spring-cloud-openfeign.adoc +++ b/docs/modules/ROOT/pages/spring-cloud-openfeign.adoc @@ -729,10 +729,32 @@ public interface DemoTemplate { @GetMapping(path = "/demo") String demoEndpoint(@SpringQueryMap Params params); + + @GetMapping(path = "/custom") + String customEndpoint(@SpringQueryMap(mapEncoder = CustomQueryMapEncoder.class) Params params); } ---- -If you need more control over the generated query parameter map, you can implement a custom `QueryMapEncoder` bean. +If you need more control over the generated global query parameter map, you can implement a custom `QueryMapEncoder` bean and mark it with `@BaseQueryMapEncoder`. + + +[source,java,indent=0] +---- +@Configuration +public class CustomConfig { + + @Bean + @BaseQueryMapEncoder + public QueryMapEncoder globalQueryMapEncoder() { + return new BeanQueryMapEncoder(); + } + + @Bean + public QueryMapEncoder customQueryMapEncoder() { + return new CustomQueryMapEncoder(); + } +} +---- [[hateoas-support]] === HATEOAS support diff --git a/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignClientFactory.java b/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignClientFactory.java index 2ef8ba662..591efac39 100644 --- a/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignClientFactory.java +++ b/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignClientFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2022 the original author or authors. + * Copyright 2013-2024 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. @@ -16,7 +16,10 @@ package org.springframework.cloud.openfeign; +import java.lang.annotation.Annotation; +import java.util.ArrayList; import java.util.HashMap; +import java.util.List; import java.util.Map; import org.springframework.beans.BeansException; @@ -35,6 +38,7 @@ * @author Matt King * @author Jasbir Singh * @author Olga Maciaszek-Sharma + * @author changjin wei(魏昌进) */ public class FeignClientFactory extends NamedContextFactory { @@ -58,6 +62,24 @@ public T getInstanceWithoutAncestors(String name, Class type) { } } + @Nullable + public T getInstanceWithoutAncestorsForAnnotation(String name, Class type, + Class annotationType) { + GenericApplicationContext context = getContext(name); + String[] beanNames = context.getBeanNamesForAnnotation(annotationType); + List beans = new ArrayList<>(); + for (String beanName : beanNames) { + if (context.isTypeMatch(beanName, type)) { + beans.add((T) context.getBean(beanName)); + } + } + if (beans.size() > 1) { + throw new IllegalStateException("Only one annotated bean for type expected."); + } + return beans.isEmpty() ? null : beans.get(0); + + } + @Nullable public Map getInstancesWithoutAncestors(String name, Class type) { return getContext(name).getBeansOfType(type); diff --git a/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignClientFactoryBean.java b/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignClientFactoryBean.java index e0d65e3a7..edd590cf4 100644 --- a/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignClientFactoryBean.java +++ b/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignClientFactoryBean.java @@ -16,6 +16,7 @@ package org.springframework.cloud.openfeign; +import java.lang.annotation.Annotation; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; @@ -52,8 +53,10 @@ import org.springframework.cloud.openfeign.clientconfig.FeignClientConfigurer; import org.springframework.cloud.openfeign.loadbalancer.FeignBlockingLoadBalancerClient; import org.springframework.cloud.openfeign.loadbalancer.RetryableFeignBlockingLoadBalancerClient; +import org.springframework.cloud.openfeign.support.BaseQueryMapEncoder; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; +import org.springframework.core.ResolvableType; import org.springframework.core.annotation.AnnotationAwareOrderComparator; import org.springframework.util.Assert; import org.springframework.util.StringUtils; @@ -74,6 +77,7 @@ * @author Felix Dittrich * @author Dominique Villard * @author Can Bezmen + * @author changjin wei(魏昌进) */ public class FeignClientFactoryBean implements FactoryBean, InitializingBean, ApplicationContextAware, BeanFactoryAware { @@ -229,7 +233,11 @@ protected void configureUsingConfiguration(FeignClientFactory context, Feign.Bui if (responseInterceptor != null) { builder.responseInterceptor(responseInterceptor); } - QueryMapEncoder queryMapEncoder = getInheritedAwareOptional(context, QueryMapEncoder.class); + QueryMapEncoder queryMapEncoder = getInheritedAwareAnnotated(context, QueryMapEncoder.class, + BaseQueryMapEncoder.class); + if (queryMapEncoder == null) { + queryMapEncoder = getInheritedAwareOptional(context, QueryMapEncoder.class); + } if (queryMapEncoder != null) { builder.queryMapEncoder(queryMapEncoder); } @@ -420,6 +428,16 @@ protected Map getInheritedAwareInstances(FeignClientFactory conte } } + protected T getInheritedAwareAnnotated(FeignClientFactory context, Class type, + Class annotationType) { + if (inheritParentContext) { + return context.getAnnotatedInstance(contextId, ResolvableType.forType(type), annotationType); + } + else { + return context.getInstanceWithoutAncestorsForAnnotation(contextId, type, annotationType); + } + } + protected T loadBalance(Feign.Builder builder, FeignClientFactory context, HardCodedTarget target) { Client client = getOptional(context, Client.class); if (client != null) { diff --git a/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignClientsConfiguration.java b/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignClientsConfiguration.java index 3372a0c55..27082b469 100644 --- a/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignClientsConfiguration.java +++ b/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignClientsConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2022 the original author or authors. + * Copyright 2013-2024 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. @@ -74,6 +74,7 @@ * @author Olga Maciaszek-Sharma * @author Hyeonmin Park * @author Yanming Zhou + * @author changjin wei(魏昌进) */ @Configuration(proxyBeanMethods = false) public class FeignClientsConfiguration { @@ -144,9 +145,9 @@ public QueryMapEncoder feignQueryMapEncoderPageable() { @Bean @ConditionalOnMissingBean - public Contract feignContract(ConversionService feignConversionService) { + public Contract feignContract(ConversionService feignConversionService, List springMapEncoders) { boolean decodeSlash = feignClientProperties == null || feignClientProperties.isDecodeSlash(); - return new SpringMvcContract(parameterProcessors, feignConversionService, decodeSlash); + return new SpringMvcContract(parameterProcessors, feignConversionService, decodeSlash, springMapEncoders); } @Bean diff --git a/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/SpringQueryMap.java b/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/SpringQueryMap.java index 918f0438a..4b5a31b83 100644 --- a/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/SpringQueryMap.java +++ b/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/SpringQueryMap.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2022 the original author or authors. + * Copyright 2013-2024 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. @@ -21,10 +21,13 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import feign.QueryMapEncoder; + /** * Spring MVC equivalent of OpenFeign's {@link feign.QueryMap} parameter annotation. * * @author Aram Peres + * @author changjin wei(魏昌进) * @see feign.QueryMap * @see org.springframework.cloud.openfeign.annotation.QueryMapParameterProcessor */ @@ -32,4 +35,11 @@ @Target({ ElementType.PARAMETER }) public @interface SpringQueryMap { + /** + * Specifies the {@link feign.QueryMapEncoder} implementation to use to transform DTO + * into query map. The {@link feign.QueryMapEncoder} must be a valid spring bean. + * @return the {@link feign.QueryMapEncoder} instance. + */ + Class mapEncoder() default QueryMapEncoder.class; + } diff --git a/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/annotation/QueryMapParameterProcessor.java b/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/annotation/QueryMapParameterProcessor.java index eb7b747f1..4f0a97b8d 100644 --- a/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/annotation/QueryMapParameterProcessor.java +++ b/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/annotation/QueryMapParameterProcessor.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2022 the original author or authors. + * Copyright 2013-2024 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. @@ -18,8 +18,10 @@ import java.lang.annotation.Annotation; import java.lang.reflect.Method; +import java.util.Map; import feign.MethodMetadata; +import feign.QueryMapEncoder; import org.springframework.cloud.openfeign.AnnotatedParameterProcessor; import org.springframework.cloud.openfeign.SpringQueryMap; @@ -29,12 +31,23 @@ * * @author Aram Peres * @author Olga Maciaszek-Sharma + * @author changjin wei(魏昌进) * @see AnnotatedParameterProcessor */ public class QueryMapParameterProcessor implements AnnotatedParameterProcessor { private static final Class ANNOTATION = SpringQueryMap.class; + private final Map, QueryMapEncoder> encoders; + + public QueryMapParameterProcessor() { + this.encoders = Map.of(); + } + + public QueryMapParameterProcessor(Map, QueryMapEncoder> encoders) { + this.encoders = encoders; + } + @Override public Class getAnnotationType() { return ANNOTATION; @@ -46,8 +59,14 @@ public boolean processArgument(AnnotatedParameterContext context, Annotation ann MethodMetadata metadata = context.getMethodMetadata(); if (metadata.queryMapIndex() == null) { metadata.queryMapIndex(paramIndex); + metadata.queryMapEncoder(getQueryMapEncoder(annotation)); } return true; } + protected QueryMapEncoder getQueryMapEncoder(Annotation annotation) { + SpringQueryMap springQueryMap = (SpringQueryMap) annotation; + return encoders.get(springQueryMap.mapEncoder()); + } + } diff --git a/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/support/BaseQueryMapEncoder.java b/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/support/BaseQueryMapEncoder.java new file mode 100644 index 000000000..c1872ba54 --- /dev/null +++ b/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/support/BaseQueryMapEncoder.java @@ -0,0 +1,35 @@ +/* + * Copyright 2016-2024 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.cloud.openfeign.support; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import feign.QueryMapEncoder; + +/** + * Globally specifies the {@link QueryMapEncoder} implementation to use to transform DTO + * into query map. + * @author changjin wei(魏昌进) + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ ElementType.METHOD }) +public @interface BaseQueryMapEncoder { + +} diff --git a/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/support/SpringMvcContract.java b/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/support/SpringMvcContract.java index f4e445342..6897b29cd 100644 --- a/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/support/SpringMvcContract.java +++ b/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/support/SpringMvcContract.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2023 the original author or authors. + * Copyright 2013-2024 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. @@ -35,6 +35,7 @@ import feign.MethodMetadata; import feign.Param; import feign.QueryMap; +import feign.QueryMapEncoder; import feign.Request; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -87,6 +88,7 @@ * @author Darren Foong * @author Ram Anaswara * @author Sam Kruglov + * @author changjin wei(魏昌进) */ public class SpringMvcContract extends Contract.BaseContract implements ResourceLoaderAware { @@ -129,10 +131,16 @@ public SpringMvcContract(List annotatedParameterPro public SpringMvcContract(List annotatedParameterProcessors, ConversionService conversionService, boolean decodeSlash) { + this(annotatedParameterProcessors, conversionService, decodeSlash, List.of()); + } + + public SpringMvcContract(List annotatedParameterProcessors, + ConversionService conversionService, boolean decodeSlash, List springMapEncoders) { Assert.notNull(annotatedParameterProcessors, "Parameter processors can not be null."); Assert.notNull(conversionService, "ConversionService can not be null."); - List processors = getDefaultAnnotatedArgumentsProcessors(); + Map, QueryMapEncoder> encoders = toSpringMapEncodersMap(springMapEncoders); + List processors = getDefaultAnnotatedArgumentsProcessors(encoders); processors.addAll(annotatedParameterProcessors); annotatedArgumentProcessors = toAnnotatedArgumentProcessorMap(processors); @@ -363,7 +371,8 @@ private Map, AnnotatedParameterProcessor> toAnnotate return result; } - private List getDefaultAnnotatedArgumentsProcessors() { + private List getDefaultAnnotatedArgumentsProcessors( + Map, QueryMapEncoder> encoders) { List annotatedArgumentResolvers = new ArrayList<>(); @@ -371,7 +380,7 @@ private List getDefaultAnnotatedArgumentsProcessors annotatedArgumentResolvers.add(new PathVariableParameterProcessor()); annotatedArgumentResolvers.add(new RequestParamParameterProcessor()); annotatedArgumentResolvers.add(new RequestHeaderParameterProcessor()); - annotatedArgumentResolvers.add(new QueryMapParameterProcessor()); + annotatedArgumentResolvers.add(new QueryMapParameterProcessor(encoders)); annotatedArgumentResolvers.add(new RequestPartParameterProcessor()); annotatedArgumentResolvers.add(new CookieValueParameterProcessor()); @@ -415,6 +424,15 @@ private boolean isMultipartFormData(MethodMetadata data) { return false; } + public Map, QueryMapEncoder> toSpringMapEncodersMap( + List springMapEncoders) { + Map, QueryMapEncoder> result = new HashMap<>(); + for (QueryMapEncoder encoder : springMapEncoders) { + result.put(encoder.getClass(), encoder); + } + return result; + } + private static class ConvertingExpanderFactory { private final ConversionService conversionService; diff --git a/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/FeignClientFactoryTest.java b/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/FeignClientFactoryTest.java index d4ed2f051..02e38aa7a 100644 --- a/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/FeignClientFactoryTest.java +++ b/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/FeignClientFactoryTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2022 the original author or authors. + * Copyright 2013-2024 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. @@ -17,12 +17,15 @@ package org.springframework.cloud.openfeign; import java.util.Collection; +import java.util.Map; import feign.Logger; +import feign.QueryMapEncoder; import feign.RequestInterceptor; import org.assertj.core.util.Lists; import org.junit.jupiter.api.Test; +import org.springframework.cloud.openfeign.support.BaseQueryMapEncoder; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -30,6 +33,9 @@ import static org.assertj.core.api.Assertions.assertThat; +/** + * @author changjin wei(魏昌进) + */ class FeignClientFactoryTest { @Test @@ -94,6 +100,20 @@ void getInstancesWithoutAncestors() { assertThat(interceptors.size()).isEqualTo(1); } + @Test + void getInstanceWithoutAncestorsForAnnotation() { + AnnotationConfigApplicationContext parent = new AnnotationConfigApplicationContext(); + parent.refresh(); + + FeignClientFactory feignClientFactory = new FeignClientFactory(); + feignClientFactory.setApplicationContext(parent); + feignClientFactory.setConfigurations(Lists.newArrayList(getSpec("demo", null, DemoConfiguration.class))); + + QueryMapEncoder queryMapEncoder = feignClientFactory.getInstanceWithoutAncestorsForAnnotation("demo", + QueryMapEncoder.class, BaseQueryMapEncoder.class); + assertThat(queryMapEncoder.getClass()).isEqualTo(DemoConfiguration.TestBaseQueryMapEncoder.class); + } + @Configuration(proxyBeanMethods = false) @Import(FeignClientsConfiguration.class) protected static class EmptyConfiguration { @@ -109,12 +129,41 @@ public Logger.Level loggerLevel() { return Logger.Level.FULL; } + @Bean + @BaseQueryMapEncoder + public QueryMapEncoder testBaseQueryMapEncoder() { + return new TestBaseQueryMapEncoder(); + } + + @Bean + public QueryMapEncoder queryMapEncoder() { + return new TestQueryMapEncoder(); + } + @Bean public RequestInterceptor requestInterceptor() { return (requestTemplate) -> { }; } + public static class TestBaseQueryMapEncoder implements QueryMapEncoder { + + @Override + public Map encode(Object object) { + return null; + } + + } + + public static class TestQueryMapEncoder implements QueryMapEncoder { + + @Override + public Map encode(Object object) { + return null; + } + + } + } } diff --git a/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/FeignClientOverrideDefaultsTests.java b/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/FeignClientOverrideDefaultsTests.java index 477392fed..aa1012218 100644 --- a/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/FeignClientOverrideDefaultsTests.java +++ b/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/FeignClientOverrideDefaultsTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2022 the original author or authors. + * Copyright 2013-2024 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. @@ -43,6 +43,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.cloud.openfeign.support.BaseQueryMapEncoder; import org.springframework.cloud.openfeign.support.PageableSpringEncoder; import org.springframework.cloud.openfeign.support.SpringMvcContract; import org.springframework.context.annotation.Bean; @@ -57,6 +58,7 @@ * @author Spencer Gibb * @author Jonatan Ivanov * @author Olga Maciaszek-Sharma + * @author changjin wei(魏昌进) */ @SpringBootTest(classes = FeignClientOverrideDefaultsTests.TestConfiguration.class) @DirtiesContext @@ -133,6 +135,7 @@ void overrideQueryMapEncoder() { assertThatCode(() -> { FieldQueryMapEncoder.class.cast(context.getInstance("foo", QueryMapEncoder.class)); BeanQueryMapEncoder.class.cast(context.getInstance("bar", QueryMapEncoder.class)); + GlobalQueryMapEncoder.class.cast(context.getInstance("queryMapEncoder", QueryMapEncoder.class)); }).doesNotThrowAnyException(); } @@ -208,8 +211,17 @@ interface BazClient { } + @FeignClient(name = "queryMapEncoder", url = "https://queryMapEncoder", + configuration = QueryMapEncoderConfiguration.class) + interface QueryMapEncoderClient { + + @GetMapping("/baz") + String get(); + + } + @Configuration(proxyBeanMethods = false) - @EnableFeignClients(clients = { FooClient.class, BarClient.class, BazClient.class }) + @EnableFeignClients(clients = { FooClient.class, BarClient.class, BazClient.class, QueryMapEncoderClient.class }) @EnableAutoConfiguration protected static class TestConfiguration { @@ -318,6 +330,22 @@ Capability noOpCapability() { } + static class QueryMapEncoderConfiguration { + + @Bean + @BaseQueryMapEncoder + QueryMapEncoder globalQueryMapEncoder() { + return new GlobalQueryMapEncoder(); + } + + @Bean + @BaseQueryMapEncoder + QueryMapEncoder partQueryMapEncoder() { + return new PartQueryMapEncoder(); + } + + } + private static class TestMicrometerObservationCapability extends feign.micrometer.MicrometerObservationCapability { TestMicrometerObservationCapability() { @@ -334,4 +362,12 @@ private static class NoOpCapability implements Capability { } + private static class GlobalQueryMapEncoder extends BeanQueryMapEncoder { + + } + + private static class PartQueryMapEncoder extends BeanQueryMapEncoder { + + } + } diff --git a/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/SpringDecoderTests.java b/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/SpringDecoderTests.java index 70a6eba05..0b7a9200d 100644 --- a/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/SpringDecoderTests.java +++ b/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/SpringDecoderTests.java @@ -33,6 +33,7 @@ * Tests for {@link SpringDecoder}. * * @author Olga Maciaszek-Sharma + * @author changjin wei(魏昌进) */ class SpringDecoderTests { @@ -40,7 +41,7 @@ class SpringDecoderTests { @BeforeEach void setUp() { - ObjectFactory factory = mock(); + ObjectFactory factory = mock(ObjectFactory.class); when(factory.getObject()).thenReturn(new HttpMessageConverters()); decoder = new SpringDecoder(factory); } diff --git a/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/support/SpringMvcContractTests.java b/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/support/SpringMvcContractTests.java index 9f95ba515..26576a023 100644 --- a/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/support/SpringMvcContractTests.java +++ b/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/support/SpringMvcContractTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2023 the original author or authors. + * Copyright 2013-2024 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. @@ -32,6 +32,7 @@ import com.fasterxml.jackson.annotation.JsonAutoDetect; import feign.MethodMetadata; import feign.Param; +import feign.QueryMapEncoder; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -82,6 +83,7 @@ * @author Szymon Linowski * @author Sam Kruglov * @author Bhavya Agrawal + * @author changjin wei(魏昌进) **/ class SpringMvcContractTests { @@ -548,6 +550,22 @@ void testProcessQueryMapObject() throws Exception { assertThat(params.get("aParam").iterator().next()).isEqualTo("{aParam}"); } + @Test + void testProcessQueryMapEncoder() throws Exception { + contract = new SpringMvcContract(Collections.emptyList(), getConversionService(), true, + List.of(new TestQueryMapEncoder())); + Method method = TestTemplate_QueryMap.class.getDeclaredMethod("queryMapEncoder", TestObject.class, + String.class); + MethodMetadata data = contract.parseAndValidateMetadata(method.getDeclaringClass(), method); + + assertThat(data.template().url()).isEqualTo("/queryMapEncoder?aParam=" + "{aParam}"); + assertThat(data.template().method()).isEqualTo("GET"); + assertThat(data.queryMapIndex().intValue()).isEqualTo(0); + assertThat(data.queryMapEncoder().getClass()).isEqualTo(TestQueryMapEncoder.class); + Map> params = data.template().queries(); + assertThat(params.get("aParam").iterator().next()).isEqualTo("{aParam}"); + } + @Test void testProcessQueryMapMoreThanOnce() throws Exception { Method method = TestTemplate_QueryMap.class.getDeclaredMethod("queryMapMoreThanOnce", MultiValueMap.class, @@ -775,6 +793,10 @@ String queryMapMoreThanOnce(@RequestParam MultiValueMap queryMap @GetMapping("/queryMapObject") String queryMapObject(@SpringQueryMap TestObject queryMap, @RequestParam(name = "aParam") String aParam); + @GetMapping("/queryMapEncoder") + String queryMapEncoder(@SpringQueryMap(mapEncoder = TestQueryMapEncoder.class) TestObject queryMap, + @RequestParam(name = "aParam") String aParam); + } public interface TestTemplate_RequestPart { @@ -920,4 +942,13 @@ public String toString() { } + public static class TestQueryMapEncoder implements QueryMapEncoder { + + @Override + public Map encode(Object object) { + return Map.of("foo", "foo"); + } + + } + }