diff --git a/docs/src/main/asciidoc/spring-cloud-netflix.adoc b/docs/src/main/asciidoc/spring-cloud-netflix.adoc index badf72a38..657eaf574 100644 --- a/docs/src/main/asciidoc/spring-cloud-netflix.adoc +++ b/docs/src/main/asciidoc/spring-cloud-netflix.adoc @@ -1131,6 +1131,9 @@ feign: - com.example.FooRequestInterceptor - com.example.BarRequestInterceptor decode404: false + encoder: com.example.SimpleEncoder + decoder: com.example.SimpleDecoder + contract: com.example.SimpleContract ---- Default configurations can be specified in the `@EnableFeignClients` attribute `defaultConfiguration` in a similar manner as described above. The difference is that this configuration will apply to _all_ feign clients. diff --git a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/feign/FeignClientFactoryBean.java b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/feign/FeignClientFactoryBean.java index d2c5a947e..1b6d030fb 100644 --- a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/feign/FeignClientFactoryBean.java +++ b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/feign/FeignClientFactoryBean.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2017 the original author or authors. + * Copyright 2013-2018 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. @@ -179,6 +179,18 @@ class FeignClientFactoryBean implements FactoryBean, InitializingBean, builder.decode404(); } } + + if (Objects.nonNull(config.getEncoder())) { + builder.encoder(getOrInstantiate(config.getEncoder())); + } + + if (Objects.nonNull(config.getDecoder())) { + builder.decoder(getOrInstantiate(config.getDecoder())); + } + + if (Objects.nonNull(config.getContract())) { + builder.contract(getOrInstantiate(config.getContract())); + } } private T getOrInstantiate(Class tClass) { diff --git a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/feign/FeignClientProperties.java b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/feign/FeignClientProperties.java index 38a20af0c..ac6c1465a 100644 --- a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/feign/FeignClientProperties.java +++ b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/feign/FeignClientProperties.java @@ -15,9 +15,12 @@ */ package org.springframework.cloud.netflix.feign; +import feign.Contract; import feign.Logger; import feign.RequestInterceptor; import feign.Retryer; +import feign.codec.Decoder; +import feign.codec.Encoder; import feign.codec.ErrorDecoder; import org.springframework.boot.context.properties.ConfigurationProperties; @@ -93,6 +96,12 @@ public class FeignClientProperties { private Boolean decode404; + private Class decoder; + + private Class encoder; + + private Class contract; + public Logger.Level getLoggerLevel() { return loggerLevel; } @@ -149,6 +158,30 @@ public class FeignClientProperties { this.decode404 = decode404; } + public Class getDecoder() { + return decoder; + } + + public void setDecoder(Class decoder) { + this.decoder = decoder; + } + + public Class getEncoder() { + return encoder; + } + + public void setEncoder(Class encoder) { + this.encoder = encoder; + } + + public Class getContract() { + return contract; + } + + public void setContract(Class contract) { + this.contract = contract; + } + @Override public boolean equals(Object o) { if (this == o) return true; @@ -160,13 +193,16 @@ public class FeignClientProperties { Objects.equals(retryer, that.retryer) && Objects.equals(errorDecoder, that.errorDecoder) && Objects.equals(requestInterceptors, that.requestInterceptors) && - Objects.equals(decode404, that.decode404); + Objects.equals(decode404, that.decode404) && + Objects.equals(encoder, that.encoder) && + Objects.equals(decoder, that.decoder) && + Objects.equals(contract, that.contract); } @Override public int hashCode() { return Objects.hash(loggerLevel, connectTimeout, readTimeout, retryer, - errorDecoder, requestInterceptors, decode404); + errorDecoder, requestInterceptors, decode404, encoder, decoder, contract); } } diff --git a/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/feign/FeignClientUsingPropertiesTests.java b/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/feign/FeignClientUsingPropertiesTests.java index bc3ff3367..c56c62079 100644 --- a/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/feign/FeignClientUsingPropertiesTests.java +++ b/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/feign/FeignClientUsingPropertiesTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2015 the original author or authors. + * Copyright 2013-2018 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. @@ -20,6 +20,8 @@ import feign.RequestInterceptor; import feign.RequestTemplate; import feign.RetryableException; import feign.Retryer; +import feign.codec.EncodeException; +import feign.codec.Encoder; import feign.codec.ErrorDecoder; import org.junit.Test; import org.junit.runner.RunWith; @@ -30,6 +32,8 @@ import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Configuration; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.context.TestPropertySource; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; @@ -38,8 +42,11 @@ import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RestController; import javax.servlet.http.HttpServletRequest; +import java.lang.reflect.Type; +import java.util.Collections; +import java.util.Map; -import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; /** @@ -64,6 +71,8 @@ public class FeignClientUsingPropertiesTests { private FeignClientFactoryBean barFactoryBean; + private FeignClientFactoryBean formFactoryBean; + public FeignClientUsingPropertiesTests() { fooFactoryBean = new FeignClientFactoryBean(); fooFactoryBean.setName("foo"); @@ -72,6 +81,10 @@ public class FeignClientUsingPropertiesTests { barFactoryBean = new FeignClientFactoryBean(); barFactoryBean.setName("bar"); barFactoryBean.setType(FeignClientFactoryBean.class); + + formFactoryBean = new FeignClientFactoryBean(); + formFactoryBean.setName("form"); + formFactoryBean.setType(FeignClientFactoryBean.class); } public FooClient fooClient() { @@ -84,10 +97,15 @@ public class FeignClientUsingPropertiesTests { return barFactoryBean.feign(context).target(BarClient.class, "http://localhost:" + this.port); } + public FormClient formClient() { + formFactoryBean.setApplicationContext(applicationContext); + return formFactoryBean.feign(context).target(FormClient.class, "http://localhost:" + this.port); + } + @Test public void testFoo() { String response = fooClient().foo(); - assertNotNull("OK", response); + assertEquals("OK", response); } @Test(expected = RetryableException.class) @@ -96,6 +114,13 @@ public class FeignClientUsingPropertiesTests { fail("it should timeout"); } + @Test + public void testForm() { + Map request = Collections.singletonMap("form", "Data"); + String response = formClient().form(request); + assertEquals("Data", response); + } + protected interface FooClient { @RequestMapping(method = RequestMethod.GET, value = "/foo") @@ -108,6 +133,14 @@ public class FeignClientUsingPropertiesTests { String bar(); } + protected interface FormClient { + + @RequestMapping(value = "/form", method = RequestMethod.POST, + consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE) + String form(Map form); + + } + @Configuration @EnableAutoConfiguration @RestController @@ -129,6 +162,12 @@ public class FeignClientUsingPropertiesTests { return "OK"; } + @RequestMapping(value = "/form", method = RequestMethod.POST, + consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE) + public String form(HttpServletRequest request) { + return request.getParameter("form"); + } + } public static class FooRequestInterceptor implements RequestInterceptor { @@ -161,4 +200,19 @@ public class FeignClientUsingPropertiesTests { public static class DefaultErrorDecoder extends ErrorDecoder.Default { } + public static class FormEncoder implements Encoder { + + @Override + public void encode(Object o, Type type, RequestTemplate requestTemplate) throws EncodeException { + Map form = (Map) o; + StringBuilder builder = new StringBuilder(); + form.forEach((key, value) -> { + builder.append(key + "=" + value + "&"); + }); + + requestTemplate.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED_VALUE); + requestTemplate.body(builder.toString()); + } + } + } diff --git a/spring-cloud-netflix-core/src/test/resources/feign-properties.properties b/spring-cloud-netflix-core/src/test/resources/feign-properties.properties index bc045d17c..27338d09d 100644 --- a/spring-cloud-netflix-core/src/test/resources/feign-properties.properties +++ b/spring-cloud-netflix-core/src/test/resources/feign-properties.properties @@ -16,4 +16,6 @@ feign.client.config.foo.requestInterceptors[0]=org.springframework.cloud.netflix feign.client.config.foo.requestInterceptors[1]=org.springframework.cloud.netflix.feign.FeignClientUsingPropertiesTests.BarRequestInterceptor feign.client.config.bar.connectTimeout=1000 -feign.client.config.bar.readTimeout=1000 \ No newline at end of file +feign.client.config.bar.readTimeout=1000 + +feign.client.config.form.encoder=org.springframework.cloud.netflix.feign.FeignClientUsingPropertiesTests.FormEncoder \ No newline at end of file