From a8bcda0b5b114125e61d4e0c8297416a76b49af0 Mon Sep 17 00:00:00 2001 From: matt62king Date: Wed, 15 Apr 2020 06:55:27 -0500 Subject: [PATCH] add customizer for feign builder (#289). Fixes gh-436. * add customizer for feign builder * support @Order for customizers * switch to annotation aware comparator for @Order support * remove intermediate instances * removed default feign builder customizer # Conflicts: # spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignClientFactoryBean.java --- .../openfeign/FeignBuilderCustomizer.java | 31 ++++ .../openfeign/FeignClientFactoryBean.java | 13 ++ .../openfeign/FeignClientsRegistrar.java | 3 +- .../FeignBuilderCustomizerTests.java | 160 ++++++++++++++++++ .../openfeign/FeignClientsRegistrarTests.java | 14 +- .../feignclientsregistrar/TopLevelClient.java | 1 + .../sub/SubLevelClient.java | 1 + 7 files changed, 215 insertions(+), 8 deletions(-) create mode 100644 spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignBuilderCustomizer.java create mode 100644 spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/FeignBuilderCustomizerTests.java diff --git a/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignBuilderCustomizer.java b/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignBuilderCustomizer.java new file mode 100644 index 00000000..53a35f9a --- /dev/null +++ b/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignBuilderCustomizer.java @@ -0,0 +1,31 @@ +/* + * 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 feign.Feign; + +/** + * Allows application to customize the Feign builder. + * + * @author Matt King + */ +@FunctionalInterface +public interface FeignBuilderCustomizer { + + void customize(Feign.Builder builder); + +} diff --git a/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignClientFactoryBean.java b/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignClientFactoryBean.java index 1bb38cdf..7d729072 100644 --- a/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignClientFactoryBean.java +++ b/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignClientFactoryBean.java @@ -111,10 +111,23 @@ class FeignClientFactoryBean // @formatter:on configureFeign(context, builder); + applyBuildCustomizers(context, builder); return builder; } + private void applyBuildCustomizers(FeignContext context, Feign.Builder builder) { + Map customizerMap = context + .getInstances(contextId, FeignBuilderCustomizer.class); + + if (customizerMap != null) { + customizerMap.values().stream() + .sorted(AnnotationAwareOrderComparator.INSTANCE) + .forEach(feignBuilderCustomizer -> feignBuilderCustomizer + .customize(builder)); + } + } + protected void configureFeign(FeignContext context, Feign.Builder builder) { FeignClientProperties properties = applicationContext .getBean(FeignClientProperties.class); diff --git a/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignClientsRegistrar.java b/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignClientsRegistrar.java index dbb04fef..e340e420 100644 --- a/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignClientsRegistrar.java +++ b/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignClientsRegistrar.java @@ -192,8 +192,7 @@ class FeignClientsRegistrar "@FeignClient can only be specified on an interface"); Map attributes = annotationMetadata - .getAnnotationAttributes( - FeignClient.class.getCanonicalName()); + .getAnnotationAttributes(FeignClient.class.getCanonicalName()); String name = getClientName(attributes); registerClientConfiguration(registry, name, diff --git a/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/FeignBuilderCustomizerTests.java b/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/FeignBuilderCustomizerTests.java new file mode 100644 index 00000000..d66d3423 --- /dev/null +++ b/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/FeignBuilderCustomizerTests.java @@ -0,0 +1,160 @@ +/* + * 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 feign.Feign; +import feign.Logger; +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 org.springframework.core.annotation.Order; +import org.springframework.util.ReflectionUtils; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Matt King + */ +public class FeignBuilderCustomizerTests { + + @Test + public void testBuilderCustomizer() { + AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext( + FeignBuilderCustomizerTests.SampleConfiguration2.class); + + FeignClientFactoryBean clientFactoryBean = context + .getBean(FeignClientFactoryBean.class); + FeignContext feignContext = context.getBean(FeignContext.class); + + Feign.Builder builder = clientFactoryBean.feign(feignContext); + assertFeignBuilderField(builder, "logLevel", Logger.Level.HEADERS); + assertFeignBuilderField(builder, "decode404", true); + + context.close(); + } + + private void assertFeignBuilderField(Feign.Builder builder, String fieldName, + Object expectedValue) { + Field builderField = ReflectionUtils.findField(Feign.Builder.class, fieldName); + ReflectionUtils.makeAccessible(builderField); + + Object value = ReflectionUtils.getField(builderField, builder); + assertThat(value).as("Expected value for the field '" + fieldName + "':") + .isEqualTo(expectedValue); + } + + @Test + public void testBuildCustomizerOrdered() { + AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext( + FeignBuilderCustomizerTests.SampleConfiguration3.class); + + FeignClientFactoryBean clientFactoryBean = context + .getBean(FeignClientFactoryBean.class); + FeignContext feignContext = context.getBean(FeignContext.class); + + Feign.Builder builder = clientFactoryBean.feign(feignContext); + assertFeignBuilderField(builder, "logLevel", Logger.Level.FULL); + assertFeignBuilderField(builder, "decode404", true); + + context.close(); + } + + private static FeignClientFactoryBean defaultFeignClientFactoryBean() { + FeignClientFactoryBean feignClientFactoryBean = new FeignClientFactoryBean(); + feignClientFactoryBean.setContextId("test"); + feignClientFactoryBean.setName("test"); + feignClientFactoryBean.setType(FeignClientFactoryTests.TestType.class); + feignClientFactoryBean.setPath(""); + feignClientFactoryBean.setUrl("http://some.absolute.url"); + return feignClientFactoryBean; + } + + @Configuration(proxyBeanMethods = false) + @Import(FeignClientsConfiguration.class) + protected static class SampleConfiguration2 { + + @Bean + FeignContext feignContext() { + return new FeignContext(); + } + + @Bean + FeignClientProperties feignClientProperties() { + return new FeignClientProperties(); + } + + @Bean + FeignBuilderCustomizer feignBuilderCustomizer() { + return builder -> builder.logLevel(Logger.Level.HEADERS); + } + + @Bean + FeignBuilderCustomizer feignBuilderCustomizer2() { + return Feign.Builder::decode404; + } + + @Bean + FeignClientFactoryBean feignClientFactoryBean() { + return defaultFeignClientFactoryBean(); + } + + } + + @Configuration(proxyBeanMethods = false) + @Import(FeignClientsConfiguration.class) + protected static class SampleConfiguration3 { + + @Bean + FeignContext feignContext() { + return new FeignContext(); + } + + @Bean + FeignClientProperties feignClientProperties() { + return new FeignClientProperties(); + } + + @Bean + @Order(1) + FeignBuilderCustomizer feignBuilderCustomizer() { + return builder -> builder.logLevel(Logger.Level.HEADERS); + } + + @Bean + @Order(2) + FeignBuilderCustomizer feignBuilderCustomizer1() { + return builder -> builder.logLevel(Logger.Level.FULL); + } + + @Bean + FeignBuilderCustomizer feignBuilderCustomizer2() { + return Feign.Builder::decode404; + } + + @Bean + FeignClientFactoryBean feignClientFactoryBean() { + return defaultFeignClientFactoryBean(); + } + + } + +} diff --git a/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/FeignClientsRegistrarTests.java b/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/FeignClientsRegistrarTests.java index b9c924dc..b25f5a06 100644 --- a/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/FeignClientsRegistrarTests.java +++ b/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/FeignClientsRegistrarTests.java @@ -96,11 +96,12 @@ public class FeignClientsRegistrarTests { @Test public void shouldPassSubLevelFeignClient() { AnnotationConfigApplicationContext config = new AnnotationConfigApplicationContext(); - ((DefaultListableBeanFactory) config.getBeanFactory()).setAllowBeanDefinitionOverriding(false); + ((DefaultListableBeanFactory) config.getBeanFactory()) + .setAllowBeanDefinitionOverriding(false); config.register(TopLevelSubLevelTestConfig.class); - assertThatCode(() -> config.refresh()) - .as("Case https://github.com/spring-cloud/spring-cloud-openfeign/issues/331 should be solved") - .doesNotThrowAnyException(); + assertThatCode(() -> config.refresh()).as( + "Case https://github.com/spring-cloud/spring-cloud-openfeign/issues/331 should be solved") + .doesNotThrowAnyException(); } @@ -138,10 +139,11 @@ public class FeignClientsRegistrarTests { } @EnableFeignClients(clients = { - org.springframework.cloud.openfeign.feignclientsregistrar.TopLevelClient.class, - org.springframework.cloud.openfeign.feignclientsregistrar.sub.SubLevelClient.class}) + org.springframework.cloud.openfeign.feignclientsregistrar.TopLevelClient.class, + org.springframework.cloud.openfeign.feignclientsregistrar.sub.SubLevelClient.class }) @EnableAutoConfiguration(exclude = TestAutoConfiguration.class) protected static class TopLevelSubLevelTestConfig { + } } diff --git a/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/feignclientsregistrar/TopLevelClient.java b/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/feignclientsregistrar/TopLevelClient.java index 2128bce4..d52cc4a1 100644 --- a/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/feignclientsregistrar/TopLevelClient.java +++ b/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/feignclientsregistrar/TopLevelClient.java @@ -24,4 +24,5 @@ import org.springframework.cloud.openfeign.FeignClient; @FeignClient("top-level") public interface TopLevelClient { + } diff --git a/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/feignclientsregistrar/sub/SubLevelClient.java b/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/feignclientsregistrar/sub/SubLevelClient.java index 84d01aea..29126482 100644 --- a/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/feignclientsregistrar/sub/SubLevelClient.java +++ b/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/feignclientsregistrar/sub/SubLevelClient.java @@ -24,4 +24,5 @@ import org.springframework.cloud.openfeign.FeignClient; @FeignClient("sub-level") public interface SubLevelClient { + }