From f4b49d554281e6d3eda0434c3015f7c2242e518d 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) * 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 --- .../openfeign/FeignBuilderCustomizer.java | 31 ++++ .../openfeign/FeignClientFactoryBean.java | 15 ++ .../FeignBuilderCustomizerTests.java | 160 ++++++++++++++++++ 3 files changed, 206 insertions(+) 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 76473b08..ec8d8136 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 @@ -41,6 +41,7 @@ import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.cloud.openfeign.loadbalancer.FeignBlockingLoadBalancerClient; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; +import org.springframework.core.annotation.AnnotationAwareOrderComparator; import org.springframework.util.Assert; import org.springframework.util.StringUtils; @@ -49,6 +50,7 @@ import org.springframework.util.StringUtils; * @author Venil Noronha * @author Eko Kurniawan Khannedy * @author Gregor Zurowski + * @author Matt King */ class FeignClientFactoryBean implements FactoryBean, InitializingBean, ApplicationContextAware { @@ -96,10 +98,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 = this.applicationContext .getBean(FeignClientProperties.class); 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(); + } + + } + +}