Commit 810502c1 by Spencer Gibb

Allow overriding default feign beans.

Similar to @RibbonClient fixes gh-288
parent 0df6f0c0
......@@ -767,6 +767,67 @@ 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>>).
[[spring-cloud-feign-overriding-defaults]]
=== Overriding Feign Defaults
A central concept in Spring Cloud's Feign support is that of the named client. Each feign client is part of an ensemble of components that work together to contact a remote server on demand, and the ensemble has a name that you give it as an application developer using the `@FeignClient` annotation. Spring Cloud creates a new ensemble as an
`ApplicationContext` on demand for each named client using `FeignClientsConfiguration`. This contains (amongst other things) an `feign.Decoder`, a `feign.Encoder`, and a `feign.Contract`.
Spring Cloud lets you take full control of the feign client by declaring additional configuration (on top of the `FeignClientsConfiguration`) using `@FeignClient`. Example:
[source,java,indent=0]
----
@FeignClient(name = "stores", configuration = FooConfiguration.class)
public interface StoreClient {
//..
}
----
In this case the client is composed from the components already in `FeignClientsConfiguration` together with any in `FooConfiguration` (where the latter will override the former).
WARNING: The `FooConfiguration` has to be `@Configuration` but take care that it is not in a `@ComponentScan` for the main application context, otherwise it will be shared by all the `@FeignClient`s. If you use `@ComponentScan` (or `@SpringBootApplication`) you need to take steps to avoid it being included (for instance put it in a separate, non-overlapping package, or specify the packages to scan explicitly in the `@ComponentScan`).
NOTE: The `serviceId` attribute is now deprecated in favor of the `name` attribute.
WARNING: Previously, using the `url` attribute, did not require the `name` attribute. Using `name` is now required.
Spring Cloud Netflix provides the following beans by default for feign (`BeanType` beanName: `ClassName`):
* `Decoder` feignDecoder: `ResponseEntityDecoder` (which wraps a `SpringDecoder`)
* `Encoder` feignEncoder: `SpringEncoder`
* `Logger` feignLogger: `Slf4jLogger`
* `Contract` feignContract: `SpringMvcContract`
Spring Cloud Netflix _does not_ provide the following beans by default for feign, but still looks up beans of these types from the application context to create the feign client:
* `Logger.Level`
* `Retryer`
* `ErrorDecoder`
* `Request.Options`
* `Collection<RequestInterceptor>`
Creating a bean of one of those type and placing it in a `@FeignClient` configuration (such as `FooConfiguration` above) allows you to override each one of the beans described. Example:
[source,java,indent=0]
----
@Configuration
public class FooConfiguration {
@Bean
public Contract feignContractg() {
return new feign.Contract.Default();
}
@Bean
public BasicAuthRequestInterceptor basicAuthRequestInterceptor() {
return new BasicAuthRequestInterceptor("user", "password");
}
}
----
This replaces the `SpringMvcContract` with `feign.Contract.Default` and adds a `RequestInterceptor` to the collection of `RequestInterceptor`.
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.
[[spring-cloud-feign-inheritance]]
=== Feign Inheritance Support
......
......@@ -37,7 +37,7 @@ import org.springframework.context.annotation.Import;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import({ FeignClientsConfiguration.class, FeignClientsRegistrar.class })
@Import(FeignClientsRegistrar.class)
public @interface EnableFeignClients {
/**
......@@ -71,4 +71,18 @@ public @interface EnableFeignClients {
*/
Class<?>[] basePackageClasses() default {};
/**
* A custom <code>@Configuration</code> for all feign clients. Can contain override
* <code>@Bean</code> definition for the pieces that make up the client, for instance
* {@link feign.codec.Decoder}, {@link feign.codec.Encoder}, {@link feign.Contract}.
*
* @see FeignClientsConfiguration for the defaults
*/
Class<?>[] defaultConfiguration() default {};
/**
* List of classes annotated with @FeignClient. If not empty, disables classpath scanning.
* @return
*/
Class<?>[] clients() default {};
}
......@@ -16,13 +16,14 @@
package org.springframework.cloud.netflix.feign;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import java.util.ArrayList;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.cloud.client.actuator.HasFeatures;
import org.springframework.cloud.netflix.archaius.ArchaiusAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import feign.Feign;
......@@ -32,12 +33,20 @@ import feign.Feign;
*/
@Configuration
@ConditionalOnClass(Feign.class)
@AutoConfigureAfter(ArchaiusAutoConfiguration.class)
@Import(FeignClientsConfiguration.class)
public class FeignAutoConfiguration {
@Autowired(required = false)
private List<FeignClientSpecification> configurations = new ArrayList<>();
@Bean
public HasFeatures feignFeature() {
return HasFeatures.namedFeature("Feign", Feign.class);
}
@Bean
public FeignClientFactory feignClientFactory() {
FeignClientFactory factory = new FeignClientFactory();
factory.setConfigurations(this.configurations);
return factory;
}
}
......@@ -22,6 +22,8 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.core.annotation.AliasFor;
/**
* Annotation for interfaces declaring that a REST client with that interface should be
* created (e.g. for autowiring into another component). If ribbon is available it will be
......@@ -39,17 +41,30 @@ public @interface FeignClient {
* The serviceId with optional protocol prefix. Synonym for {@link #serviceId()
* serviceId}. Either serviceId or url must be specified but not both.
*/
@AliasFor("name")
String value() default "";
/**
* The serviceId with optional protocol prefix. Synonym for {@link #value() value}.
* Either serviceId or url must be specified but not both.
*/
@Deprecated
String serviceId() default "";
@AliasFor("value")
String name() default "";
/**
* An absolute URL or resolvable hostname (the protocol is optional).
*/
String url() default "";
/**
* A custom <code>@Configuration</code> for the feign client. Can contain override
* <code>@Bean</code> definition for the pieces that make up the client, for instance
* {@link feign.codec.Decoder}, {@link feign.codec.Encoder}, {@link feign.Contract}.
*
* @see FeignClientsConfiguration for the defaults
*/
Class<?>[] configuration() default {};
}
/*
* 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.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactoryUtils;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfiguration;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.core.env.MapPropertySource;
/**
* A factory that creates client, load balancer and client configuration instances. It
* creates a Spring ApplicationContext per client name, and extracts the beans that it
* needs from there.
*
* @author Spencer Gibb
* @author Dave Syer
*/
public class FeignClientFactory implements DisposableBean, ApplicationContextAware {
private Map<String, AnnotationConfigApplicationContext> contexts = new ConcurrentHashMap<>();
private Map<String, FeignClientSpecification> configurations = new ConcurrentHashMap<>();
private ApplicationContext parent;
@Override
public void setApplicationContext(ApplicationContext parent) throws BeansException {
this.parent = parent;
}
public void setConfigurations(List<FeignClientSpecification> configurations) {
for (FeignClientSpecification client : configurations) {
this.configurations.put(client.getName(), client);
}
}
@Override
public void destroy() {
Collection<AnnotationConfigApplicationContext> values = this.contexts.values();
this.contexts.clear();
for (AnnotationConfigApplicationContext context : values) {
context.close();
}
}
private AnnotationConfigApplicationContext getContext(String name) {
if (!this.contexts.containsKey(name)) {
synchronized (this.contexts) {
if (!this.contexts.containsKey(name)) {
this.contexts.put(name, createContext(name));
}
}
}
return this.contexts.get(name);
}
private AnnotationConfigApplicationContext createContext(String name) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
if (this.configurations.containsKey(name)) {
for (Class<?> configuration : this.configurations.get(name)
.getConfiguration()) {
context.register(configuration);
}
}
for (Entry<String, FeignClientSpecification> entry : this.configurations
.entrySet()) {
if (entry.getKey().startsWith("default.")) {
for (Class<?> configuration : entry.getValue().getConfiguration()) {
context.register(configuration);
}
}
}
context.register(PropertyPlaceholderAutoConfiguration.class,
FeignClientsConfiguration.class);
context.getEnvironment().getPropertySources()
.addFirst(new MapPropertySource("feign",
Collections.<String, Object> singletonMap(
"feign.client.name", name)));
if (this.parent != null) {
// Uses Environment from parent as well as beans
context.setParent(this.parent);
}
context.refresh();
return context;
}
public <C> C getInstance(String name, Class<C> type) {
AnnotationConfigApplicationContext context = getContext(name);
if (BeanFactoryUtils.beanNamesForTypeIncludingAncestors(context, type).length > 0) {
return context.getBean(type);
}
return null;
}
}
......@@ -24,7 +24,6 @@ import lombok.EqualsAndHashCode;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.util.Assert;
......@@ -59,10 +58,7 @@ class FeignClientFactoryBean implements FactoryBean<Object>, InitializingBean, A
@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");
}
Assert.hasText(this.name, "Name must be set");
}
@Override
......@@ -70,30 +66,30 @@ class FeignClientFactoryBean implements FactoryBean<Object>, InitializingBean, A
this.context = context;
}
protected Feign.Builder feign() {
protected Feign.Builder feign(FeignClientFactory factory) {
// @formatter:off
Feign.Builder builder = Feign.builder()
// required values
.logger(get(Logger.class))
.encoder(get(Encoder.class))
.decoder(get(Decoder.class))
.contract(get(Contract.class));
.logger(get(factory, Logger.class))
.encoder(get(factory, Encoder.class))
.decoder(get(factory, Decoder.class))
.contract(get(factory, Contract.class));
// @formatter:on
// optional values
Logger.Level level = getOptional(Logger.Level.class);
Logger.Level level = getOptional(factory, Logger.Level.class);
if (level != null) {
builder.logLevel(level);
}
Retryer retryer = getOptional(Retryer.class);
Retryer retryer = getOptional(factory, Retryer.class);
if (retryer != null) {
builder.retryer(retryer);
}
ErrorDecoder errorDecoder = getOptional(ErrorDecoder.class);
ErrorDecoder errorDecoder = getOptional(factory, ErrorDecoder.class);
if (errorDecoder != null) {
builder.errorDecoder(errorDecoder);
}
Request.Options options = getOptional(Request.Options.class);
Request.Options options = getOptional(factory, Request.Options.class);
if (options != null) {
builder.options(options);
}
......@@ -105,24 +101,23 @@ class FeignClientFactoryBean implements FactoryBean<Object>, InitializingBean, A
return builder;
}
protected <T> T get(Class<T> type) {
return this.context.getBean(type);
protected <T> T get(FeignClientFactory factory, Class<T> type) {
T instance = factory.getInstance(this.name, type);
if (instance == null) {
throw new IllegalStateException("No bean found of type " + type + " for " + this.name);
}
return instance;
}
protected <T> T getOptional(Class<T> type) {
try {
return this.context.getBean(type);
} catch (NoSuchBeanDefinitionException e) {
//ignore
}
return null;
protected <T> T getOptional(FeignClientFactory factory, Class<T> type) {
return factory.getInstance(this.name, type);
}
protected <T> T loadBalance(Feign.Builder builder, Class<T> type, String schemeName) {
protected <T> T loadBalance(Feign.Builder builder, FeignClientFactory factory, Class<T> type, String url) {
builder.logger(new Slf4jLogger(type)); // TODO: how to have choice here?
Client client = getOptional(Client.class);
Client client = getOptional(factory, Client.class);
if (client != null) {
return builder.client(client).target(type, schemeName);
return builder.client(client).target(type, url);
}
throw new IllegalStateException("No Feign Client for loadBalancing defined. Did you forget to include spring-cloud-starter-ribbon?");
......@@ -130,16 +125,21 @@ class FeignClientFactoryBean implements FactoryBean<Object>, InitializingBean, A
@Override
public Object getObject() throws Exception {
if (StringUtils.hasText(this.name) && !this.name.startsWith("http")) {
this.name = "http://" + this.name;
}
if (StringUtils.hasText(this.name)) {
return loadBalance(feign(), this.type, this.name);
FeignClientFactory factory = context.getBean(FeignClientFactory.class);
if (!StringUtils.hasText(this.url)) {
String url;
if (!this.name.startsWith("http")) {
url = "http://" + this.name;
} else {
url = this.name;
}
return loadBalance(feign(factory), factory, this.type, url);
}
if (StringUtils.hasText(this.url) && !this.url.startsWith("http")) {
this.url = "http://" + this.url;
}
return feign().target(this.type, this.url);
return feign(factory).target(this.type, this.url);
}
@Override
......
/*
* 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 lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* @author Dave Syer
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class FeignClientSpecification {
private String name;
private Class<?>[] configuration;
}
......@@ -47,21 +47,25 @@ public class FeignClientsConfiguration {
private ObjectFactory<HttpMessageConverters> messageConverters;
@Bean
@ConditionalOnMissingBean
public Decoder feignDecoder() {
return new ResponseEntityDecoder(new SpringDecoder(messageConverters));
}
@Bean
@ConditionalOnMissingBean
public Encoder feignEncoder() {
return new SpringEncoder(messageConverters);
}
@Bean
@ConditionalOnMissingBean
public Logger feignLogger() {
return new Slf4jLogger();
}
@Bean
@ConditionalOnMissingBean
public Contract feignContract() {
return new SpringMvcContract();
}
......
......@@ -16,10 +16,13 @@
package org.springframework.cloud.netflix.feign;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
......@@ -36,7 +39,12 @@ import org.springframework.context.annotation.ClassPathScanningCandidateComponen
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.core.type.ClassMetadata;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.MetadataReaderFactory;
import org.springframework.core.type.filter.AbstractClassTestingTypeFilter;
import org.springframework.core.type.filter.AnnotationTypeFilter;
import org.springframework.core.type.filter.TypeFilter;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;
......@@ -49,6 +57,7 @@ public class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar,
ResourceLoaderAware, BeanClassLoaderAware {
// patterned after Spring Integration IntegrationComponentScanRegistrar
// and RibbonClientsConfigurationRegistgrar
private ResourceLoader resourceLoader;
......@@ -68,15 +77,59 @@ public class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar,
}
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,
public void registerBeanDefinitions(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
registerDefaultConfiguration(metadata, registry);
registerFeignClients(metadata, registry);
}
private void registerDefaultConfiguration(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
Map<String, Object> defaultAttrs = metadata.getAnnotationAttributes(
EnableFeignClients.class.getName(), true);
Set<String> basePackages = getBasePackages(importingClassMetadata);
if (defaultAttrs != null && defaultAttrs.containsKey("defaultConfiguration")) {
String name;
if (metadata.hasEnclosingClass()) {
name = "default." + metadata.getEnclosingClassName();
} else {
name = "default." + metadata.getClassName();
}
registerClientConfiguration(registry, name,
defaultAttrs.get("defaultConfiguration"));
}
}
public void registerFeignClients(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
ClassPathScanningCandidateComponentProvider scanner = getScanner();
scanner.addIncludeFilter(new AnnotationTypeFilter(FeignClient.class));
scanner.setResourceLoader(this.resourceLoader);
Set<String> basePackages;
Map<String, Object> attrs = metadata.getAnnotationAttributes(
EnableFeignClients.class.getName());
AnnotationTypeFilter annotationTypeFilter = new AnnotationTypeFilter(FeignClient.class);
if (attrs != null && attrs.containsKey("clients")) {
final Class<?>[] clients = (Class<?>[]) attrs.get("clients");
final Set<String> clientClasses = new HashSet<>();
basePackages = new HashSet<>();
for (Class<?> clazz : clients) {
basePackages.add(ClassUtils.getPackageName(clazz));
clientClasses.add(clazz.getCanonicalName());
}
AbstractClassTestingTypeFilter filter = new AbstractClassTestingTypeFilter() {
@Override
protected boolean match(ClassMetadata metadata) {
String cleaned = metadata.getClassName().replaceAll("\\$", ".");
return clientClasses.contains(cleaned);
}
};
scanner.addIncludeFilter(new AllTypeFilter(Arrays.asList(filter, annotationTypeFilter)));
} else {
scanner.addIncludeFilter(annotationTypeFilter);
basePackages = getBasePackages(metadata);
}
for (String basePackage : basePackages) {
Set<BeanDefinition> candidateComponents = scanner
.findCandidateComponents(basePackage);
......@@ -88,18 +141,21 @@ public class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar,
Assert.isTrue(annotationMetadata.isInterface(),
"@FeignClient can only be specified on an interface");
BeanDefinitionHolder holder = createBeanDefinition(annotationMetadata);
BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
Map<String, Object> attributes = annotationMetadata
.getAnnotationAttributes(FeignClient.class.getCanonicalName());
String name = getClientName(attributes);
registerClientConfiguration(registry, name, attributes.get("configuration"));
registerFeignClient(registry, annotationMetadata, attributes);
}
}
}
}
private BeanDefinitionHolder createBeanDefinition(
AnnotationMetadata annotationMetadata) {
Map<String, Object> attributes = annotationMetadata
.getAnnotationAttributes(FeignClient.class.getCanonicalName());
private void registerFeignClient( BeanDefinitionRegistry registry,
AnnotationMetadata annotationMetadata,
Map<String, Object> attributes) {
String className = annotationMetadata.getClassName();
BeanDefinitionBuilder definition = BeanDefinitionBuilder
.genericBeanDefinition(FeignClientFactoryBean.class);
......@@ -111,7 +167,8 @@ public class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar,
String beanName = StringUtils.uncapitalize(className.substring(className
.lastIndexOf(".") + 1));
return new BeanDefinitionHolder(definition.getBeanDefinition(), beanName);
BeanDefinitionHolder holder = new BeanDefinitionHolder(definition.getBeanDefinition(), beanName);
BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
}
private void validate(Map<String, Object> attributes) {
......@@ -124,6 +181,9 @@ public class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar,
private String getServiceId(Map<String, Object> attributes) {
String name = (String) attributes.get("serviceId");
if (!StringUtils.hasText(name)) {
name = (String) attributes.get("name");
}
if (!StringUtils.hasText(name)) {
name = (String) attributes.get("value");
}
if (!StringUtils.hasText(name)) {
......@@ -153,10 +213,10 @@ public class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar,
if (beanDefinition.getMetadata().isInterface()
&& beanDefinition.getMetadata().getInterfaceNames().length == 1
&& Annotation.class.getName().equals(
beanDefinition.getMetadata().getInterfaceNames()[0])) {
beanDefinition.getMetadata().getInterfaceNames()[0])) {
try {
Class<?> target = ClassUtils.forName(beanDefinition
.getMetadata().getClassName(),
.getMetadata().getClassName(),
FeignClientsRegistrar.this.classLoader);
return !target.isAnnotation();
}
......@@ -200,4 +260,68 @@ public class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar,
return basePackages;
}
private String getClientName(Map<String, Object> client) {
if (client == null) {
return null;
}
String value = (String) client.get("value");
if (!StringUtils.hasText(value)) {
value = (String) client.get("name");
}
if (!StringUtils.hasText(value)) {
value = (String) client.get("serviceId");
}
if (StringUtils.hasText(value)) {
return value;
}
throw new IllegalStateException(
"Either 'name' or 'value' must be provided in @" + FeignClient.class.getSimpleName());
}
private void registerClientConfiguration(BeanDefinitionRegistry registry,
Object name, Object configuration) {
BeanDefinitionBuilder builder = BeanDefinitionBuilder
.genericBeanDefinition(FeignClientSpecification.class);
builder.addConstructorArgValue(name);
builder.addConstructorArgValue(configuration);
registry.registerBeanDefinition(name + "." + FeignClientSpecification.class.getSimpleName(),
builder.getBeanDefinition());
}
/**
* Helper class to create a {@link TypeFilter} that matches if all the delegates match.
*
* @author Oliver Gierke
*/
private static class AllTypeFilter implements TypeFilter {
private final List<TypeFilter> delegates;
/**
* Creates a new {@link AllTypeFilter} to match if all the given delegates match.
*
* @param delegates must not be {@literal null}.
*/
public AllTypeFilter(List<TypeFilter> delegates) {
Assert.notNull(delegates);
this.delegates = delegates;
}
/*
* (non-Javadoc)
* @see org.springframework.core.type.filter.TypeFilter#match(org.springframework.core.type.classreading.MetadataReader, org.springframework.core.type.classreading.MetadataReaderFactory)
*/
public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
for (TypeFilter filter : delegates) {
if (!filter.match(metadataReader, metadataReaderFactory)) {
return false;
}
}
return true;
}
}
}
/*
* 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 feign.Contract;
import feign.Logger;
import feign.codec.Decoder;
import feign.codec.Encoder;
import feign.slf4j.Slf4jLogger;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfiguration;
import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.cloud.netflix.archaius.ArchaiusAutoConfiguration;
import org.springframework.cloud.netflix.feign.support.ResponseEntityDecoder;
import org.springframework.cloud.netflix.feign.support.SpringEncoder;
import org.springframework.cloud.netflix.feign.support.SpringMvcContract;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
/**
* @author Spencer Gibb
*/
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = EnableFeignClientsTests.PlainConfiguration.class)
@DirtiesContext
public class EnableFeignClientsTests {
@Autowired
private FeignClientFactory factory;
@Autowired
private ApplicationContext context;
@Test
public void decoderDefaultCorrect() {
ResponseEntityDecoder.class.cast(this.factory.getInstance("foo", Decoder.class));
}
@Test
public void encoderDefaultCorrect() {
SpringEncoder.class.cast(this.factory.getInstance("foo", Encoder.class));
}
@Test
public void loggerDefaultCorrect() {
Slf4jLogger.class.cast(this.factory.getInstance("foo", Logger.class));
}
@Test
public void contractDefaultCorrect() {
SpringMvcContract.class.cast(this.factory.getInstance("foo", Contract.class));
}
@Configuration
@Import({ PropertyPlaceholderAutoConfiguration.class,
ArchaiusAutoConfiguration.class, FeignAutoConfiguration.class })
protected static class PlainConfiguration {
}
}
/*
* 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 static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
import org.junit.Test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import java.util.Arrays;
/**
* @author Spencer Gibb
*/
public class FeignClientFactoryTests {
@Test
public void testChildContexts() {
AnnotationConfigApplicationContext parent = new AnnotationConfigApplicationContext();
parent.refresh();
FeignClientFactory factory = new FeignClientFactory();
factory.setApplicationContext(parent);
factory.setConfigurations(Arrays.asList(getSpec("foo", FooConfig.class),
getSpec("bar", BarConfig.class)));
Foo foo = factory.getInstance("foo", Foo.class);
assertThat("foo was null", foo, is(notNullValue()));
Bar bar = factory.getInstance("bar", Bar.class);
assertThat("bar was null", bar, is(notNullValue()));
Bar foobar = factory.getInstance("foo", Bar.class);
assertThat("bar was not null", foobar, is(nullValue()));
}
private FeignClientSpecification getSpec(String name, Class<?> configClass) {
return new FeignClientSpecification(name, new Class[]{configClass});
}
static class FooConfig {
@Bean
Foo foo() {
return new Foo();
}
}
static class Foo{}
static class BarConfig {
@Bean
Bar bar() {
return new Bar();
}
}
static class Bar{}
}
/*
* 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 static org.junit.Assert.*;
import feign.Contract;
import feign.Logger;
import feign.Request;
import feign.RequestInterceptor;
import feign.Retryer;
import feign.auth.BasicAuthRequestInterceptor;
import feign.codec.Decoder;
import feign.codec.Encoder;
import feign.codec.ErrorDecoder;
import feign.slf4j.Slf4jLogger;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfiguration;
import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.cloud.netflix.archaius.ArchaiusAutoConfiguration;
import org.springframework.cloud.netflix.feign.support.ResponseEntityDecoder;
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;
import org.springframework.context.annotation.Import;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.web.bind.annotation.RequestMapping;
/**
* @author Spencer Gibb
*/
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = FeignClientOverrideDefaultsTests.TestConfiguration.class)
@DirtiesContext
public class FeignClientOverrideDefaultsTests {
@Autowired
private FeignClientFactory factory;
@Test
public void overrideDecoder() {
Decoder.Default.class.cast(factory.getInstance("foo", Decoder.class));
ResponseEntityDecoder.class.cast(factory.getInstance("bar", Decoder.class));
}
@Test
public void overrideEncoder() {
Encoder.Default.class.cast(factory.getInstance("foo", Encoder.class));
SpringEncoder.class.cast(factory.getInstance("bar", Encoder.class));
}
@Test
public void overrideLogger() {
Logger.JavaLogger.class.cast(factory.getInstance("foo", Logger.class));
Slf4jLogger.class.cast(factory.getInstance("bar", Logger.class));
}
@Test
public void overrideContract() {
Contract.Default.class.cast(factory.getInstance("foo", Contract.class));
SpringMvcContract.class.cast(factory.getInstance("bar", Contract.class));
}
@Test
public void overrideLoggerLevel() {
assertNull(factory.getInstance("foo", Logger.Level.class));
assertEquals(Logger.Level.HEADERS, factory.getInstance("bar", Logger.Level.class));
}
@Test
public void overrideRetryer() {
assertNull(factory.getInstance("foo", Retryer.class));
Retryer.Default.class.cast(factory.getInstance("bar", Retryer.class));
}
@Test
public void overrideErrorDecoder() {
assertNull(factory.getInstance("foo", ErrorDecoder.class));
ErrorDecoder.Default.class.cast(factory.getInstance("bar", ErrorDecoder.class));
}
@Test
public void overrideRequestOptions() {
assertNull(factory.getInstance("foo", Request.Options.class));
Request.Options options = factory.getInstance("bar", Request.Options.class);
assertEquals(1, options.connectTimeoutMillis());
assertEquals(1, options.readTimeoutMillis());
}
@Test
public void addRequestInterceptor() {
assertNull(factory.getInstance("foo", RequestInterceptor.class));
BasicAuthRequestInterceptor.class.cast(factory.getInstance("bar", RequestInterceptor.class));
}
@Configuration
@EnableFeignClients(clients = {FooClient.class, BarClient.class})
@Import({ PropertyPlaceholderAutoConfiguration.class,
ArchaiusAutoConfiguration.class, FeignAutoConfiguration.class})
protected static class TestConfiguration {
}
@FeignClient(value = "foo", configuration = FooConfiguration.class)
interface FooClient{
@RequestMapping("/")
String get();
}
@Configuration
public static class FooConfiguration {
@Bean
public Decoder feignDecoder() {
return new Decoder.Default();
}
@Bean
public Encoder feignEncoder() {
return new Encoder.Default();
}
@Bean
public Logger feignLogger() {
return new Logger.JavaLogger();
}
@Bean
public Contract feignContract() {
return new Contract.Default();
}
}
@FeignClient(value = "bar", configuration = BarConfiguration.class)
interface BarClient{
@RequestMapping("/")
String get();
}
@Configuration
public static class BarConfiguration {
@Bean
Logger.Level feignLevel() {
return Logger.Level.HEADERS;
}
@Bean
Retryer feignRetryer() {
return new Retryer.Default();
}
@Bean
ErrorDecoder feignErrorDecoder() {
return new ErrorDecoder.Default();
}
@Bean
Request.Options feignRequestOptions() {
return new Request.Options(1, 1);
}
@Bean
RequestInterceptor feignRequestInterceptor() {
return new BasicAuthRequestInterceptor("user", "pass");
}
}
}
......@@ -25,6 +25,7 @@ import lombok.NoArgsConstructor;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.builder.SpringApplicationBuilder;
......@@ -54,11 +55,18 @@ import static org.junit.Assert.assertNotNull;
@DirtiesContext
public class SpringDecoderTests extends FeignClientFactoryBean {
@Autowired
FeignClientFactory factory;
@Value("${local.server.port}")
private int port = 0;
public SpringDecoderTests() {
setName("test");
}
public TestClient testClient() {
return feign().target(TestClient.class, "http://localhost:" + this.port);
return feign(factory).target(TestClient.class, "http://localhost:" + this.port);
}
@Test
......
......@@ -75,7 +75,7 @@ public class Demo {
}
@EnableFeignClients
@EnableFeignClients(clients = InvoiceClient.class)
@RibbonClient(name = "local", configuration = LocalRibbonClientConfiguration.class)
@ComponentScan("org.springframework.cloud.netflix.feign.encoding.app")
@EnableAutoConfiguration
......
......@@ -72,7 +72,7 @@ public class FeignAcceptEncodingTest {
}
@EnableFeignClients
@EnableFeignClients(clients = InvoiceClient.class)
@RibbonClient(name = "local", configuration = LocalRibbonClientConfiguration.class)
@ComponentScan("org.springframework.cloud.netflix.feign.encoding.app")
@EnableAutoConfiguration
......
......@@ -75,7 +75,7 @@ public class FeignContentEncodingTest {
}
@EnableFeignClients
@EnableFeignClients(clients = InvoiceClient.class)
@RibbonClient(name = "local", configuration = LocalRibbonClientConfiguration.class)
@ComponentScan("org.springframework.cloud.netflix.feign.encoding.app")
@EnableAutoConfiguration
......
......@@ -20,9 +20,11 @@ import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.springframework.cloud.netflix.feign.EnableFeignClients;
import org.springframework.cloud.netflix.feign.FeignAutoConfiguration;
import org.springframework.cloud.netflix.feign.FeignClient;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
......@@ -46,7 +48,8 @@ public class FeignClientValidationTests {
}
@Configuration
@EnableFeignClients()
@Import(FeignAutoConfiguration.class)
@EnableFeignClients(clients = BadConfiguration.Client.class)
protected static class BadConfiguration {
@FeignClient("foo_bar")
......
......@@ -83,7 +83,7 @@ public class FeignRibbonClientRetryTests {
@Configuration
@EnableAutoConfiguration
@RestController
@EnableFeignClients
@EnableFeignClients(clients = TestClient.class)
@RibbonClient(name = "localapp", configuration = LocalRibbonClientConfiguration.class)
public static class Application {
......
......@@ -113,7 +113,7 @@ public class FeignClientTests {
@Configuration
@EnableAutoConfiguration
@RestController
@EnableFeignClients
@EnableFeignClients(clients = {TestClientServiceId.class, TestClient.class})
@RibbonClient(name = "localapp", configuration = LocalRibbonClientConfiguration.class)
protected static class Application {
......
......@@ -18,11 +18,13 @@ package org.springframework.cloud.netflix.feign.valid;
import org.junit.Test;
import org.springframework.cloud.netflix.feign.EnableFeignClients;
import org.springframework.cloud.netflix.feign.FeignAutoConfiguration;
import org.springframework.cloud.netflix.feign.FeignClient;
import org.springframework.cloud.netflix.feign.ribbon.FeignRibbonClientAutoConfiguration;
import org.springframework.cloud.netflix.ribbon.RibbonAutoConfiguration;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
......@@ -42,10 +44,11 @@ public class FeignClientValidationTests {
}
@Configuration
@EnableFeignClients
@Import(FeignAutoConfiguration.class)
@EnableFeignClients(clients = GoodUrlConfiguration.Client.class)
protected static class GoodUrlConfiguration {
@FeignClient(url="http://example.com")
@FeignClient(name="example", url="http://example.com")
interface Client {
@RequestMapping(method = RequestMethod.GET, value = "/")
@Deprecated
......@@ -65,7 +68,8 @@ public class FeignClientValidationTests {
}
@Configuration
@EnableFeignClients
@Import(FeignAutoConfiguration.class)
@EnableFeignClients(clients = GoodServiceIdConfiguration.Client.class)
protected static class GoodServiceIdConfiguration {
@FeignClient("foo")
......
......@@ -100,7 +100,7 @@ public class FeignHttpClientTests {
@Configuration
@EnableAutoConfiguration
@RestController
@EnableFeignClients
@EnableFeignClients(clients = {TestClient.class, UserClient.class})
@RibbonClient(name = "localapp", configuration = LocalRibbonClientConfiguration.class)
protected static class Application implements UserService {
......
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