Ignore parent config option 2.2.x (#326)

* added support to disable feign parent configurations, fixes gh-132

* added feign context test

* added feign context test class

* fix documentation on feign client

* removed inherit parent setting from annotation gh-132

* fix cherry pick commits gh-132

* add unit test gh-132

* add unit test gh-132
This commit is contained in:
matt62king
2020-05-11 06:10:58 -05:00
committed by GitHub
parent f6cf14f257
commit af103e0137
8 changed files with 419 additions and 19 deletions

View File

@@ -27,6 +27,7 @@ import org.springframework.context.ApplicationContext;
* {@link FeignClient} annotation.
*
* @author Sven Döring
* @author Matt King
*/
public class FeignClientBuilder {
@@ -57,6 +58,7 @@ public class FeignClientBuilder {
this.feignClientFactoryBean.setType(type);
this.feignClientFactoryBean.setName(FeignClientsRegistrar.getName(name));
this.feignClientFactoryBean.setContextId(FeignClientsRegistrar.getName(name));
this.feignClientFactoryBean.setInheritParentContext(true);
// preset default values - these values resemble the default values on the
// FeignClient annotation
this.url("").path("").decode404(false);
@@ -82,6 +84,11 @@ public class FeignClientBuilder {
return this;
}
public Builder<T> inheritParentContext(final boolean inheritParentContext) {
this.feignClientFactoryBean.setInheritParentContext(inheritParentContext);
return this;
}
public Builder<T> fallback(final Class<? extends T> fallback) {
FeignClientsRegistrar.validateFallback(fallback);
this.feignClientFactoryBean.setFallback(fallback);

View File

@@ -38,6 +38,7 @@ 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.cloud.openfeign.clientconfig.FeignClientConfigurer;
import org.springframework.cloud.openfeign.loadbalancer.FeignBlockingLoadBalancerClient;
import org.springframework.cloud.openfeign.ribbon.LoadBalancerFeignClient;
import org.springframework.context.ApplicationContext;
@@ -50,6 +51,7 @@ import org.springframework.util.StringUtils;
* @author Venil Noronha
* @author Eko Kurniawan Khannedy
* @author Gregor Zurowski
* @author Matt King
*/
class FeignClientFactoryBean
implements FactoryBean<Object>, InitializingBean, ApplicationContextAware {
@@ -71,6 +73,8 @@ class FeignClientFactoryBean
private boolean decode404;
private boolean inheritParentContext = true;
private ApplicationContext applicationContext;
private Class<?> fallback = void.class;
@@ -104,7 +108,12 @@ class FeignClientFactoryBean
protected void configureFeign(FeignContext context, Feign.Builder builder) {
FeignClientProperties properties = this.applicationContext
.getBean(FeignClientProperties.class);
if (properties != null) {
FeignClientConfigurer feignClientConfigurer = getOptional(context,
FeignClientConfigurer.class);
setInheritParentContext(feignClientConfigurer.inheritParentConfiguration());
if (properties != null && inheritParentContext) {
if (properties.isDefaultToProperties()) {
configureUsingConfiguration(context, builder);
configureUsingProperties(
@@ -129,15 +138,16 @@ class FeignClientFactoryBean
protected void configureUsingConfiguration(FeignContext context,
Feign.Builder builder) {
Logger.Level level = getOptional(context, Logger.Level.class);
Logger.Level level = getInheritedAwareOptional(context, Logger.Level.class);
if (level != null) {
builder.logLevel(level);
}
Retryer retryer = getOptional(context, Retryer.class);
Retryer retryer = getInheritedAwareOptional(context, Retryer.class);
if (retryer != null) {
builder.retryer(retryer);
}
ErrorDecoder errorDecoder = getOptional(context, ErrorDecoder.class);
ErrorDecoder errorDecoder = getInheritedAwareOptional(context,
ErrorDecoder.class);
if (errorDecoder != null) {
builder.errorDecoder(errorDecoder);
}
@@ -149,24 +159,26 @@ class FeignClientFactoryBean
builder.errorDecoder(factoryErrorDecoder);
}
}
Request.Options options = getOptional(context, Request.Options.class);
Request.Options options = getInheritedAwareOptional(context,
Request.Options.class);
if (options != null) {
builder.options(options);
}
Map<String, RequestInterceptor> requestInterceptors = context
.getInstances(this.contextId, RequestInterceptor.class);
Map<String, RequestInterceptor> requestInterceptors = getInheritedAwareInstances(
context, RequestInterceptor.class);
if (requestInterceptors != null) {
builder.requestInterceptors(requestInterceptors.values());
}
QueryMapEncoder queryMapEncoder = getOptional(context, QueryMapEncoder.class);
QueryMapEncoder queryMapEncoder = getInheritedAwareOptional(context,
QueryMapEncoder.class);
if (queryMapEncoder != null) {
builder.queryMapEncoder(queryMapEncoder);
}
if (this.decode404) {
builder.decode404();
}
ExceptionPropagationPolicy exceptionPropagationPolicy = getOptional(context,
ExceptionPropagationPolicy.class);
ExceptionPropagationPolicy exceptionPropagationPolicy = getInheritedAwareOptional(
context, ExceptionPropagationPolicy.class);
if (exceptionPropagationPolicy != null) {
builder.exceptionPropagationPolicy(exceptionPropagationPolicy);
}
@@ -252,6 +264,25 @@ class FeignClientFactoryBean
return context.getInstance(this.contextId, type);
}
protected <T> T getInheritedAwareOptional(FeignContext context, Class<T> type) {
if (inheritParentContext) {
return getOptional(context, type);
}
else {
return context.getInstanceWithoutAncestors(this.contextId, type);
}
}
protected <T> Map<String, T> getInheritedAwareInstances(FeignContext context,
Class<T> type) {
if (inheritParentContext) {
return context.getInstances(this.contextId, type);
}
else {
return context.getInstancesWithoutAncestors(this.contextId, type);
}
}
protected <T> T loadBalance(Feign.Builder builder, FeignContext context,
HardCodedTarget<T> target) {
Client client = getOptional(context, Client.class);
@@ -384,6 +415,14 @@ class FeignClientFactoryBean
this.decode404 = decode404;
}
public boolean isInheritParentContext() {
return inheritParentContext;
}
public void setInheritParentContext(boolean inheritParentContext) {
this.inheritParentContext = inheritParentContext;
}
public ApplicationContext getApplicationContext() {
return this.applicationContext;
}
@@ -420,6 +459,7 @@ class FeignClientFactoryBean
FeignClientFactoryBean that = (FeignClientFactoryBean) o;
return Objects.equals(this.applicationContext, that.applicationContext)
&& this.decode404 == that.decode404
&& this.inheritParentContext == that.inheritParentContext
&& Objects.equals(this.fallback, that.fallback)
&& Objects.equals(this.fallbackFactory, that.fallbackFactory)
&& Objects.equals(this.name, that.name)
@@ -430,8 +470,9 @@ class FeignClientFactoryBean
@Override
public int hashCode() {
return Objects.hash(this.applicationContext, this.decode404, this.fallback,
this.fallbackFactory, this.name, this.path, this.type, this.url);
return Objects.hash(this.applicationContext, this.decode404,
this.inheritParentContext, this.fallback, this.fallbackFactory, this.name,
this.path, this.type, this.url);
}
@Override
@@ -440,10 +481,12 @@ class FeignClientFactoryBean
.append(this.type).append(", ").append("name='").append(this.name)
.append("', ").append("url='").append(this.url).append("', ")
.append("path='").append(this.path).append("', ").append("decode404=")
.append(this.decode404).append(", ").append("applicationContext=")
.append(this.applicationContext).append(", ").append("fallback=")
.append(this.fallback).append(", ").append("fallbackFactory=")
.append(this.fallbackFactory).append("}").toString();
.append(this.decode404).append(", ").append("inheritParentContext=")
.append(this.inheritParentContext).append(", ")
.append("applicationContext=").append(this.applicationContext)
.append(", ").append("fallback=").append(this.fallback).append(", ")
.append("fallbackFactory=").append(this.fallbackFactory).append("}")
.toString();
}
}

View File

@@ -41,6 +41,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClas
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.data.web.SpringDataWebProperties;
import org.springframework.boot.autoconfigure.http.HttpMessageConverters;
import org.springframework.cloud.openfeign.clientconfig.FeignClientConfigurer;
import org.springframework.cloud.openfeign.support.AbstractFormWriter;
import org.springframework.cloud.openfeign.support.PageJacksonModule;
import org.springframework.cloud.openfeign.support.PageableSpringEncoder;
@@ -153,6 +154,13 @@ public class FeignClientsConfiguration {
return new PageJacksonModule();
}
@Bean
@ConditionalOnMissingBean(FeignClientConfigurer.class)
public FeignClientConfigurer feignClientConfigurer() {
return new FeignClientConfigurer() {
};
}
private Encoder springEncoder(ObjectProvider<AbstractFormWriter> formWriterProvider) {
AbstractFormWriter formWriter = formWriterProvider.getIfAvailable();

View File

@@ -16,7 +16,12 @@
package org.springframework.cloud.openfeign;
import java.util.Map;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactoryUtils;
import org.springframework.cloud.context.named.NamedContextFactory;
import org.springframework.lang.Nullable;
/**
* A factory that creates instances of feign classes. It creates a Spring
@@ -24,6 +29,7 @@ import org.springframework.cloud.context.named.NamedContextFactory;
*
* @author Spencer Gibb
* @author Dave Syer
* @author Matt King
*/
public class FeignContext extends NamedContextFactory<FeignClientSpecification> {
@@ -31,4 +37,19 @@ public class FeignContext extends NamedContextFactory<FeignClientSpecification>
super(FeignClientsConfiguration.class, "feign", "feign.client.name");
}
@Nullable
public <T> T getInstanceWithoutAncestors(String name, Class<T> type) {
try {
return BeanFactoryUtils.beanOfType(getContext(name), type);
}
catch (BeansException ex) {
return null;
}
}
@Nullable
public <T> Map<String, T> getInstancesWithoutAncestors(String name, Class<T> type) {
return getContext(name).getBeansOfType(type);
}
}

View File

@@ -0,0 +1,45 @@
/*
* Copyright 2013-2019 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
*
* https://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.openfeign.clientconfig;
/**
* Additional Feign Client configuration that are not included in
* {@link org.springframework.cloud.openfeign.FeignClient}.
*
* @author Matt King
*/
public interface FeignClientConfigurer {
/**
* @return whether to mark the feign proxy as a primary bean. Defaults to true.
*/
default boolean primary() {
return true;
}
/**
* FALSE will only apply configurations from classes listed in
* <code>configuration()</code>. Will still use parent instance of
* {@link feign.codec.Decoder}, {@link feign.codec.Encoder}, and
* {@link feign.Contract} if none are provided.
* @return weather to inherit parent context for client configuration.
*/
default boolean inheritParentConfiguration() {
return true;
}
}

View File

@@ -109,6 +109,7 @@ public class FeignClientBuilderTests {
assertFactoryBeanField(builder, "type", TestFeignClient.class);
assertFactoryBeanField(builder, "name", "TestClient");
assertFactoryBeanField(builder, "contextId", "TestClient");
assertFactoryBeanField(builder, "inheritParentContext", true);
// and:
assertFactoryBeanField(builder, "url",
@@ -127,10 +128,10 @@ public class FeignClientBuilderTests {
public void forType_allFieldsSetOnBuilder() {
// when:
final FeignClientBuilder.Builder builder = this.feignClientBuilder
.forType(TestFeignClient.class, "TestClient").decode404(true)
.forType(TestFeignClient.class, "TestClient").inheritParentContext(false)
.fallback(TestFeignClientFallback.class)
.fallbackFactory(TestFeignClientFallbackFactory.class).path("Path/")
.url("Url/").contextId("TestContext");
.fallbackFactory(TestFeignClientFallbackFactory.class).decode404(true)
.url("Url/").path("/Path").contextId("TestContext");
// then:
assertFactoryBeanField(builder, "applicationContext", this.applicationContext);
@@ -142,6 +143,7 @@ public class FeignClientBuilderTests {
assertFactoryBeanField(builder, "url", "http://Url/");
assertFactoryBeanField(builder, "path", "/Path");
assertFactoryBeanField(builder, "decode404", true);
assertFactoryBeanField(builder, "inheritParentContext", false);
assertFactoryBeanField(builder, "fallback", TestFeignClientFallback.class);
assertFactoryBeanField(builder, "fallbackFactory",
TestFeignClientFallbackFactory.class);

View File

@@ -0,0 +1,148 @@
/*
* Copyright 2013-2019 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
*
* https://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.openfeign;
import java.lang.reflect.Field;
import java.util.List;
import feign.Feign;
import feign.Logger;
import feign.RequestInterceptor;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.cloud.openfeign.clientconfig.FeignClientConfigurer;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.util.ReflectionUtils;
import static org.assertj.core.api.Assertions.assertThat;
/**
* @author matt king
*/
@DirtiesContext
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes = FeignClientUsingConfigurerTest.Application.class, value = {
"feign.client.config.default.loggerLevel=full",
"feign.client.config.default.requestInterceptors[0]=org.springframework.cloud.openfeign.FeignClientUsingPropertiesTests.FooRequestInterceptor",
"feign.client.config.default.requestInterceptors[1]=org.springframework.cloud.openfeign.FeignClientUsingPropertiesTests.BarRequestInterceptor" })
public class FeignClientUsingConfigurerTest {
private static final String BEAN_NAME_PREFIX = "&org.springframework.cloud.openfeign.FeignClientUsingConfigurerTest$";
@Autowired
private ApplicationContext applicationContext;
@Autowired
private FeignContext context;
@Test
public void testFeignClient() {
FeignClientFactoryBean factoryBean = (FeignClientFactoryBean) applicationContext
.getBean(BEAN_NAME_PREFIX + "TestFeignClient");
Feign.Builder builder = factoryBean.feign(context);
List<RequestInterceptor> interceptors = (List) getBuilderValue(builder,
"requestInterceptors");
assertThat(interceptors.size()).as("interceptors not set").isEqualTo(3);
assertThat(getBuilderValue(builder, "logLevel")).as("log level not set")
.isEqualTo(Logger.Level.FULL);
}
private Object getBuilderValue(Feign.Builder builder, String member) {
Field builderField = ReflectionUtils.findField(Feign.Builder.class, member);
ReflectionUtils.makeAccessible(builderField);
return ReflectionUtils.getField(builderField, builder);
}
@Test
public void testNoInheritFeignClient() {
FeignClientFactoryBean factoryBean = (FeignClientFactoryBean) applicationContext
.getBean(BEAN_NAME_PREFIX + "NoInheritFeignClient");
Feign.Builder builder = factoryBean.feign(context);
List<RequestInterceptor> interceptors = (List) getBuilderValue(builder,
"requestInterceptors");
assertThat(interceptors).as("interceptors not set").isEmpty();
assertThat(factoryBean.isInheritParentContext()).as("is inheriting from parent configuration").isFalse();
}
@Test
public void testNoInheritFeignClient_ignoreProperties() {
FeignClientFactoryBean factoryBean = (FeignClientFactoryBean) applicationContext
.getBean(BEAN_NAME_PREFIX + "NoInheritFeignClient");
Feign.Builder builder = factoryBean.feign(context);
assertThat(getBuilderValue(builder, "logLevel")).as("log level not set")
.isEqualTo(Logger.Level.HEADERS);
}
@EnableAutoConfiguration
@Configuration(proxyBeanMethods = false)
@EnableFeignClients(clients = { TestFeignClient.class, NoInheritFeignClient.class })
protected static class Application {
@Bean
public RequestInterceptor requestInterceptor() {
return requestTemplate -> {
};
}
}
public static class NoInheritConfiguration {
@Bean
public Logger.Level logLevel() {
return Logger.Level.HEADERS;
}
@Bean
public FeignClientConfigurer feignClientConfigurer() {
return new FeignClientConfigurer() {
@Override
public boolean inheritParentConfiguration() {
return false;
}
};
}
}
@FeignClient("testFeignClient")
interface TestFeignClient {
}
@FeignClient(name = "noInheritFeignClient",
configuration = NoInheritConfiguration.class)
interface NoInheritFeignClient {
}
}

View File

@@ -0,0 +1,126 @@
/*
* Copyright 2013-2019 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
*
* https://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.openfeign;
import java.util.Collection;
import feign.Logger;
import feign.RequestInterceptor;
import org.assertj.core.util.Lists;
import org.junit.Test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import static org.assertj.core.api.Assertions.assertThat;
public class FeignContextTest {
@Test
public void getInstanceWithoutAncestors_verifyNullForMissing() {
AnnotationConfigApplicationContext parent = new AnnotationConfigApplicationContext();
parent.refresh();
FeignContext feignContext = new FeignContext();
feignContext.setApplicationContext(parent);
feignContext.setConfigurations(
Lists.newArrayList(getSpec("empty", EmptyConfiguration.class)));
Logger.Level level = feignContext.getInstanceWithoutAncestors("empty",
Logger.Level.class);
assertThat(level).as("Logger was not null").isNull();
}
private FeignClientSpecification getSpec(String name, Class<?> configClass) {
return new FeignClientSpecification(name, new Class[] { configClass });
}
@Test
public void getInstancesWithoutAncestors_verifyEmptyForMissing() {
AnnotationConfigApplicationContext parent = new AnnotationConfigApplicationContext();
parent.refresh();
FeignContext feignContext = new FeignContext();
feignContext.setApplicationContext(parent);
feignContext.setConfigurations(
Lists.newArrayList(getSpec("empty", EmptyConfiguration.class)));
Collection<RequestInterceptor> interceptors = feignContext
.getInstancesWithoutAncestors("empty", RequestInterceptor.class).values();
assertThat(interceptors).as("Interceptors is not empty").isEmpty();
}
@Test
public void getInstanceWithoutAncestors() {
AnnotationConfigApplicationContext parent = new AnnotationConfigApplicationContext();
parent.refresh();
FeignContext feignContext = new FeignContext();
feignContext.setApplicationContext(parent);
feignContext.setConfigurations(
Lists.newArrayList(getSpec("demo", DemoConfiguration.class)));
Logger.Level level = feignContext.getInstanceWithoutAncestors("demo",
Logger.Level.class);
assertThat(level).isEqualTo(Logger.Level.FULL);
}
@Test
public void getInstancesWithoutAncestors() {
AnnotationConfigApplicationContext parent = new AnnotationConfigApplicationContext();
parent.refresh();
FeignContext feignContext = new FeignContext();
feignContext.setApplicationContext(parent);
feignContext.setConfigurations(
Lists.newArrayList(getSpec("demo", DemoConfiguration.class)));
Collection<RequestInterceptor> interceptors = feignContext
.getInstancesWithoutAncestors("demo", RequestInterceptor.class).values();
assertThat(interceptors.size()).isEqualTo(1);
}
@Configuration(proxyBeanMethods = false)
@Import(FeignClientsConfiguration.class)
protected static class EmptyConfiguration {
}
@Configuration(proxyBeanMethods = false)
@Import(FeignClientsConfiguration.class)
protected static class DemoConfiguration {
@Bean
public Logger.Level loggerLevel() {
return Logger.Level.FULL;
}
@Bean
public RequestInterceptor requestInterceptor() {
return (requestTemplate) -> {
};
}
}
}