Commit e100207e by Dave Syer

Clarify and document some Feign stuff

parent a7398842
...@@ -226,7 +226,7 @@ exclude it manually, e.g. in Maven ...@@ -226,7 +226,7 @@ exclude it manually, e.g. in Maven
The Eureka server does not have a backend store, but the service The Eureka server does not have a backend store, but the service
instances in the registry all have to send heartbeats to keep their instances in the registry all have to send heartbeats to keep their
resistrations up to date (so this can be done in memory). Clients also registrations up to date (so this can be done in memory). Clients also
have an in-memory cache of eureka registrations (so they don't have to have an in-memory cache of eureka registrations (so they don't have to
go to the registry for every single request to a service). go to the registry for every single request to a service).
...@@ -481,7 +481,7 @@ configured server list, and you can supply the configuration like this ...@@ -481,7 +481,7 @@ configured server list, and you can supply the configuration like this
---- ----
stores: stores:
ribbon: ribbon:
listOfClients: example.com,google.com listOfServers: example.com,google.com
---- ----
[[spring-cloud-ribbon]] [[spring-cloud-ribbon]]
......
...@@ -16,17 +16,33 @@ ...@@ -16,17 +16,33 @@
package org.springframework.cloud.netflix.feign; package org.springframework.cloud.netflix.feign;
import java.util.List;
import lombok.Data; import lombok.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import org.springframework.beans.factory.FactoryBean; import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.annotation.Autowired;
import feign.Client;
import feign.Contract;
import feign.Feign;
import feign.Logger;
import feign.Request;
import feign.RequestInterceptor;
import feign.Retryer;
import feign.codec.Decoder;
import feign.codec.Encoder;
import feign.codec.ErrorDecoder;
import feign.ribbon.LoadBalancingTarget;
import feign.slf4j.Slf4jLogger;
/** /**
* @author Spencer Gibb * @author Spencer Gibb
*/ */
@Data @Data
@EqualsAndHashCode(callSuper = false) @EqualsAndHashCode(callSuper = false)
class FeignClientFactoryBean extends FeignConfiguration implements FactoryBean<Object> { class FeignClientFactoryBean implements FactoryBean<Object> {
private boolean loadbalance; private boolean loadbalance;
...@@ -34,6 +50,76 @@ class FeignClientFactoryBean extends FeignConfiguration implements FactoryBean<O ...@@ -34,6 +50,76 @@ class FeignClientFactoryBean extends FeignConfiguration implements FactoryBean<O
private String schemeName; private String schemeName;
@Autowired
private Decoder decoder;
@Autowired
private Encoder encoder;
@Autowired
private Logger logger;
@Autowired
private Contract contract;
@Autowired(required = false)
private Logger.Level logLevel;
@Autowired(required = false)
private Retryer retryer;
@Autowired(required = false)
private ErrorDecoder errorDecoder;
@Autowired(required = false)
private Request.Options options;
@Autowired(required = false)
private Client ribbonClient;
@Autowired(required = false)
private List<RequestInterceptor> requestInterceptors;
protected Feign.Builder feign() {
Feign.Builder builder = Feign.builder()
// required values
.logger(this.logger).encoder(this.encoder).decoder(this.decoder)
.contract(this.contract);
// optional values
if (this.logLevel != null) {
builder.logLevel(this.logLevel);
}
if (this.retryer != null) {
builder.retryer(this.retryer);
}
if (this.errorDecoder != null) {
builder.errorDecoder(this.errorDecoder);
}
if (this.options != null) {
builder.options(this.options);
}
if (this.requestInterceptors != null) {
builder.requestInterceptors(this.requestInterceptors);
}
return builder;
}
protected <T> T loadBalance(Class<T> type, String schemeName) {
return loadBalance(feign(), type, schemeName);
}
protected <T> T loadBalance(Feign.Builder builder, Class<T> type, String schemeName) {
builder.logger(new Slf4jLogger(type)); // TODO: how to have choice here?
if (this.ribbonClient != null) {
return builder.client(this.ribbonClient).target(type, schemeName);
}
else {
return builder.target(LoadBalancingTarget.create(type, schemeName));
}
}
@Override @Override
public Object getObject() throws Exception { public Object getObject() throws Exception {
if (!this.schemeName.startsWith("http")) { if (!this.schemeName.startsWith("http")) {
......
...@@ -41,8 +41,8 @@ import org.springframework.util.StringUtils; ...@@ -41,8 +41,8 @@ import org.springframework.util.StringUtils;
/** /**
* @author Spencer Gibb * @author Spencer Gibb
*/ */
public class FeignClientScanRegistrar extends FeignConfiguration implements public class FeignClientScanRegistrar implements ImportBeanDefinitionRegistrar,
ImportBeanDefinitionRegistrar, ResourceLoaderAware, BeanClassLoaderAware { ResourceLoaderAware, BeanClassLoaderAware {
// patterned after Spring Integration IntegrationComponentScanRegistrar // patterned after Spring Integration IntegrationComponentScanRegistrar
......
/*
* Copyright 2013-2015 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
*
* http://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.netflix.feign;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.netflix.archaius.ConfigurableEnvironmentConfiguration;
import org.springframework.context.annotation.Configuration;
import feign.*;
import feign.codec.Decoder;
import feign.codec.Encoder;
import feign.codec.ErrorDecoder;
import feign.ribbon.LoadBalancingTarget;
import feign.slf4j.Slf4jLogger;
/**
* @author Spencer Gibb
*/
@Configuration
public class FeignConfiguration {
@Autowired
ConfigurableEnvironmentConfiguration envConfig; // FIXME: howto enforce this?
@Autowired
private Decoder decoder;
@Autowired
private Encoder encoder;
@Autowired
private Logger logger;
@Autowired
private Contract contract;
@Autowired(required = false)
private Logger.Level logLevel;
@Autowired(required = false)
private Retryer retryer;
@Autowired(required = false)
private ErrorDecoder errorDecoder;
@Autowired(required = false)
private Request.Options options;
@Autowired(required = false)
private Client ribbonClient;
@Autowired(required = false)
private List<RequestInterceptor> requestInterceptors;
protected Feign.Builder feign() {
Feign.Builder builder = Feign.builder()
// required values
.logger(this.logger).encoder(this.encoder).decoder(this.decoder)
.contract(this.contract);
// optional values
if (this.logLevel != null) {
builder.logLevel(this.logLevel);
}
if (this.retryer != null) {
builder.retryer(this.retryer);
}
if (this.errorDecoder != null) {
builder.errorDecoder(this.errorDecoder);
}
if (this.options != null) {
builder.options(this.options);
}
if (this.requestInterceptors != null) {
builder.requestInterceptors(requestInterceptors);
}
return builder;
}
protected <T> T loadBalance(Class<T> type, String schemeName) {
return loadBalance(feign(), type, schemeName);
}
protected <T> T loadBalance(Feign.Builder builder, Class<T> type, String schemeName) {
builder.logger(new Slf4jLogger(type)); // TODO: how to have choice here?
if (this.ribbonClient != null) {
return builder.client(this.ribbonClient).target(type, schemeName);
}
else {
return builder.target(LoadBalancingTarget.create(type, schemeName));
}
}
}
...@@ -25,7 +25,15 @@ import java.lang.annotation.Target; ...@@ -25,7 +25,15 @@ import java.lang.annotation.Target;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import; import org.springframework.context.annotation.Import;
import com.netflix.loadbalancer.ILoadBalancer;
import com.netflix.loadbalancer.IRule;
import com.netflix.loadbalancer.ServerListFilter;
/** /**
* Declarative configuration for a ribbon client. Add this annotation to any
* <code>@Configuration</code> and then inject a {@link SpringClientFactory} to access the
* client that is created.
*
* @author Dave Syer * @author Dave Syer
*/ */
@Configuration @Configuration
...@@ -35,10 +43,26 @@ import org.springframework.context.annotation.Import; ...@@ -35,10 +43,26 @@ import org.springframework.context.annotation.Import;
@Documented @Documented
public @interface RibbonClient { public @interface RibbonClient {
/**
* Synonym for name (the name of the client)
*
* @see #name()
*/
String value() default ""; String value() default "";
/**
* The name of the ribbon client, uniquely identifying a set of client resources,
* including a load balancer.
*/
String name() default ""; String name() default "";
/**
* A custom <code>@Configuration</code> for the ribbon client. Can contain override
* <code>@Bean</code> definition for the pieces that make up the client, for instance
* {@link ILoadBalancer}, {@link ServerListFilter}, {@link IRule}.
*
* @see RibbonClientConfiguration for the defaults
*/
Class<?>[] configuration() default {}; Class<?>[] configuration() default {};
} }
...@@ -16,9 +16,6 @@ ...@@ -16,9 +16,6 @@
package org.springframework.cloud.netflix.feign; package org.springframework.cloud.netflix.feign;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import java.lang.reflect.InvocationHandler; import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy; import java.lang.reflect.Proxy;
import java.util.ArrayList; import java.util.ArrayList;
...@@ -55,7 +52,9 @@ import com.netflix.loadbalancer.Server; ...@@ -55,7 +52,9 @@ import com.netflix.loadbalancer.Server;
import feign.RequestInterceptor; import feign.RequestInterceptor;
import feign.RequestTemplate; import feign.RequestTemplate;
import static org.junit.Assert.*; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
/** /**
* @author Spencer Gibb * @author Spencer Gibb
...@@ -65,7 +64,7 @@ import static org.junit.Assert.*; ...@@ -65,7 +64,7 @@ import static org.junit.Assert.*;
@WebAppConfiguration @WebAppConfiguration
@IntegrationTest({ "server.port=0", "spring.application.name=feignclienttest" }) @IntegrationTest({ "server.port=0", "spring.application.name=feignclienttest" })
@DirtiesContext @DirtiesContext
public class FeignClientTests extends FeignConfiguration { public class FeignClientTests {
@Value("${local.server.port}") @Value("${local.server.port}")
private int port = 0; private int port = 0;
...@@ -169,24 +168,26 @@ public class FeignClientTests extends FeignConfiguration { ...@@ -169,24 +168,26 @@ public class FeignClientTests extends FeignConfiguration {
@Test @Test
public void testSimpleType() { public void testSimpleType() {
Hello hello = testClient.getHello(); Hello hello = this.testClient.getHello();
assertNotNull("hello was null", hello); assertNotNull("hello was null", hello);
assertEquals("first hello didn't match", new Hello("hello world 1"), hello); assertEquals("first hello didn't match", new Hello("hello world 1"), hello);
} }
@Test @Test
public void testGenericType() { public void testGenericType() {
List<Hello> hellos = testClient.getHellos(); List<Hello> hellos = this.testClient.getHellos();
assertNotNull("hellos was null", hellos); assertNotNull("hellos was null", hellos);
assertEquals("hellos didn't match", hellos, getHelloList()); assertEquals("hellos didn't match", hellos, getHelloList());
} }
@Test @Test
public void testRequestInterceptors() { public void testRequestInterceptors() {
List<String> headers = testClient.getHelloHeaders(); List<String> headers = this.testClient.getHelloHeaders();
assertNotNull("headers was null", headers); assertNotNull("headers was null", headers);
assertTrue("headers didn't contain myheader1value", headers.contains("myheader1value")); assertTrue("headers didn't contain myheader1value",
assertTrue("headers didn't contain myheader2value", headers.contains("myheader2value")); headers.contains("myheader1value"));
assertTrue("headers didn't contain myheader2value",
headers.contains("myheader2value"));
} }
@Data @Data
...@@ -207,7 +208,7 @@ class LocalRibbonClientConfiguration { ...@@ -207,7 +208,7 @@ class LocalRibbonClientConfiguration {
@Bean @Bean
public ILoadBalancer ribbonLoadBalancer() { public ILoadBalancer ribbonLoadBalancer() {
BaseLoadBalancer balancer = new BaseLoadBalancer(); BaseLoadBalancer balancer = new BaseLoadBalancer();
balancer.setServersList(Arrays.asList(new Server("localhost", port))); balancer.setServersList(Arrays.asList(new Server("localhost", this.port)));
return balancer; return balancer;
} }
......
...@@ -50,7 +50,7 @@ import static org.junit.Assert.assertNotNull; ...@@ -50,7 +50,7 @@ import static org.junit.Assert.assertNotNull;
@IntegrationTest({ "server.port=0", "spring.application.name=springdecodertest", @IntegrationTest({ "server.port=0", "spring.application.name=springdecodertest",
"spring.jmx.enabled=true" }) "spring.jmx.enabled=true" })
@DirtiesContext @DirtiesContext
public class SpringDecoderTests extends FeignConfiguration { public class SpringDecoderTests extends FeignClientFactoryBean {
@Value("${local.server.port}") @Value("${local.server.port}")
private int port = 0; private int port = 0;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment