Refactor @FeignClient to use name= or url= but not both
Replaces the boolean flag (loadbalance) for switching between service resolution (by name) or straight URL bashing.
This commit is contained in:
@@ -425,65 +425,6 @@ On the server side Just create a Spring Boot application and annotate it with `@
|
||||
|
||||
Spring Cloud provides a `spring-cloud-starter-turbine-amqp` that has all the dependencies you need to get a Turbine AMQP server running. You need Java 8 to run the app because it is Netty-based.
|
||||
|
||||
[[spring-cloud-feign]]
|
||||
== Declarative REST Client: Feign
|
||||
|
||||
https://github.com/Netflix/feign[Feign] is a declarative web service client. It makes writing web service clients easier. To use Feign create an interface and annotate it. It has pluggable annotation support including Feign annotations and JAX-RS annotations. Feign also supports pluggable encoders and decoders. Spring Cloud adds support for Spring MVC annotations and for using the same `HttpMessageConverters` used by default in Spring Web. Spring Cloud integrates Ribbon and Eureka to provide a load balanced http client when using Feign.
|
||||
|
||||
Example spring boot app
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
@Configuration
|
||||
@ComponentScan
|
||||
@EnableAutoConfiguration
|
||||
@EnableEurekaClient
|
||||
@FeignClientScan
|
||||
public class Application {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(Application.class, args);
|
||||
}
|
||||
|
||||
}
|
||||
----
|
||||
|
||||
.StoreClient.java
|
||||
[source,java,indent=0]
|
||||
----
|
||||
@FeignClient("stores")
|
||||
public interface StoreClient {
|
||||
@RequestMapping(method = RequestMethod.GET, value = "/stores")
|
||||
List<Store> getStores();
|
||||
|
||||
@RequestMapping(method = RequestMethod.POST, value = "/stores/{storeId}", consumes = "application/json")
|
||||
Store update(@PathParameter("storeId") Long storeId, Store store);
|
||||
}
|
||||
----
|
||||
|
||||
In the `@FeignClient` annotation the String value ("stores" above) is
|
||||
the arbitrary name of the client, used to create a configuration
|
||||
prefix (see <<spring-cloud-ribbon,below for details of Ribbon
|
||||
support>>).
|
||||
|
||||
[[spring-cloud-feign-without-eureka]]
|
||||
=== Example: How to Use Feign Without Eureka
|
||||
|
||||
Eureka is a convenient way to abstract the discovery of remote servers
|
||||
so you don't have to hard code their URLs in clients, but if you
|
||||
prefer not to use it, Ribbon and Feign are still quite
|
||||
amenable. Suppose you have declared a Feign client as above for
|
||||
"stores", and Eureka is not in use (and not even on the
|
||||
classpath). You should find that the Ribbon client defaults to a
|
||||
configured server list, and you can supply the configuration like this
|
||||
|
||||
.application.yml
|
||||
----
|
||||
stores:
|
||||
ribbon:
|
||||
listOfServers: example.com,google.com
|
||||
----
|
||||
|
||||
[[spring-cloud-ribbon]]
|
||||
== Client Side Load Balancer: Ribbon
|
||||
|
||||
@@ -505,8 +446,7 @@ annotation). Spring Cloud creates a new ensemble as an
|
||||
You can configure some bits of a Ribbon client using external
|
||||
properties in `<client>.ribbon.*`, which is no different than using
|
||||
the Netflix APIs natively, except that you can use Spring Boot
|
||||
configuration files (example
|
||||
<<spring-cloud-feign-without-eureka,above>>). The native options can
|
||||
configuration files. The native options can
|
||||
be inspected as static fields in `CommonClientConfigKey` (part of
|
||||
ribbon-core).
|
||||
|
||||
@@ -561,6 +501,24 @@ populates the list of servers from Eureka. It also replaces the `IPing`
|
||||
interface with `NIWSDiscoveryPing` which delegates to Eureka to determine if
|
||||
a server is up.
|
||||
|
||||
[[spring-cloud-ribbon-without-eureka]]
|
||||
=== Example: How to Use Ribbon Without Eureka
|
||||
|
||||
Eureka is a convenient way to abstract the discovery of remote servers
|
||||
so you don't have to hard code their URLs in clients, but if you
|
||||
prefer not to use it, Ribbon and Feign are still quite
|
||||
amenable. Suppose you have declared a `@RibbonClient` for "stores",
|
||||
and Eureka is not in use (and not even on the classpath). The Ribbon
|
||||
client defaults to a configured server list, and you can supply the
|
||||
configuration like this
|
||||
|
||||
.application.yml
|
||||
----
|
||||
stores:
|
||||
ribbon:
|
||||
listOfServers: example.com,google.com
|
||||
----
|
||||
|
||||
=== Using the Ribbon API Directly
|
||||
|
||||
You can also use the `LoadBalancerClient` directly. Example:
|
||||
@@ -603,6 +561,55 @@ physical address. See
|
||||
{github-code}/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/ribbon/RibbonAutoConfiguration.java[RibbonAutoConfiguration]
|
||||
for details of how the `RestTemplate` is set up.
|
||||
|
||||
[[spring-cloud-feign]]
|
||||
== Declarative REST Client: Feign
|
||||
|
||||
https://github.com/Netflix/feign[Feign] is a declarative web service client. It makes writing web service clients easier. To use Feign create an interface and annotate it. It has pluggable annotation support including Feign annotations and JAX-RS annotations. Feign also supports pluggable encoders and decoders. Spring Cloud adds support for Spring MVC annotations and for using the same `HttpMessageConverters` used by default in Spring Web. Spring Cloud integrates Ribbon and Eureka to provide a load balanced http client when using Feign.
|
||||
|
||||
Example spring boot app
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
@Configuration
|
||||
@ComponentScan
|
||||
@EnableAutoConfiguration
|
||||
@EnableEurekaClient
|
||||
@EnableFeignClients
|
||||
public class Application {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(Application.class, args);
|
||||
}
|
||||
|
||||
}
|
||||
----
|
||||
|
||||
.StoreClient.java
|
||||
[source,java,indent=0]
|
||||
----
|
||||
@FeignClient("stores")
|
||||
public interface StoreClient {
|
||||
@RequestMapping(method = RequestMethod.GET, value = "/stores")
|
||||
List<Store> getStores();
|
||||
|
||||
@RequestMapping(method = RequestMethod.POST, value = "/stores/{storeId}", consumes = "application/json")
|
||||
Store update(@PathParameter("storeId") Long storeId, Store store);
|
||||
}
|
||||
----
|
||||
|
||||
In the `@FeignClient` annotation the String value ("stores" above) is
|
||||
an arbitrary client name, which is used to create a Ribbon load
|
||||
balancer (see <<spring-cloud-ribbon,below for details of Ribbon
|
||||
support>>). You can also specify a URL using the `url` attribute
|
||||
(absolute value or just a hostname).
|
||||
|
||||
The Ribbon client above will want to discover the physical addresses
|
||||
for the "stores" service. If your application is a Eureka client then
|
||||
it will resolve the service in the Eureka service registry. If you
|
||||
don't want to use Eureka, you can simply configure a list of servers
|
||||
in your external configuration (see
|
||||
<<spring-cloud-ribbon-without-eureka,above for example>>).
|
||||
|
||||
== External Configuration: Archaius
|
||||
|
||||
https://github.com/Netflix/archaius[Archaius] is the Netflix client side configuration library. It is the library used by all of the Netflix OSS components for configuration. Archaius is an extension of the http://commons.apache.org/proper/commons-configuration[Apache Commons Configuration] project. It allows updates to configuration by either polling a source for changes or for a source to push changes to the client. Archaius uses Dynamic<Type>Property classes as handles to properties.
|
||||
|
||||
@@ -19,14 +19,8 @@ package org.springframework.cloud.netflix.feign;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.cloud.netflix.archaius.ArchaiusAutoConfiguration;
|
||||
import org.springframework.cloud.netflix.feign.ribbon.FeignRibbonClient;
|
||||
import org.springframework.cloud.netflix.ribbon.SpringClientFactory;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
import com.netflix.loadbalancer.ILoadBalancer;
|
||||
|
||||
import feign.Client;
|
||||
import feign.Feign;
|
||||
|
||||
/**
|
||||
@@ -39,13 +33,4 @@ import feign.Feign;
|
||||
@EnableFeignClients
|
||||
public class FeignAutoConfiguration {
|
||||
|
||||
@ConditionalOnClass(ILoadBalancer.class)
|
||||
@Configuration
|
||||
protected static class RibbonClientConfiguration {
|
||||
@Bean
|
||||
public Client feignRibbonClient(SpringClientFactory factory) {
|
||||
return new FeignRibbonClient(factory);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -36,16 +36,20 @@ import java.lang.annotation.Target;
|
||||
public @interface FeignClient {
|
||||
|
||||
/**
|
||||
* The serviceId if loadbalance is true, or an absolute URL otherwise There is no need
|
||||
* to prefix serviceId with http://.
|
||||
* The serviceId with optional protocol prefix. Synonym for {@link #serviceId()
|
||||
* serviceId}. Either serviceId or url must be specified but not both.
|
||||
*/
|
||||
String value();
|
||||
String value() default "";
|
||||
|
||||
/**
|
||||
* Set to true if calls should be load balanced (assuming a load balancer is
|
||||
* available). If no load balancer is available this flag is ignored (and hence the
|
||||
* {@link #value() value} should be an absolute URL).
|
||||
* The serviceId with optional protocol prefix. Synonym for {@link #value() value}.
|
||||
* Either serviceId or url must be specified but not both.
|
||||
*/
|
||||
boolean loadbalance() default true;
|
||||
String serviceId() default "";
|
||||
|
||||
/**
|
||||
* An absolute URL or resolvable hostname (the protocol is optional).
|
||||
*/
|
||||
String url() default "";
|
||||
|
||||
}
|
||||
|
||||
@@ -22,7 +22,10 @@ import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import org.springframework.beans.factory.FactoryBean;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import feign.Client;
|
||||
import feign.Contract;
|
||||
@@ -42,13 +45,13 @@ import feign.slf4j.Slf4jLogger;
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = false)
|
||||
class FeignClientFactoryBean implements FactoryBean<Object> {
|
||||
|
||||
private boolean loadbalance;
|
||||
class FeignClientFactoryBean implements FactoryBean<Object>, InitializingBean {
|
||||
|
||||
private Class<?> type;
|
||||
|
||||
private String schemeName;
|
||||
private String name;
|
||||
|
||||
private String url;
|
||||
|
||||
@Autowired
|
||||
private Decoder decoder;
|
||||
@@ -80,6 +83,14 @@ class FeignClientFactoryBean implements FactoryBean<Object> {
|
||||
@Autowired(required = false)
|
||||
private List<RequestInterceptor> requestInterceptors;
|
||||
|
||||
@Override
|
||||
public void afterPropertiesSet() throws Exception {
|
||||
if (StringUtils.hasText(this.name)) {
|
||||
Assert.state(!StringUtils.hasText(this.url),
|
||||
"Either value or url can be specified, but not both");
|
||||
}
|
||||
}
|
||||
|
||||
protected Feign.Builder feign() {
|
||||
Feign.Builder builder = Feign.builder()
|
||||
// required values
|
||||
@@ -118,13 +129,13 @@ class FeignClientFactoryBean implements FactoryBean<Object> {
|
||||
|
||||
@Override
|
||||
public Object getObject() throws Exception {
|
||||
if (!this.schemeName.startsWith("http")) {
|
||||
this.schemeName = "http://" + this.schemeName;
|
||||
if (StringUtils.hasText(this.name) && !this.name.startsWith("http")) {
|
||||
this.name = "http://" + this.name;
|
||||
}
|
||||
if (this.loadbalance) {
|
||||
return loadBalance(feign(), this.type, this.schemeName);
|
||||
if (StringUtils.hasText(this.name)) {
|
||||
return loadBalance(feign(), this.type, this.name);
|
||||
}
|
||||
return feign().target(this.type, this.schemeName);
|
||||
return feign().target(this.type, this.url);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -16,6 +16,9 @@
|
||||
|
||||
package org.springframework.cloud.netflix.feign;
|
||||
|
||||
import org.springframework.cloud.netflix.feign.support.SpringDecoder;
|
||||
import org.springframework.cloud.netflix.feign.support.SpringEncoder;
|
||||
import org.springframework.cloud.netflix.feign.support.SpringMvcContract;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
|
||||
@@ -99,15 +99,34 @@ public class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar,
|
||||
String className = annotationMetadata.getClassName();
|
||||
BeanDefinitionBuilder definition = BeanDefinitionBuilder
|
||||
.genericBeanDefinition(FeignClientFactoryBean.class);
|
||||
definition.addPropertyValue("loadbalance", attributes.get("loadbalance"));
|
||||
validate(attributes);
|
||||
definition.addPropertyValue("url", getUrl(attributes));
|
||||
definition.addPropertyValue("name", getServiceId(attributes));
|
||||
definition.addPropertyValue("type", className);
|
||||
definition.addPropertyValue("schemeName", attributes.get("value"));
|
||||
|
||||
String beanName = StringUtils.uncapitalize(className.substring(className
|
||||
.lastIndexOf(".") + 1));
|
||||
return new BeanDefinitionHolder(definition.getBeanDefinition(), beanName);
|
||||
}
|
||||
|
||||
private void validate(Map<String, Object> attributes) {
|
||||
if (StringUtils.hasText((String) attributes.get("value"))) {
|
||||
Assert.isTrue(!StringUtils.hasText((String) attributes.get("name")),
|
||||
"Either name or value can be specified, but not both");
|
||||
}
|
||||
}
|
||||
|
||||
private String getServiceId(Map<String, Object> attributes) {
|
||||
if (StringUtils.hasText((String) attributes.get("name"))) {
|
||||
return (String) attributes.get("name");
|
||||
}
|
||||
return (String) attributes.get("value");
|
||||
}
|
||||
|
||||
private String getUrl(Map<String, Object> attributes) {
|
||||
return (String) attributes.get("url");
|
||||
}
|
||||
|
||||
protected ClassPathScanningCandidateComponentProvider getScanner() {
|
||||
return new ClassPathScanningCandidateComponentProvider(false) {
|
||||
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* 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.ribbon;
|
||||
|
||||
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.cloud.netflix.feign.FeignAutoConfiguration;
|
||||
import org.springframework.cloud.netflix.ribbon.SpringClientFactory;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
import com.netflix.loadbalancer.ILoadBalancer;
|
||||
|
||||
import feign.Client;
|
||||
import feign.Feign;
|
||||
|
||||
/**
|
||||
* Autoconfiguration to be activated if Feign is in use and needs to be use Ribbon as a
|
||||
* load balancer.
|
||||
*
|
||||
* @author Dave Syer
|
||||
*/
|
||||
@ConditionalOnClass({ ILoadBalancer.class, Feign.class })
|
||||
@Configuration
|
||||
@AutoConfigureBefore(FeignAutoConfiguration.class)
|
||||
public class FeignRibbonClientAutoConfiguration {
|
||||
@Bean
|
||||
public Client feignRibbonClient(SpringClientFactory factory) {
|
||||
return new FeignRibbonClient(factory);
|
||||
}
|
||||
}
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.netflix.feign;
|
||||
package org.springframework.cloud.netflix.feign.support;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
@@ -14,9 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.netflix.feign;
|
||||
|
||||
import static org.springframework.cloud.netflix.feign.FeignUtils.getHttpHeaders;
|
||||
package org.springframework.cloud.netflix.feign.support;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
@@ -32,6 +30,8 @@ import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.client.ClientHttpResponse;
|
||||
import org.springframework.web.client.HttpMessageConverterExtractor;
|
||||
|
||||
import static org.springframework.cloud.netflix.feign.support.FeignUtils.getHttpHeaders;
|
||||
|
||||
import feign.FeignException;
|
||||
import feign.Response;
|
||||
import feign.codec.DecodeException;
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.netflix.feign;
|
||||
package org.springframework.cloud.netflix.feign.support;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
@@ -24,6 +24,7 @@ import java.util.Collection;
|
||||
import javax.inject.Provider;
|
||||
|
||||
import lombok.extern.apachecommons.CommonsLog;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.autoconfigure.web.HttpMessageConverters;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
@@ -33,12 +34,12 @@ import org.springframework.http.converter.HttpMessageConverter;
|
||||
|
||||
import com.google.common.base.Charsets;
|
||||
|
||||
import static org.springframework.cloud.netflix.feign.support.FeignUtils.getHttpHeaders;
|
||||
|
||||
import feign.RequestTemplate;
|
||||
import feign.codec.EncodeException;
|
||||
import feign.codec.Encoder;
|
||||
|
||||
import static org.springframework.cloud.netflix.feign.FeignUtils.getHttpHeaders;
|
||||
|
||||
/**
|
||||
* @author Spencer Gibb
|
||||
*/
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.netflix.feign;
|
||||
package org.springframework.cloud.netflix.feign.support;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.Method;
|
||||
@@ -1,6 +1,7 @@
|
||||
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
|
||||
org.springframework.cloud.netflix.archaius.ArchaiusAutoConfiguration,\
|
||||
org.springframework.cloud.netflix.config.EurekaClientConfigServerAutoConfiguration,\
|
||||
org.springframework.cloud.netflix.feign.ribbon.FeignRibbonClientAutoConfiguration,\
|
||||
org.springframework.cloud.netflix.feign.FeignAutoConfiguration,\
|
||||
org.springframework.cloud.netflix.hystrix.HystrixAutoConfiguration,\
|
||||
org.springframework.cloud.netflix.eureka.EurekaClientAutoConfiguration,\
|
||||
|
||||
Reference in New Issue
Block a user