diff --git a/docs/src/main/asciidoc/spring-cloud-openfeign.adoc b/docs/src/main/asciidoc/spring-cloud-openfeign.adoc index e96b32591..12a895716 100644 --- a/docs/src/main/asciidoc/spring-cloud-openfeign.adoc +++ b/docs/src/main/asciidoc/spring-cloud-openfeign.adoc @@ -206,6 +206,7 @@ spring: requestInterceptors: - com.example.FooRequestInterceptor - com.example.BarRequestInterceptor + responseInterceptor: com.example.BazResponseInterceptor dismiss404: false encoder: com.example.SimpleEncoder decoder: com.example.SimpleDecoder 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 6ef2aec67..b7bacf637 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 @@ -32,6 +32,7 @@ import feign.QueryMapEncoder; import feign.Request; import feign.RequestInterceptor; +import feign.ResponseInterceptor; import feign.Retryer; import feign.Target.HardCodedTarget; import feign.codec.Decoder; @@ -219,6 +220,10 @@ protected void configureUsingConfiguration(FeignClientFactory context, Feign.Bui AnnotationAwareOrderComparator.sort(interceptors); builder.requestInterceptors(interceptors); } + ResponseInterceptor responseInterceptor = getInheritedAwareOptional(context, ResponseInterceptor.class); + if (responseInterceptor != null) { + builder.responseInterceptor(responseInterceptor); + } QueryMapEncoder queryMapEncoder = getInheritedAwareOptional(context, QueryMapEncoder.class); if (queryMapEncoder != null) { builder.queryMapEncoder(queryMapEncoder); @@ -277,6 +282,10 @@ protected void configureUsingProperties(FeignClientProperties.FeignClientConfigu } } + if (config.getResponseInterceptor() != null) { + builder.responseInterceptor(getOrInstantiate(config.getResponseInterceptor())); + } + if (config.getDismiss404() != null) { if (config.getDismiss404()) { builder.dismiss404(); diff --git a/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignClientProperties.java b/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignClientProperties.java index 134e4891f..ec0b0f001 100644 --- a/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignClientProperties.java +++ b/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignClientProperties.java @@ -28,6 +28,7 @@ import feign.Logger; import feign.QueryMapEncoder; import feign.RequestInterceptor; +import feign.ResponseInterceptor; import feign.Retryer; import feign.codec.Decoder; import feign.codec.Encoder; @@ -126,6 +127,8 @@ public static class FeignClientConfiguration { private List> requestInterceptors; + private Class responseInterceptor; + private Map> defaultRequestHeaders; private Map> defaultQueryParameters; @@ -202,6 +205,14 @@ public void setRequestInterceptors(List> requestInterc this.requestInterceptors = requestInterceptors; } + public Class getResponseInterceptor() { + return responseInterceptor; + } + + public void setResponseInterceptor(Class responseInterceptor) { + this.responseInterceptor = responseInterceptor; + } + public Map> getDefaultRequestHeaders() { return defaultRequestHeaders; } @@ -311,6 +322,7 @@ public boolean equals(Object o) { && Objects.equals(readTimeout, that.readTimeout) && Objects.equals(retryer, that.retryer) && Objects.equals(errorDecoder, that.errorDecoder) && Objects.equals(requestInterceptors, that.requestInterceptors) + && Objects.equals(responseInterceptor, that.responseInterceptor) && Objects.equals(dismiss404, that.dismiss404) && Objects.equals(encoder, that.encoder) && Objects.equals(decoder, that.decoder) && Objects.equals(contract, that.contract) && Objects.equals(exceptionPropagationPolicy, that.exceptionPropagationPolicy) @@ -325,8 +337,9 @@ public boolean equals(Object o) { @Override public int hashCode() { return Objects.hash(loggerLevel, connectTimeout, readTimeout, retryer, errorDecoder, requestInterceptors, - dismiss404, encoder, decoder, contract, exceptionPropagationPolicy, defaultQueryParameters, - defaultRequestHeaders, capabilities, queryMapEncoder, micrometer, followRedirects, url); + responseInterceptor, dismiss404, encoder, decoder, contract, exceptionPropagationPolicy, + defaultQueryParameters, defaultRequestHeaders, capabilities, queryMapEncoder, micrometer, + followRedirects, url); } } diff --git a/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/FeignClientConfigurationTests.java b/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/FeignClientConfigurationTests.java index 343b736fb..5e9875a96 100644 --- a/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/FeignClientConfigurationTests.java +++ b/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/FeignClientConfigurationTests.java @@ -27,6 +27,7 @@ import feign.Logger; import feign.QueryMapEncoder; import feign.RequestInterceptor; +import feign.ResponseInterceptor; import feign.Retryer; import feign.codec.Decoder; import feign.codec.Encoder; @@ -60,6 +61,7 @@ void shouldDefaultToValuesWhenFieldsNotSet() { assertThat(config.getRetryer()).isNull(); assertThat(config.getErrorDecoder()).isNull(); assertThat(config.getRequestInterceptors()).isNull(); + assertThat(config.getResponseInterceptor()).isNull(); assertThat(config.getDefaultRequestHeaders()).isNull(); assertThat(config.getDefaultQueryParameters()).isNull(); assertThat(config.getDismiss404()).isNull(); @@ -82,6 +84,8 @@ void shouldReturnValuesWhenSet() { config.setErrorDecoder(ErrorDecoder.class); List> requestInterceptors = Lists.list(RequestInterceptor.class); config.setRequestInterceptors(requestInterceptors); + Class responseInterceptor = ResponseInterceptor.class; + config.setResponseInterceptor(responseInterceptor); Map> defaultRequestHeaders = Maps.newHashMap("default", Collections.emptyList()); config.setDefaultRequestHeaders(defaultRequestHeaders); Map> defaultQueryParameters = Maps.newHashMap("default", Collections.emptyList()); @@ -103,6 +107,7 @@ void shouldReturnValuesWhenSet() { assertThat(config.getRetryer()).isSameAs(Retryer.class); assertThat(config.getErrorDecoder()).isSameAs(ErrorDecoder.class); assertThat(config.getRequestInterceptors()).isSameAs(requestInterceptors); + assertThat(config.getResponseInterceptor()).isSameAs(responseInterceptor); assertThat(config.getDefaultRequestHeaders()).isSameAs(defaultRequestHeaders); assertThat(config.getDefaultQueryParameters()).isSameAs(defaultQueryParameters); assertThat(config.getDismiss404()).isTrue(); diff --git a/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/FeignClientUsingPropertiesTests.java b/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/FeignClientUsingPropertiesTests.java index 7cbb6d982..27de1c65e 100644 --- a/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/FeignClientUsingPropertiesTests.java +++ b/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/FeignClientUsingPropertiesTests.java @@ -34,11 +34,13 @@ import feign.Capability; import feign.Feign; +import feign.InvocationContext; import feign.InvocationHandlerFactory; import feign.QueryMapEncoder; import feign.Request; import feign.RequestInterceptor; import feign.RequestTemplate; +import feign.ResponseInterceptor; import feign.RetryableException; import feign.Retryer; import feign.codec.EncodeException; @@ -99,6 +101,8 @@ public class FeignClientUsingPropertiesTests { private FeignClientFactoryBean barFactoryBean; + private FeignClientFactoryBean bazFactoryBean; + private FeignClientFactoryBean unwrapFactoryBean; private FeignClientFactoryBean formFactoryBean; @@ -116,6 +120,10 @@ public FeignClientUsingPropertiesTests() { barFactoryBean.setContextId("bar"); barFactoryBean.setType(FeignClientFactoryBean.class); + bazFactoryBean = new FeignClientFactoryBean(); + bazFactoryBean.setContextId("baz"); + bazFactoryBean.setType(FeignClientFactoryBean.class); + unwrapFactoryBean = new FeignClientFactoryBean(); unwrapFactoryBean.setContextId("unwrap"); unwrapFactoryBean.setType(FeignClientFactoryBean.class); @@ -143,6 +151,11 @@ public BarClient barClient() { return barFactoryBean.feign(context).target(BarClient.class, "http://localhost:" + port); } + public PingClient pingClient() { + bazFactoryBean.setApplicationContext(applicationContext); + return bazFactoryBean.feign(context).target(PingClient.class, "http://localhost:" + port); + } + public UnwrapClient unwrapClient() { unwrapFactoryBean.setApplicationContext(applicationContext); return unwrapFactoryBean.feign(context).target(UnwrapClient.class, "http://localhost:" + port); @@ -164,6 +177,12 @@ public void testBar() { assertThatThrownBy(() -> barClient().bar()).isInstanceOf(RetryableException.class); } + @Test + public void testBaz() { + String response = pingClient().ping(); + assertThat(response).isEqualTo("baz"); + } + @Test public void testUnwrap() throws Exception { assertThatThrownBy(() -> unwrapClient().unwrap()).isInstanceOf(SocketTimeoutException.class); @@ -298,6 +317,13 @@ protected interface BarClient { } + protected interface PingClient { + + @GetMapping(path = "/ping") + String ping(); + + } + protected interface UnwrapClient { @GetMapping(path = "/bar") // intentionally /bar @@ -355,6 +381,11 @@ public String bar() throws InterruptedException { return "OK"; } + @GetMapping(path = "/ping") + public String ping() { + return "pong"; + } + @PostMapping(path = "/form", consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE) public String form(HttpServletRequest request) { return request.getParameter("form"); @@ -394,6 +425,15 @@ public void apply(RequestTemplate template) { } + public static class BazResponseInterceptor implements ResponseInterceptor { + + @Override + public Object aroundDecode(InvocationContext invocationContext) { + return "baz"; + } + + } + public static class NoRetryer implements Retryer { @Override diff --git a/spring-cloud-openfeign-core/src/test/resources/feign-properties.properties b/spring-cloud-openfeign-core/src/test/resources/feign-properties.properties index 3e7f380e3..b4c6e9ef8 100644 --- a/spring-cloud-openfeign-core/src/test/resources/feign-properties.properties +++ b/spring-cloud-openfeign-core/src/test/resources/feign-properties.properties @@ -12,6 +12,7 @@ spring.cloud.openfeign.client.config.default.capabilities=org.springframework.cl spring.cloud.openfeign.client.config.default.queryMapEncoder=org.springframework.cloud.openfeign.FeignClientUsingPropertiesTests.NoOpQueryMapEncoder spring.cloud.openfeign.client.config.foo.requestInterceptors[0]=org.springframework.cloud.openfeign.FeignClientUsingPropertiesTests.FooRequestInterceptor spring.cloud.openfeign.client.config.foo.requestInterceptors[1]=org.springframework.cloud.openfeign.FeignClientUsingPropertiesTests.BarRequestInterceptor +spring.cloud.openfeign.client.config.baz.responseInterceptor=org.springframework.cloud.openfeign.FeignClientUsingPropertiesTests.BazResponseInterceptor spring.cloud.openfeign.client.config.singleValue.defaultRequestHeaders[singleValueHeaders]=header spring.cloud.openfeign.client.config.singleValue.defaultQueryParameters[singleValueParameters]=parameter spring.cloud.openfeign.client.config.multipleValue.defaultRequestHeaders[multipleValueHeaders]=header1,header2