From e745dd0c7cc84c916765337761edc7a92ad00e60 Mon Sep 17 00:00:00 2001 From: Jonatan Ivanov Date: Tue, 9 Feb 2021 11:58:10 -0800 Subject: [PATCH] Micrometer Support (#462) Setting up micrometer for Feign, fixes #457 This makes spring-cloud-openfeign capable of configuring any Capability (e.g.: Metrics5Capability for Dropwizard Metrics or MicrometerCapability for micrometer). This also auto-configures MicrometerCapability if a MeterRegistry is available and feign-micrometer is on the classpath. --- docs/src/main/asciidoc/_configprops.adoc | 1 + .../main/asciidoc/spring-cloud-openfeign.adoc | 197 +++++++++------ spring-cloud-openfeign-core/pom.xml | 5 + .../openfeign/FeignClientFactoryBean.java | 12 + .../FeignClientMetricsEnabledCondition.java | 52 ++++ .../openfeign/FeignClientProperties.java | 62 ++++- .../openfeign/FeignClientsConfiguration.java | 18 ++ ...itional-spring-configuration-metadata.json | 6 + .../FeignClientConfigurationTests.java | 140 +++++++++++ ...lientDisabledClientLevelFeaturesTests.java | 121 ++++++++++ .../FeignClientDisabledFeaturesTests.java | 120 ++++++++++ .../FeignClientErrorDecoderTests.java | 4 +- ...ignClientMetricsEnabledConditionTests.java | 225 ++++++++++++++++++ .../FeignClientOverrideDefaultsTests.java | 39 +++ .../openfeign/FeignClientPropertiesTests.java | 90 +++++++ .../FeignClientUsingConfigurerTest.java | 30 ++- .../FeignClientUsingPropertiesTests.java | 24 +- .../openfeign/MetricsPropertiesTests.java | 76 ++++++ .../test/EqualsAndHashCodeAssert.java | 105 ++++++++ .../src/test/resources/application.yml | 8 + .../resources/feign-properties.properties | 1 + 21 files changed, 1263 insertions(+), 73 deletions(-) create mode 100644 spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignClientMetricsEnabledCondition.java create mode 100644 spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/FeignClientConfigurationTests.java create mode 100644 spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/FeignClientDisabledClientLevelFeaturesTests.java create mode 100644 spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/FeignClientDisabledFeaturesTests.java create mode 100644 spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/FeignClientMetricsEnabledConditionTests.java create mode 100644 spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/FeignClientPropertiesTests.java create mode 100644 spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/MetricsPropertiesTests.java create mode 100644 spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/test/EqualsAndHashCodeAssert.java diff --git a/docs/src/main/asciidoc/_configprops.adoc b/docs/src/main/asciidoc/_configprops.adoc index d538f1bd..130064bf 100644 --- a/docs/src/main/asciidoc/_configprops.adoc +++ b/docs/src/main/asciidoc/_configprops.adoc @@ -21,6 +21,7 @@ |feign.httpclient.max-connections-per-route | `50` | |feign.httpclient.time-to-live | `900` | |feign.httpclient.time-to-live-unit | | +|feign.metrics.enabled | `true` | Enables metrics capability for Feign. |feign.okhttp.enabled | `false` | Enables the use of the OK HTTP Client by Feign. |=== \ No newline at end of file diff --git a/docs/src/main/asciidoc/spring-cloud-openfeign.adoc b/docs/src/main/asciidoc/spring-cloud-openfeign.adoc index 2a75479b..79cdb065 100644 --- a/docs/src/main/asciidoc/spring-cloud-openfeign.adoc +++ b/docs/src/main/asciidoc/spring-cloud-openfeign.adoc @@ -32,9 +32,9 @@ Example spring boot app @EnableFeignClients public class Application { - public static void main(String[] args) { - SpringApplication.run(Application.class, args); - } + public static void main(String[] args) { + SpringApplication.run(Application.class, args); + } } ---- @@ -44,14 +44,14 @@ public class Application { ---- @FeignClient("stores") public interface StoreClient { - @RequestMapping(method = RequestMethod.GET, value = "/stores") - List getStores(); + @RequestMapping(method = RequestMethod.GET, value = "/stores") + List getStores(); - @RequestMapping(method = RequestMethod.GET, value = "/stores") - Page getStores(Pageable pageable); + @RequestMapping(method = RequestMethod.GET, value = "/stores") + Page getStores(Pageable pageable); - @RequestMapping(method = RequestMethod.POST, value = "/stores/{storeId}", consumes = "application/json") - Store update(@PathVariable("storeId") Long storeId, Store store); + @RequestMapping(method = RequestMethod.POST, value = "/stores/{storeId}", consumes = "application/json") + Store update(@PathVariable("storeId") Long storeId, Store store); } ---- @@ -65,7 +65,7 @@ of the `@FeignClient` annotation. The load-balancer 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 +don't want to use Eureka, you can configure a list of servers in your external configuration using https://cloud.spring.io/spring-cloud-static/spring-cloud-commons/current/reference/html/#simplediscoveryclient[`SimpleDiscoveryClient`]. Spring Cloud OpenFeign supports all the features available for the blocking mode of Spring Cloud LoadBalancer. You can read more about them in the https://docs.spring.io/spring-cloud-commons/docs/current/reference/html/#spring-cloud-loadbalancer[project documentation]. @@ -84,7 +84,7 @@ Spring Cloud lets you take full control of the feign client by declaring additio ---- @FeignClient(name = "stores", configuration = FooConfiguration.class) public interface StoreClient { - //.. + //.. } ---- @@ -104,7 +104,7 @@ Placeholders are supported in the `name` and `url` attributes. ---- @FeignClient(name = "${feign.name}", url = "${feign.url}") public interface StoreClient { - //.. + //.. } ---- @@ -113,10 +113,11 @@ Spring Cloud OpenFeign provides the following beans by default for feign (`BeanT * `Decoder` feignDecoder: `ResponseEntityDecoder` (which wraps a `SpringDecoder`) * `Encoder` feignEncoder: `SpringEncoder` * `Logger` feignLogger: `Slf4jLogger` +* `MicrometerCapability` micrometerCapability: If `feign-micrometer` is on the classpath and `MeterRegistry` is available * `Contract` feignContract: `SpringMvcContract` * `Feign.Builder` feignBuilder: `FeignCircuitBreaker.Builder` -* `Client` feignClient: if Spring Cloud LoadBalancer is in the classpath, `FeignBlockingLoadBalancerClient` is used. -If none of them is in the classpath, the default feign client is used. +* `Client` feignClient: If Spring Cloud LoadBalancer is on the classpath, `FeignBlockingLoadBalancerClient` is used. +If none of them is on the classpath, the default feign client is used. NOTE: `spring-cloud-starter-openfeign` supports `spring-cloud-starter-loadbalancer`. However, as is an optional dependency, you need to make sure it been added to your project if you want to use it. @@ -132,6 +133,7 @@ Spring Cloud OpenFeign _does not_ provide the following beans by default for fei * `Collection` * `SetterFactory` * `QueryMapEncoder` +* `Capability` (`MicrometerCapability` is provided by default) A bean of `Retryer.NEVER_RETRY` with the type `Retryer` is created by default, which will disable retrying. Notice this retrying behavior is different from the Feign default one, where it will automatically retry IOExceptions, @@ -143,15 +145,15 @@ Creating a bean of one of those type and placing it in a `@FeignClient` configur ---- @Configuration public class FooConfiguration { - @Bean - public Contract feignContract() { - return new feign.Contract.Default(); - } + @Bean + public Contract feignContract() { + return new feign.Contract.Default(); + } - @Bean - public BasicAuthRequestInterceptor basicAuthRequestInterceptor() { - return new BasicAuthRequestInterceptor("user", "password"); - } + @Bean + public BasicAuthRequestInterceptor basicAuthRequestInterceptor() { + return new BasicAuthRequestInterceptor("user", "password"); + } } ---- @@ -163,25 +165,29 @@ application.yml [source,yaml] ---- feign: - client: - config: - feignName: - connectTimeout: 5000 - readTimeout: 5000 - loggerLevel: full - errorDecoder: com.example.SimpleErrorDecoder - retryer: com.example.SimpleRetryer - defaultQueryParameters: - query: queryValue - defaultRequestHeaders: - header: headerValue - requestInterceptors: - - com.example.FooRequestInterceptor - - com.example.BarRequestInterceptor - decode404: false - encoder: com.example.SimpleEncoder - decoder: com.example.SimpleDecoder - contract: com.example.SimpleContract + client: + config: + feignName: + connectTimeout: 5000 + readTimeout: 5000 + loggerLevel: full + errorDecoder: com.example.SimpleErrorDecoder + retryer: com.example.SimpleRetryer + defaultQueryParameters: + query: queryValue + defaultRequestHeaders: + header: headerValue + requestInterceptors: + - com.example.FooRequestInterceptor + - com.example.BarRequestInterceptor + decode404: false + encoder: com.example.SimpleEncoder + decoder: com.example.SimpleDecoder + contract: com.example.SimpleContract + capabilities: + - com.example.FooCapability + - com.example.BarCapability + metrics.enabled: false ---- 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. @@ -194,12 +200,12 @@ application.yml [source,yaml] ---- feign: - client: - config: - default: - connectTimeout: 5000 - readTimeout: 5000 - loggerLevel: basic + client: + config: + default: + connectTimeout: 5000 + readTimeout: 5000 + loggerLevel: basic ---- If we create both `@Configuration` bean and configuration properties, configuration properties will win. @@ -215,7 +221,7 @@ collision of these configuration beans. ---- @FeignClient(contextId = "fooClient", name = "stores", configuration = FooConfiguration.class) public interface FooClient { - //.. + //.. } ---- @@ -223,7 +229,7 @@ public interface FooClient { ---- @FeignClient(contextId = "barClient", name = "stores", configuration = BarConfiguration.class) public interface BarClient { - //.. + //.. } ---- @@ -279,12 +285,13 @@ class FooController { private FooClient adminClient; - @Autowired - public FooController(Decoder decoder, Encoder encoder, Client client, Contract contract) { + @Autowired + public FooController(Client client, Encoder encoder, Decoder decoder, Contract contract, MicrometerCapability micrometerCapability) { this.fooClient = Feign.builder().client(client) .encoder(encoder) .decoder(decoder) .contract(contract) + .addCapability(micrometerCapability) .requestInterceptor(new BasicAuthRequestInterceptor("user", "user")) .target(FooClient.class, "https://PROD-SVC"); @@ -292,9 +299,10 @@ class FooController { .encoder(encoder) .decoder(decoder) .contract(contract) + .addCapability(micrometerCapability) .requestInterceptor(new BasicAuthRequestInterceptor("admin", "admin")) .target(FooClient.class, "https://PROD-SVC"); - } + } } ---- @@ -321,7 +329,7 @@ To disable Spring Cloud CircuitBreaker support on a per-client basis create a va ---- @Configuration public class FooConfiguration { - @Bean + @Bean @Scope("prototype") public Feign.Builder feignBuilder() { return Feign.builder(); @@ -371,8 +379,8 @@ This allows grouping common operations into convenient base interfaces. ---- public interface UserService { - @RequestMapping(method = RequestMethod.GET, value ="/users/{id}") - User getUser(@PathVariable("id") long id); + @RequestMapping(method = RequestMethod.GET, value ="/users/{id}") + User getUser(@PathVariable("id") long id); } ---- @@ -455,10 +463,63 @@ For example, the following would set the `Logger.Level` to `FULL`: ---- @Configuration public class FooConfiguration { - @Bean - Logger.Level feignLoggerLevel() { - return Logger.Level.FULL; - } + @Bean + Logger.Level feignLoggerLevel() { + return Logger.Level.FULL; + } +} +---- + +=== Feign Capability support + +The Feign capabilities expose core Feign components so that these components can be modified. For example, the capabilities can take the `Client`, _decorate_ it, and give the decorated instance back to Feign. +The support for metrics libraries is a good real-life example for this. See <>. + +Creating one or more `Capability` beans and placing them in a `@FeignClient` configuration lets you register them and modify the behavior of the involved client. + +[source,java,indent=0] +---- +@Configuration +public class FooConfiguration { + @Bean + Capability customCapability() { + return new CustomCapability(); + } +} +---- + +=== Feign metrics + +If all of the following conditions are true, a `MicrometerCapability` bean is created and registered so that your Feign client publishes metrics to Micrometer: + +* `feign-micrometer` is on the classpath +* A `MeterRegistry` bean is available +* feign metrics properties are set to `true` (by default) + - `feign.metrics.enabled=true` (for all clients) + - `feign.client.config.feignName.metrics.enabled=true` (for a single client) + +NOTE: If your application already uses Micrometer, enabling metrics is as simple as putting `feign-micrometer` onto your classpath. + +You can also disable the feature by either: + +* excluding `feign-micrometer` from your classpath +* setting one of the feign metrics properties to `false` + - `feign.metrics.enabled=false` + - `feign.client.config.feignName.metrics.enabled=false` + +NOTE: `feign.metrics.enabled=false` disables metrics support for *all* Feign clients regardless of the value of the client-level flags: `feign.client.config.feignName.metrics.enabled`. +If you want to enable or disable merics per client, don't set `feign.metrics.enabled` and use `feign.client.config.feignName.metrics.enabled`. + +You can also customize the `MicrometerCapability` by registering your own bean: + +[source,java,indent=0] +---- +@Configuration +public class FooConfiguration { + @Bean + public MicrometerCapability micrometerCapability(MeterRegistry meterRegistry) { + return new MicrometerCapability(meterRegistry); + } } ---- @@ -477,10 +538,10 @@ For example, the `Params` class defines parameters `param1` and `param2`: ---- // Params.java public class Params { - private String param1; - private String param2; + private String param1; + private String param2; - // [Getters and setters omitted for brevity] + // [Getters and setters omitted for brevity] } ---- @@ -491,8 +552,8 @@ The following feign client uses the `Params` class by using the `@SpringQueryMap @FeignClient("demo") public interface DemoTemplate { - @GetMapping(path = "/demo") - String demoEndpoint(@SpringQueryMap Params params); + @GetMapping(path = "/demo") + String demoEndpoint(@SpringQueryMap Params params); } ---- @@ -513,8 +574,8 @@ and deserialize HATEOAS representation models: https://docs.spring.io/spring-hat @FeignClient("demo") public interface DemoTemplate { - @GetMapping(path = "/stores") - CollectionModel getStores(); + @GetMapping(path = "/stores") + CollectionModel getStores(); } ---- @@ -543,8 +604,8 @@ Note that both variable name and the path segment placeholder are called `matrix @FeignClient("demo") public interface DemoTemplate { - @GetMapping(path = "/stores") - CollectionModel getStores(); + @GetMapping(path = "/stores") + CollectionModel getStores(); } ---- diff --git a/spring-cloud-openfeign-core/pom.xml b/spring-cloud-openfeign-core/pom.xml index bb3b2c2d..19a7d322 100644 --- a/spring-cloud-openfeign-core/pom.xml +++ b/spring-cloud-openfeign-core/pom.xml @@ -99,6 +99,11 @@ feign-slf4j true + + io.github.openfeign + feign-micrometer + true + io.github.openfeign feign-httpclient 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 b9c03c6b..ed7feec0 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 @@ -22,6 +22,7 @@ import java.util.Map; import java.util.Objects; import java.util.concurrent.TimeUnit; +import feign.Capability; import feign.Client; import feign.Contract; import feign.ExceptionPropagationPolicy; @@ -60,6 +61,7 @@ import org.springframework.util.StringUtils; * @author Olga Maciaszek-Sharma * @author Ilia Ilinykh * @author Marcin Grzejszczak + * @author Jonatan Ivanov */ public class FeignClientFactoryBean implements FactoryBean, InitializingBean, ApplicationContextAware, BeanFactoryAware { @@ -199,6 +201,12 @@ public class FeignClientFactoryBean if (exceptionPropagationPolicy != null) { builder.exceptionPropagationPolicy(exceptionPropagationPolicy); } + + Map capabilities = getInheritedAwareInstances(context, Capability.class); + if (capabilities != null) { + capabilities.values().stream().sorted(AnnotationAwareOrderComparator.INSTANCE) + .forEach(builder::addCapability); + } } protected void configureUsingProperties(FeignClientProperties.FeignClientConfiguration config, @@ -264,6 +272,10 @@ public class FeignClientFactoryBean if (Objects.nonNull(config.getExceptionPropagationPolicy())) { builder.exceptionPropagationPolicy(config.getExceptionPropagationPolicy()); } + + if (config.getCapabilities() != null) { + config.getCapabilities().stream().map(this::getOrInstantiate).forEach(builder::addCapability); + } } private T getOrInstantiate(Class tClass) { diff --git a/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignClientMetricsEnabledCondition.java b/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignClientMetricsEnabledCondition.java new file mode 100644 index 00000000..c2706967 --- /dev/null +++ b/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignClientMetricsEnabledCondition.java @@ -0,0 +1,52 @@ +/* + * Copyright 2021-2021 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.Map; + +import org.springframework.context.annotation.Condition; +import org.springframework.context.annotation.ConditionContext; +import org.springframework.core.type.AnnotatedTypeMetadata; + +/** + * @author Jonatan Ivanov + */ +class FeignClientMetricsEnabledCondition implements Condition { + + @Override + public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { + FeignClientProperties feignClientProperties = context.getBeanFactory() + .getBeanProvider(FeignClientProperties.class).getIfAvailable(); + if (feignClientProperties != null) { + Map feignClientConfigMap = feignClientProperties + .getConfig(); + if (feignClientConfigMap != null) { + FeignClientProperties.FeignClientConfiguration feignClientConfig = feignClientConfigMap + .get(context.getEnvironment().getProperty("feign.client.name")); + if (feignClientConfig != null) { + FeignClientProperties.MetricsProperties metrics = feignClientConfig.getMetrics(); + if (metrics != null && metrics.getEnabled() != null) { + return metrics.getEnabled(); + } + } + } + } + + return true; + } + +} diff --git a/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignClientProperties.java b/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignClientProperties.java index da77ffe9..7e5cfb5b 100644 --- a/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignClientProperties.java +++ b/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignClientProperties.java @@ -22,6 +22,7 @@ import java.util.List; import java.util.Map; import java.util.Objects; +import feign.Capability; import feign.Contract; import feign.ExceptionPropagationPolicy; import feign.Logger; @@ -37,6 +38,7 @@ import org.springframework.boot.context.properties.ConfigurationProperties; * @author Eko Kurniawan Khannedy * @author Ilia Ilinykh * @author Ram Anaswara + * @author Jonatan Ivanov */ @ConfigurationProperties("feign.client") public class FeignClientProperties { @@ -134,6 +136,10 @@ public class FeignClientProperties { private ExceptionPropagationPolicy exceptionPropagationPolicy; + private List> capabilities; + + private MetricsProperties metrics; + public Logger.Level getLoggerLevel() { return loggerLevel; } @@ -238,6 +244,22 @@ public class FeignClientProperties { this.exceptionPropagationPolicy = exceptionPropagationPolicy; } + public List> getCapabilities() { + return capabilities; + } + + public void setCapabilities(List> capabilities) { + this.capabilities = capabilities; + } + + public MetricsProperties getMetrics() { + return metrics; + } + + public void setMetrics(MetricsProperties metrics) { + this.metrics = metrics; + } + @Override public boolean equals(Object o) { if (this == o) { @@ -255,14 +277,50 @@ public class FeignClientProperties { && Objects.equals(decoder, that.decoder) && Objects.equals(contract, that.contract) && Objects.equals(exceptionPropagationPolicy, that.exceptionPropagationPolicy) && Objects.equals(defaultRequestHeaders, that.defaultRequestHeaders) - && Objects.equals(defaultQueryParameters, that.defaultQueryParameters); + && Objects.equals(defaultQueryParameters, that.defaultQueryParameters) + && Objects.equals(capabilities, that.capabilities) && Objects.equals(metrics, that.metrics); } @Override public int hashCode() { return Objects.hash(loggerLevel, connectTimeout, readTimeout, retryer, errorDecoder, requestInterceptors, decode404, encoder, decoder, contract, exceptionPropagationPolicy, defaultQueryParameters, - defaultRequestHeaders); + defaultRequestHeaders, capabilities, metrics); + } + + } + + /** + * Metrics configuration for Feign Client. + */ + public static class MetricsProperties { + + private Boolean enabled = true; + + public Boolean getEnabled() { + return enabled; + } + + public void setEnabled(Boolean enabled) { + this.enabled = enabled; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + MetricsProperties that = (MetricsProperties) o; + return Objects.equals(enabled, that.enabled); + } + + @Override + public int hashCode() { + return Objects.hash(enabled); } } diff --git a/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignClientsConfiguration.java b/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignClientsConfiguration.java index 7685fd29..f5eb1fc6 100644 --- a/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignClientsConfiguration.java +++ b/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignClientsConfiguration.java @@ -27,7 +27,9 @@ import feign.codec.Decoder; import feign.codec.Encoder; import feign.form.MultipartFormContentProcessor; import feign.form.spring.SpringFormEncoder; +import feign.micrometer.MicrometerCapability; import feign.optionals.OptionalDecoder; +import io.micrometer.core.instrument.MeterRegistry; import org.springframework.beans.factory.ObjectFactory; import org.springframework.beans.factory.ObjectProvider; @@ -62,6 +64,7 @@ import static feign.form.ContentType.MULTIPART; * @author Dave Syer * @author Venil Noronha * @author Darren Foong + * @author Jonatan Ivanov */ @Configuration(proxyBeanMethods = false) public class FeignClientsConfiguration { @@ -203,4 +206,19 @@ public class FeignClientsConfiguration { } + @Configuration(proxyBeanMethods = false) + @ConditionalOnBean(type = "io.micrometer.core.instrument.MeterRegistry") + @ConditionalOnClass(name = "feign.micrometer.MicrometerCapability") + @ConditionalOnProperty(name = "feign.metrics.enabled", matchIfMissing = true) + @Conditional(FeignClientMetricsEnabledCondition.class) + protected static class MetricsConfiguration { + + @Bean + @ConditionalOnMissingBean + public MicrometerCapability micrometerCapability(MeterRegistry meterRegistry) { + return new MicrometerCapability(meterRegistry); + } + + } + } diff --git a/spring-cloud-openfeign-core/src/main/resources/META-INF/additional-spring-configuration-metadata.json b/spring-cloud-openfeign-core/src/main/resources/META-INF/additional-spring-configuration-metadata.json index 98ff39ae..0f5b8c77 100644 --- a/spring-cloud-openfeign-core/src/main/resources/META-INF/additional-spring-configuration-metadata.json +++ b/spring-cloud-openfeign-core/src/main/resources/META-INF/additional-spring-configuration-metadata.json @@ -43,6 +43,12 @@ "type": "java.lang.Boolean", "description": "Enables the request sent by Feign to be compressed.", "defaultValue": "false" + }, + { + "name": "feign.metrics.enabled", + "type": "java.lang.Boolean", + "description": "Enables metrics capability for Feign.", + "defaultValue": "true" } ] } diff --git a/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/FeignClientConfigurationTests.java b/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/FeignClientConfigurationTests.java new file mode 100644 index 00000000..ef737c91 --- /dev/null +++ b/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/FeignClientConfigurationTests.java @@ -0,0 +1,140 @@ +/* + * Copyright 2021-2021 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 java.util.List; +import java.util.Map; + +import feign.Capability; +import feign.Contract; +import feign.ExceptionPropagationPolicy; +import feign.Logger; +import feign.RequestInterceptor; +import feign.Retryer; +import feign.codec.Decoder; +import feign.codec.Encoder; +import feign.codec.ErrorDecoder; +import org.assertj.core.util.Lists; +import org.assertj.core.util.Maps; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.springframework.cloud.openfeign.test.EqualsAndHashCodeAssert.assertEqualsAndHashCodeConsistency; +import static org.springframework.cloud.openfeign.test.EqualsAndHashCodeAssert.assertEqualsConsistency; +import static org.springframework.cloud.openfeign.test.EqualsAndHashCodeAssert.assertEqualsReflexivity; +import static org.springframework.cloud.openfeign.test.EqualsAndHashCodeAssert.assertEqualsSymmetricity; +import static org.springframework.cloud.openfeign.test.EqualsAndHashCodeAssert.assertEqualsTransitivity; +import static org.springframework.cloud.openfeign.test.EqualsAndHashCodeAssert.assertHashCodeConsistency; + +/** + * @author Jonatan Ivanov + */ +class FeignClientConfigurationTests { + + @Test + void shouldDefaultToValuesWhenFieldsNotSet() { + FeignClientProperties.FeignClientConfiguration config = new FeignClientProperties.FeignClientConfiguration(); + + assertThat(config.getLoggerLevel()).isNull(); + assertThat(config.getConnectTimeout()).isNull(); + assertThat(config.getReadTimeout()).isNull(); + assertThat(config.getRetryer()).isNull(); + assertThat(config.getErrorDecoder()).isNull(); + assertThat(config.getRequestInterceptors()).isNull(); + assertThat(config.getDefaultRequestHeaders()).isNull(); + assertThat(config.getDefaultQueryParameters()).isNull(); + assertThat(config.getDecode404()).isNull(); + assertThat(config.getDecoder()).isNull(); + assertThat(config.getEncoder()).isNull(); + assertThat(config.getContract()).isNull(); + assertThat(config.getExceptionPropagationPolicy()).isNull(); + assertThat(config.getCapabilities()).isNull(); + assertThat(config.getMetrics()).isNull(); + } + + @Test + void shouldReturnValuesWhenSet() { + FeignClientProperties.FeignClientConfiguration config = new FeignClientProperties.FeignClientConfiguration(); + config.setLoggerLevel(Logger.Level.FULL); + config.setConnectTimeout(21); + config.setReadTimeout(42); + config.setRetryer(Retryer.class); + config.setErrorDecoder(ErrorDecoder.class); + List> requestInterceptors = Lists.list(RequestInterceptor.class); + config.setRequestInterceptors(requestInterceptors); + Map> defaultRequestHeaders = Maps.newHashMap("default", Lists.emptyList()); + config.setDefaultRequestHeaders(defaultRequestHeaders); + Map> defaultQueryParameters = Maps.newHashMap("default", Lists.emptyList()); + config.setDefaultQueryParameters(defaultQueryParameters); + config.setDecode404(true); + config.setDecoder(Decoder.class); + config.setEncoder(Encoder.class); + config.setContract(Contract.class); + config.setExceptionPropagationPolicy(ExceptionPropagationPolicy.UNWRAP); + List> capabilities = Lists.list(Capability.class); + config.setCapabilities(capabilities); + FeignClientProperties.MetricsProperties metrics = new FeignClientProperties.MetricsProperties(); + config.setMetrics(metrics); + + assertThat(config.getLoggerLevel()).isSameAs(Logger.Level.FULL); + assertThat(config.getConnectTimeout()).isEqualTo(21); + assertThat(config.getReadTimeout()).isEqualTo(42); + assertThat(config.getRetryer()).isSameAs(Retryer.class); + assertThat(config.getErrorDecoder()).isSameAs(ErrorDecoder.class); + assertThat(config.getRequestInterceptors()).isSameAs(requestInterceptors); + assertThat(config.getDefaultRequestHeaders()).isSameAs(defaultRequestHeaders); + assertThat(config.getDefaultQueryParameters()).isSameAs(defaultQueryParameters); + assertThat(config.getDecode404()).isTrue(); + assertThat(config.getDecoder()).isSameAs(Decoder.class); + assertThat(config.getEncoder()).isSameAs(Encoder.class); + assertThat(config.getContract()).isSameAs(Contract.class); + assertThat(config.getExceptionPropagationPolicy()).isSameAs(ExceptionPropagationPolicy.UNWRAP); + assertThat(config.getCapabilities()).isSameAs(capabilities); + assertThat(config.getMetrics()).isSameAs(metrics); + } + + /** + * Sanity-checks equals and hashCode contracts but does not check every variation of + * the fields. + */ + @Test + void shouldHaveSomewhatValidEqualsAndHashCode() { + FeignClientProperties.FeignClientConfiguration configOne = new FeignClientProperties.FeignClientConfiguration(); + FeignClientProperties.FeignClientConfiguration configTwo = new FeignClientProperties.FeignClientConfiguration(); + FeignClientProperties.FeignClientConfiguration configThree = new FeignClientProperties.FeignClientConfiguration(); + FeignClientProperties.FeignClientConfiguration differentConfig = new FeignClientProperties.FeignClientConfiguration(); + differentConfig.setDecode404(true); + + assertEqualsReflexivity(configOne); + + assertEqualsSymmetricity(configOne, configTwo); + assertEqualsSymmetricity(configOne, differentConfig); + assertEqualsSymmetricity(configOne, 42); + + assertEqualsTransitivity(configOne, configTwo, configThree); + + assertEqualsConsistency(configOne, configTwo); + assertEqualsConsistency(configOne, differentConfig); + assertEqualsConsistency(configOne, 42); + assertEqualsConsistency(configOne, null); + + assertHashCodeConsistency(configOne); + assertEqualsAndHashCodeConsistency(configOne, configTwo); + } + +} diff --git a/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/FeignClientDisabledClientLevelFeaturesTests.java b/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/FeignClientDisabledClientLevelFeaturesTests.java new file mode 100644 index 00000000..762f2f11 --- /dev/null +++ b/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/FeignClientDisabledClientLevelFeaturesTests.java @@ -0,0 +1,121 @@ +/* + * Copyright 2021-2021 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.Map; + +import feign.Capability; +import feign.Contract; +import feign.RequestLine; +import feign.micrometer.MicrometerCapability; +import org.junit.jupiter.api.Test; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Jonatan Ivanov + */ +@DirtiesContext +@ActiveProfiles("no-foo-metrics") +@SpringBootTest(classes = FeignClientDisabledClientLevelFeaturesTests.TestConfiguration.class) +class FeignClientDisabledClientLevelFeaturesTests { + + @Autowired + private FeignContext context; + + @Autowired + private FooClient foo; + + @Autowired + private BarClient bar; + + @Test + void clientsAvailable() { + assertThat(foo).isNotNull(); + assertThat(bar).isNotNull(); + } + + @Test + void capabilitiesShouldNotBeAvailableWhenDisabled() { + assertThat(context.getInstance("foo", MicrometerCapability.class)).isNull(); + assertThat(context.getInstances("foo", Capability.class)).isEmpty(); + + assertThat(context.getInstance("bar", MicrometerCapability.class)).isNotNull(); + Map barCapabilities = context.getInstances("bar", Capability.class); + assertThat(barCapabilities).hasSize(2); + assertThat(barCapabilities.get("micrometerCapability")).isExactlyInstanceOf(MicrometerCapability.class); + assertThat(barCapabilities.get("noOpCapability")).isExactlyInstanceOf(NoOpCapability.class); + } + + @FeignClient(name = "foo", url = "https://foo", configuration = FooConfiguration.class) + interface FooClient { + + @RequestLine("GET /") + String get(); + + } + + @FeignClient(name = "bar", url = "https://bar", configuration = BarConfiguration.class) + interface BarClient { + + @RequestMapping(value = "/", method = RequestMethod.GET) + String get(); + + } + + @Configuration(proxyBeanMethods = false) + @EnableAutoConfiguration + @EnableConfigurationProperties(FeignClientProperties.class) + @EnableFeignClients(clients = { FooClient.class, BarClient.class }) + protected static class TestConfiguration { + + } + + public static class FooConfiguration { + + @Bean // if the feign configuration empty, the context is not able to start + public Contract feignContract() { + return new Contract.Default(); + } + + } + + public static class BarConfiguration { + + @Bean + public Capability noOpCapability() { + return new NoOpCapability(); + } + + } + + private static class NoOpCapability implements Capability { + + } + +} diff --git a/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/FeignClientDisabledFeaturesTests.java b/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/FeignClientDisabledFeaturesTests.java new file mode 100644 index 00000000..bba6ffbc --- /dev/null +++ b/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/FeignClientDisabledFeaturesTests.java @@ -0,0 +1,120 @@ +/* + * Copyright 2021-2021 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.Map; + +import feign.Capability; +import feign.Contract; +import feign.RequestLine; +import feign.micrometer.MicrometerCapability; +import org.junit.jupiter.api.Test; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Jonatan Ivanov + */ +@DirtiesContext +@ActiveProfiles("no-metrics") +@SpringBootTest(classes = FeignClientDisabledFeaturesTests.TestConfiguration.class) +class FeignClientDisabledFeaturesTests { + + @Autowired + private FeignContext context; + + @Autowired + private FooClient foo; + + @Autowired + private BarClient bar; + + @Test + void clientsAvailable() { + assertThat(foo).isNotNull(); + assertThat(bar).isNotNull(); + } + + @Test + void capabilitiesShouldNotBeAvailable() { + assertThat(context.getInstance("foo", MicrometerCapability.class)).isNull(); + assertThat(context.getInstances("foo", Capability.class)).isEmpty(); + + assertThat(context.getInstance("bar", MicrometerCapability.class)).isNull(); + Map barCapabilities = context.getInstances("bar", Capability.class); + assertThat(barCapabilities).hasSize(1); + assertThat(barCapabilities.get("noOpCapability")).isExactlyInstanceOf(NoOpCapability.class); + } + + @FeignClient(name = "foo", url = "https://foo", configuration = FooConfiguration.class) + interface FooClient { + + @RequestLine("GET /") + String get(); + + } + + @FeignClient(name = "bar", url = "https://bar", configuration = BarConfiguration.class) + interface BarClient { + + @RequestMapping(value = "/", method = RequestMethod.GET) + String get(); + + } + + @Configuration(proxyBeanMethods = false) + @EnableAutoConfiguration + @EnableConfigurationProperties(FeignClientProperties.class) + @EnableFeignClients(clients = { FooClient.class, BarClient.class }) + protected static class TestConfiguration { + + } + + public static class FooConfiguration { + + @Bean // if the feign configuration empty, the context is not able to start + public Contract feignContract() { + return new Contract.Default(); + } + + } + + public static class BarConfiguration { + + @Bean + public Capability noOpCapability() { + return new NoOpCapability(); + } + + } + + private static class NoOpCapability implements Capability { + + } + +} diff --git a/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/FeignClientErrorDecoderTests.java b/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/FeignClientErrorDecoderTests.java index 19195751..b54f9cd0 100644 --- a/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/FeignClientErrorDecoderTests.java +++ b/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/FeignClientErrorDecoderTests.java @@ -44,6 +44,7 @@ import static org.assertj.core.api.Assertions.assertThat; /** * @author Michael Cramer + * @author Jonatan Ivanov */ @RunWith(SpringJUnit4ClassRunner.class) @SpringBootTest(classes = FeignClientErrorDecoderTests.TestConfiguration.class) @@ -85,7 +86,8 @@ public class FeignClientErrorDecoderTests { @SuppressWarnings({ "unchecked", "ConstantConditions" }) private Object getErrorDecoderFromClient(final Object client) { - Object invocationHandler = ReflectionTestUtils.getField(client, "h"); + Object invocationHandlerLambda = ReflectionTestUtils.getField(client, "h"); + Object invocationHandler = ReflectionTestUtils.getField(invocationHandlerLambda, "arg$2"); Map dispatch = (Map) ReflectionTestUtils .getField(invocationHandler, "dispatch"); Method key = new ArrayList<>(dispatch.keySet()).get(0); diff --git a/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/FeignClientMetricsEnabledConditionTests.java b/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/FeignClientMetricsEnabledConditionTests.java new file mode 100644 index 00000000..2016ca43 --- /dev/null +++ b/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/FeignClientMetricsEnabledConditionTests.java @@ -0,0 +1,225 @@ +/* + * Copyright 2021-2021 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.HashMap; + +import org.assertj.core.util.Maps; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import org.springframework.beans.factory.ObjectProvider; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.context.annotation.ConditionContext; +import org.springframework.core.env.Environment; +import org.springframework.core.type.AnnotatedTypeMetadata; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +/** + * @author Jonatan Ivanov + */ +@ExtendWith({ MockitoExtension.class }) +class FeignClientMetricsEnabledConditionTests { + + @Mock + private ConditionContext context; + + @Mock + private AnnotatedTypeMetadata metadata; + + @Mock + private ConfigurableListableBeanFactory beanFactory; + + @Mock + private ObjectProvider beanProvider; + + @Mock + private Environment environment; + + private final FeignClientMetricsEnabledCondition condition = new FeignClientMetricsEnabledCondition(); + + @BeforeEach + void setUp() { + when(context.getBeanFactory()).thenReturn(beanFactory); + when(beanFactory.getBeanProvider(FeignClientProperties.class)).thenReturn(beanProvider); + } + + @AfterEach + void tearDown() { + verify(context).getBeanFactory(); + verify(beanFactory).getBeanProvider(FeignClientProperties.class); + verify(beanProvider).getIfAvailable(); + } + + @Test + void shouldMatchWhenFeignClientPropertiesBeanIsMissing() { + when(beanProvider.getIfAvailable()).thenReturn(null); + + assertThat(condition.matches(context, metadata)).isTrue(); + verify(environment, never()).getProperty("feign.client.name"); + } + + @Test + void shouldMatchWhenConfigMapIsMissing() { + FeignClientProperties feignClientProperties = mock(FeignClientProperties.class); + when(beanProvider.getIfAvailable()).thenReturn(feignClientProperties); + when(feignClientProperties.getConfig()).thenReturn(null); + + assertThat(condition.matches(context, metadata)).isTrue(); + verify(environment, never()).getProperty("feign.client.name"); + } + + @Test + void shouldMatchWhenConfigMapDoesNotContainTheConfig() { + FeignClientProperties feignClientProperties = mock(FeignClientProperties.class); + when(beanProvider.getIfAvailable()).thenReturn(feignClientProperties); + when(context.getEnvironment()).thenReturn(environment); + when(environment.getProperty("feign.client.name")).thenReturn("foo"); + when(feignClientProperties.getConfig()).thenReturn(new HashMap<>()); + + assertThat(condition.matches(context, metadata)).isTrue(); + verify(environment).getProperty("feign.client.name"); + } + + @Test + void shouldMatchWhenClientNameIsNull() { + FeignClientProperties feignClientProperties = mock(FeignClientProperties.class); + when(beanProvider.getIfAvailable()).thenReturn(feignClientProperties); + when(context.getEnvironment()).thenReturn(environment); + when(environment.getProperty("feign.client.name")).thenReturn(null); + when(feignClientProperties.getConfig()).thenReturn(new HashMap<>()); + + assertThat(condition.matches(context, metadata)).isTrue(); + verify(environment).getProperty("feign.client.name"); + } + + @Test + void shouldMatchWhenClientNameIsEmpty() { + FeignClientProperties feignClientProperties = mock(FeignClientProperties.class); + when(beanProvider.getIfAvailable()).thenReturn(feignClientProperties); + when(context.getEnvironment()).thenReturn(environment); + when(environment.getProperty("feign.client.name")).thenReturn(""); + when(feignClientProperties.getConfig()).thenReturn(new HashMap<>()); + + assertThat(condition.matches(context, metadata)).isTrue(); + + verify(environment).getProperty("feign.client.name"); + } + + @Test + void shouldMatchWhenConfigMapContainsNullConfig() { + FeignClientProperties feignClientProperties = mock(FeignClientProperties.class); + when(beanProvider.getIfAvailable()).thenReturn(feignClientProperties); + when(context.getEnvironment()).thenReturn(environment); + when(environment.getProperty("feign.client.name")).thenReturn("foo"); + when(feignClientProperties.getConfig()).thenReturn(Maps.newHashMap("foo", null)); + + assertThat(condition.matches(context, metadata)).isTrue(); + verify(environment).getProperty("feign.client.name"); + } + + @Test + void shouldMatchWhenMetricsConfigurationIsMissing() { + FeignClientProperties feignClientProperties = mock(FeignClientProperties.class); + FeignClientProperties.FeignClientConfiguration feignClientConfig = mock( + FeignClientProperties.FeignClientConfiguration.class); + when(beanProvider.getIfAvailable()).thenReturn(feignClientProperties); + when(context.getEnvironment()).thenReturn(environment); + when(environment.getProperty("feign.client.name")).thenReturn("foo"); + when(feignClientProperties.getConfig()).thenReturn(Maps.newHashMap("foo", feignClientConfig)); + when(feignClientConfig.getMetrics()).thenReturn(null); + + assertThat(condition.matches(context, metadata)).isTrue(); + verify(environment).getProperty("feign.client.name"); + } + + @Test + void shouldMatchWhenEnabledFlagIsNotSet() { + FeignClientProperties feignClientProperties = mock(FeignClientProperties.class); + FeignClientProperties.FeignClientConfiguration feignClientConfig = mock( + FeignClientProperties.FeignClientConfiguration.class); + when(beanProvider.getIfAvailable()).thenReturn(feignClientProperties); + when(context.getEnvironment()).thenReturn(environment); + when(environment.getProperty("feign.client.name")).thenReturn("foo"); + when(feignClientProperties.getConfig()).thenReturn(Maps.newHashMap("foo", feignClientConfig)); + when(feignClientConfig.getMetrics()).thenReturn(new FeignClientProperties.MetricsProperties()); + + assertThat(condition.matches(context, metadata)).isTrue(); + verify(environment).getProperty("feign.client.name"); + } + + @Test + void shouldMatchWhenEnabledFlagIsNull() { + FeignClientProperties feignClientProperties = mock(FeignClientProperties.class); + FeignClientProperties.FeignClientConfiguration feignClientConfig = mock( + FeignClientProperties.FeignClientConfiguration.class); + when(beanProvider.getIfAvailable()).thenReturn(feignClientProperties); + when(context.getEnvironment()).thenReturn(environment); + when(environment.getProperty("feign.client.name")).thenReturn("foo"); + when(feignClientProperties.getConfig()).thenReturn(Maps.newHashMap("foo", feignClientConfig)); + FeignClientProperties.MetricsProperties metricsProperties = new FeignClientProperties.MetricsProperties(); + metricsProperties.setEnabled(null); + when(feignClientConfig.getMetrics()).thenReturn(metricsProperties); + + assertThat(condition.matches(context, metadata)).isTrue(); + verify(environment).getProperty("feign.client.name"); + } + + @Test + void shouldMatchWhenMetricsConfigurationIsEnabled() { + FeignClientProperties feignClientProperties = mock(FeignClientProperties.class); + FeignClientProperties.FeignClientConfiguration feignClientConfig = mock( + FeignClientProperties.FeignClientConfiguration.class); + when(beanProvider.getIfAvailable()).thenReturn(feignClientProperties); + when(context.getEnvironment()).thenReturn(environment); + when(environment.getProperty("feign.client.name")).thenReturn("foo"); + when(feignClientProperties.getConfig()).thenReturn(Maps.newHashMap("foo", feignClientConfig)); + FeignClientProperties.MetricsProperties metricsProperties = new FeignClientProperties.MetricsProperties(); + metricsProperties.setEnabled(true); + when(feignClientConfig.getMetrics()).thenReturn(metricsProperties); + + assertThat(condition.matches(context, metadata)).isTrue(); + verify(environment).getProperty("feign.client.name"); + } + + @Test + void shouldNotMatchWhenMetricsConfigurationIsEnabled() { + FeignClientProperties feignClientProperties = mock(FeignClientProperties.class); + FeignClientProperties.FeignClientConfiguration feignClientConfig = mock( + FeignClientProperties.FeignClientConfiguration.class); + when(beanProvider.getIfAvailable()).thenReturn(feignClientProperties); + when(context.getEnvironment()).thenReturn(environment); + when(environment.getProperty("feign.client.name")).thenReturn("foo"); + when(feignClientProperties.getConfig()).thenReturn(Maps.newHashMap("foo", feignClientConfig)); + FeignClientProperties.MetricsProperties metricsProperties = new FeignClientProperties.MetricsProperties(); + metricsProperties.setEnabled(false); + when(feignClientConfig.getMetrics()).thenReturn(metricsProperties); + + assertThat(condition.matches(context, metadata)).isFalse(); + verify(environment).getProperty("feign.client.name"); + } + +} diff --git a/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/FeignClientOverrideDefaultsTests.java b/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/FeignClientOverrideDefaultsTests.java index 3510ae43..8c099a86 100644 --- a/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/FeignClientOverrideDefaultsTests.java +++ b/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/FeignClientOverrideDefaultsTests.java @@ -16,6 +16,9 @@ package org.springframework.cloud.openfeign; +import java.util.Map; + +import feign.Capability; import feign.Contract; import feign.ExceptionPropagationPolicy; import feign.Logger; @@ -28,6 +31,7 @@ import feign.auth.BasicAuthRequestInterceptor; import feign.codec.Decoder; import feign.codec.Encoder; import feign.codec.ErrorDecoder; +import feign.micrometer.MicrometerCapability; import feign.optionals.OptionalDecoder; import feign.querymap.BeanQueryMapEncoder; import feign.slf4j.Slf4jLogger; @@ -48,6 +52,7 @@ import static org.assertj.core.api.Assertions.assertThat; /** * @author Spencer Gibb + * @author Jonatan Ivanov */ @SpringBootTest(classes = FeignClientOverrideDefaultsTests.TestConfiguration.class) @DirtiesContext @@ -137,6 +142,22 @@ class FeignClientOverrideDefaultsTests { .containsValues(ExceptionPropagationPolicy.UNWRAP); } + @Test + void shouldOverrideMicrometerCapability() { + assertThat(context.getInstance("foo", MicrometerCapability.class)) + .isExactlyInstanceOf(TestMicrometerCapability.class); + Map fooCapabilities = context.getInstances("foo", Capability.class); + assertThat(fooCapabilities).hasSize(1); + assertThat(fooCapabilities.get("micrometerCapability")).isExactlyInstanceOf(TestMicrometerCapability.class); + + assertThat(context.getInstance("bar", MicrometerCapability.class)) + .isExactlyInstanceOf(TestMicrometerCapability.class); + Map barCapabilities = context.getInstances("bar", Capability.class); + assertThat(barCapabilities).hasSize(2); + assertThat(barCapabilities.get("micrometerCapability")).isExactlyInstanceOf(TestMicrometerCapability.class); + assertThat(barCapabilities.get("noOpCapability")).isExactlyInstanceOf(NoOpCapability.class); + } + @FeignClient(name = "foo", url = "https://foo", configuration = FooConfiguration.class) interface FooClient { @@ -164,6 +185,11 @@ class FeignClientOverrideDefaultsTests { }; } + @Bean + MicrometerCapability micrometerCapability() { + return new TestMicrometerCapability(); + } + } public static class FooConfiguration { @@ -232,6 +258,19 @@ class FeignClientOverrideDefaultsTests { return ExceptionPropagationPolicy.UNWRAP; } + @Bean + public Capability noOpCapability() { + return new NoOpCapability(); + } + + } + + private static class TestMicrometerCapability extends feign.micrometer.MicrometerCapability { + + } + + private static class NoOpCapability implements Capability { + } } diff --git a/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/FeignClientPropertiesTests.java b/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/FeignClientPropertiesTests.java new file mode 100644 index 00000000..e9338b69 --- /dev/null +++ b/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/FeignClientPropertiesTests.java @@ -0,0 +1,90 @@ +/* + * Copyright 2021-2021 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.Map; + +import org.assertj.core.util.Maps; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.springframework.cloud.openfeign.test.EqualsAndHashCodeAssert.assertEqualsAndHashCodeConsistency; +import static org.springframework.cloud.openfeign.test.EqualsAndHashCodeAssert.assertEqualsConsistency; +import static org.springframework.cloud.openfeign.test.EqualsAndHashCodeAssert.assertEqualsReflexivity; +import static org.springframework.cloud.openfeign.test.EqualsAndHashCodeAssert.assertEqualsSymmetricity; +import static org.springframework.cloud.openfeign.test.EqualsAndHashCodeAssert.assertEqualsTransitivity; +import static org.springframework.cloud.openfeign.test.EqualsAndHashCodeAssert.assertHashCodeConsistency; + +/** + * @author Jonatan Ivanov + */ +class FeignClientPropertiesTests { + + @Test + void shouldDefaultToValuesWhenFieldsNotSet() { + FeignClientProperties properties = new FeignClientProperties(); + assertThat(properties.isDefaultToProperties()).isTrue(); + assertThat(properties.getDefaultConfig()).isEqualTo("default"); + assertThat(properties.getConfig()).isEmpty(); + assertThat(properties.isDecodeSlash()).isTrue(); + } + + @Test + void shouldReturnValuesWhenSet() { + FeignClientProperties properties = new FeignClientProperties(); + properties.setDefaultToProperties(false); + properties.setDefaultConfig("custom"); + Map configMap = Maps.newHashMap("foo", null); + properties.setConfig(configMap); + properties.setDecodeSlash(false); + + assertThat(properties.isDefaultToProperties()).isFalse(); + assertThat(properties.getDefaultConfig()).isEqualTo("custom"); + assertThat(properties.getConfig()).isSameAs(configMap); + assertThat(properties.isDecodeSlash()).isFalse(); + } + + /** + * Sanity-checks equals and hashCode contracts but does not check every variation of + * the fields. + */ + @Test + void shouldHaveSomewhatValidEqualsAndHashCode() { + FeignClientProperties propsOne = new FeignClientProperties(); + FeignClientProperties propsTwo = new FeignClientProperties(); + FeignClientProperties propsThree = new FeignClientProperties(); + FeignClientProperties differentProps = new FeignClientProperties(); + differentProps.setDecodeSlash(false); + + assertEqualsReflexivity(propsOne); + + assertEqualsSymmetricity(propsOne, propsTwo); + assertEqualsSymmetricity(propsOne, differentProps); + assertEqualsSymmetricity(propsOne, 42); + + assertEqualsTransitivity(propsOne, propsTwo, propsThree); + + assertEqualsConsistency(propsOne, propsTwo); + assertEqualsConsistency(propsOne, differentProps); + assertEqualsConsistency(propsOne, 42); + assertEqualsConsistency(propsOne, null); + + assertHashCodeConsistency(propsOne); + assertEqualsAndHashCodeConsistency(propsOne, propsTwo); + } + +} diff --git a/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/FeignClientUsingConfigurerTest.java b/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/FeignClientUsingConfigurerTest.java index e342a1b5..95456153 100644 --- a/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/FeignClientUsingConfigurerTest.java +++ b/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/FeignClientUsingConfigurerTest.java @@ -19,9 +19,11 @@ package org.springframework.cloud.openfeign; import java.lang.reflect.Field; import java.util.List; +import feign.Capability; import feign.Feign; import feign.Logger; import feign.RequestInterceptor; +import feign.micrometer.MicrometerCapability; import org.junit.Test; import org.junit.runner.RunWith; @@ -40,6 +42,7 @@ import static org.assertj.core.api.Assertions.assertThat; /** * @author matt king + * @author Jonatan Ivanov */ @DirtiesContext @RunWith(SpringJUnit4ClassRunner.class) @@ -67,6 +70,10 @@ public class FeignClientUsingConfigurerTest { List 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); + + List capabilities = (List) getBuilderValue(builder, "capabilities"); + assertThat(capabilities).hasSize(2).hasAtLeastOneElementOfType(NoOpCapability.class) + .hasAtLeastOneElementOfType(MicrometerCapability.class); } private Object getBuilderValue(Feign.Builder builder, String member) { @@ -84,9 +91,12 @@ public class FeignClientUsingConfigurerTest { Feign.Builder builder = factoryBean.feign(context); List interceptors = (List) getBuilderValue(builder, "requestInterceptors"); - assertThat(interceptors).as("interceptors not set").isEmpty(); assertThat(factoryBean.isInheritParentContext()).as("is inheriting from parent configuration").isFalse(); + + List capabilities = (List) getBuilderValue(builder, "capabilities"); + assertThat(capabilities).hasSize(2).hasAtLeastOneElementOfType(NoOpCapability.class) + .hasAtLeastOneElementOfType(MicrometerCapability.class); } @Test @@ -97,6 +107,10 @@ public class FeignClientUsingConfigurerTest { Feign.Builder builder = factoryBean.feign(context); assertThat(getBuilderValue(builder, "logLevel")).as("log level not set").isEqualTo(Logger.Level.HEADERS); + + List capabilities = (List) getBuilderValue(builder, "capabilities"); + assertThat(capabilities).hasSize(2).hasAtLeastOneElementOfType(NoOpCapability.class) + .hasAtLeastOneElementOfType(MicrometerCapability.class); } @EnableAutoConfiguration @@ -110,6 +124,11 @@ public class FeignClientUsingConfigurerTest { }; } + @Bean + public NoOpCapability noOpCapability() { + return new NoOpCapability(); + } + } public static class NoInheritConfiguration { @@ -119,6 +138,11 @@ public class FeignClientUsingConfigurerTest { return Logger.Level.HEADERS; } + @Bean + public NoOpCapability noOpCapability() { + return new NoOpCapability(); + } + @Bean public FeignClientConfigurer feignClientConfigurer() { return new FeignClientConfigurer() { @@ -143,4 +167,8 @@ public class FeignClientUsingConfigurerTest { } + private static class NoOpCapability implements Capability { + + } + } diff --git a/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/FeignClientUsingPropertiesTests.java b/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/FeignClientUsingPropertiesTests.java index b9206515..deaad2e2 100644 --- a/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/FeignClientUsingPropertiesTests.java +++ b/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/FeignClientUsingPropertiesTests.java @@ -34,6 +34,8 @@ import java.util.stream.Stream; import javax.servlet.http.HttpServletRequest; +import feign.Capability; +import feign.Feign; import feign.InvocationHandlerFactory; import feign.Request; import feign.RequestInterceptor; @@ -43,6 +45,7 @@ import feign.Retryer; import feign.codec.EncodeException; import feign.codec.Encoder; import feign.codec.ErrorDecoder; +import feign.micrometer.MicrometerCapability; import org.junit.Test; import org.junit.runner.RunWith; @@ -76,6 +79,7 @@ import static org.springframework.boot.test.context.SpringBootTest.WebEnvironmen * @author Eko Kurniawan Khannedy * @author Olga Maciaszek-Sharma * @author Ilia Ilinykh + * @author Jonatan Ivanov */ @SuppressWarnings("FieldMayBeFinal") @RunWith(SpringJUnit4ClassRunner.class) @@ -232,8 +236,22 @@ public class FeignClientUsingPropertiesTests { assertThat(options.readTimeoutMillis()).isEqualTo(5000); } + @Test + public void clientShouldContainCapabilities() { + fooFactoryBean.setApplicationContext(applicationContext); + Feign.Builder feignBuilder = fooFactoryBean.feign(context); + FooClient fooClient = feignBuilder.target(FooClient.class, "http://localhost:" + port); + + String response = fooClient.foo(); + assertThat(response).isEqualTo("OK"); + List capabilities = (List) ReflectionTestUtils.getField(feignBuilder, "capabilities"); + assertThat(capabilities).hasSize(2).hasAtLeastOneElementOfType(NoOpCapability.class) + .hasAtLeastOneElementOfType(MicrometerCapability.class); + } + private Request.Options getRequestOptions(Proxy client) { - Object invocationHandler = ReflectionTestUtils.getField(client, "h"); + Object invocationHandlerLambda = ReflectionTestUtils.getField(client, "h"); + Object invocationHandler = ReflectionTestUtils.getField(invocationHandlerLambda, "arg$2"); Map dispatch = (Map) ReflectionTestUtils .getField(Objects.requireNonNull(invocationHandler), "dispatch"); Method key = new ArrayList<>(dispatch.keySet()).get(0); @@ -385,4 +403,8 @@ public class FeignClientUsingPropertiesTests { } + public static class NoOpCapability implements Capability { + + } + } diff --git a/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/MetricsPropertiesTests.java b/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/MetricsPropertiesTests.java new file mode 100644 index 00000000..db62ac35 --- /dev/null +++ b/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/MetricsPropertiesTests.java @@ -0,0 +1,76 @@ +/* + * Copyright 2021-2021 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 org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.springframework.cloud.openfeign.test.EqualsAndHashCodeAssert.assertEqualsAndHashCodeConsistency; +import static org.springframework.cloud.openfeign.test.EqualsAndHashCodeAssert.assertEqualsConsistency; +import static org.springframework.cloud.openfeign.test.EqualsAndHashCodeAssert.assertEqualsReflexivity; +import static org.springframework.cloud.openfeign.test.EqualsAndHashCodeAssert.assertEqualsSymmetricity; +import static org.springframework.cloud.openfeign.test.EqualsAndHashCodeAssert.assertEqualsTransitivity; +import static org.springframework.cloud.openfeign.test.EqualsAndHashCodeAssert.assertHashCodeConsistency; + +/** + * @author Jonatan Ivanov + */ +class MetricsPropertiesTests { + + @Test + void shouldBeEnabledByDefault() { + FeignClientProperties.MetricsProperties properties = new FeignClientProperties.MetricsProperties(); + assertThat(properties.getEnabled()).isTrue(); + } + + @Test + void shouldBeDisabledWhenSet() { + FeignClientProperties.MetricsProperties properties = new FeignClientProperties.MetricsProperties(); + properties.setEnabled(false); + assertThat(properties.getEnabled()).isFalse(); + } + + /** + * Sanity-checks equals and hashCode contracts but does not check every variation of + * the fields. + */ + @Test + void shouldHaveSomewhatValidEqualsAndHashCode() { + FeignClientProperties.MetricsProperties propertyOne = new FeignClientProperties.MetricsProperties(); + FeignClientProperties.MetricsProperties propertyTwo = new FeignClientProperties.MetricsProperties(); + FeignClientProperties.MetricsProperties propertyThree = new FeignClientProperties.MetricsProperties(); + FeignClientProperties.MetricsProperties differentProperty = new FeignClientProperties.MetricsProperties(); + differentProperty.setEnabled(false); + + assertEqualsReflexivity(propertyOne); + + assertEqualsSymmetricity(propertyOne, propertyTwo); + assertEqualsSymmetricity(propertyOne, differentProperty); + assertEqualsSymmetricity(propertyOne, 42); + + assertEqualsTransitivity(propertyOne, propertyTwo, propertyThree); + + assertEqualsConsistency(propertyOne, propertyTwo); + assertEqualsConsistency(propertyOne, differentProperty); + assertEqualsConsistency(propertyOne, 42); + assertEqualsConsistency(propertyOne, null); + + assertHashCodeConsistency(propertyOne); + assertEqualsAndHashCodeConsistency(propertyOne, propertyTwo); + } + +} diff --git a/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/test/EqualsAndHashCodeAssert.java b/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/test/EqualsAndHashCodeAssert.java new file mode 100644 index 00000000..8b00a750 --- /dev/null +++ b/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/test/EqualsAndHashCodeAssert.java @@ -0,0 +1,105 @@ +/* + * Copyright 2021-2021 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.test; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * A few assertions to sanity-check equals and hashCode contracts: + * https://docs.oracle.com/javase/8/docs/api/java/lang/Object.html See + * {@link Object#equals(Object)} and {@link Object#hashCode()}. + * + * @author Jonatan Ivanov + */ +public class EqualsAndHashCodeAssert { + + /** + * Checks if equals is reflexive: for any non-null reference value x, x.equals(x) + * should return true. + * @param object the reference object to check + */ + public static void assertEqualsReflexivity(Object object) { + assertThat(object.equals(object)).isTrue(); + } + + /** + * Checks if equals is symmetric: for any non-null reference values x and y, + * x.equals(y) should return true if and only if y.equals(x) returns true The user of + * this method should call this at least twice: once with objects that are equal and + * once with objects that are not. + * @param objectOne a reference object to check + * @param objectTwo another reference object to check + */ + public static void assertEqualsSymmetricity(Object objectOne, Object objectTwo) { + assertThat(objectOne.equals(objectTwo)).isEqualTo(objectTwo.equals(objectOne)); + } + + /** + * Checks if equals is transitive: for any non-null reference values x, y, and z, if + * x.equals(y) returns true and y.equals(z) returns true, then x.equals(z) should + * return true. + * @param objectOne a reference object to check + * @param objectTwo another reference object to check + * @param objectThree and the third reference object to check + */ + public static void assertEqualsTransitivity(Object objectOne, Object objectTwo, Object objectThree) { + assertThat(objectOne.equals(objectTwo)).isTrue(); + assertThat(objectTwo.equals(objectThree)).isTrue(); + assertThat(objectOne.equals(objectThree)).isTrue(); + } + + /** + * Tries to check if equals is consistent: for any non-null reference values x and y, + * multiple invocations of x.equals(y) consistently return true or consistently return + * false. The user of this method should call this at least twice: once with objects + * that are equal and once with objects that are not. + * @param objectOne a reference object to check + * @param objectTwo another reference object to check + */ + public static void assertEqualsConsistency(Object objectOne, Object objectTwo) { + boolean equality = objectOne.equals(objectTwo); + for (int i = 0; i < 100; i++) { + assertThat(objectOne.equals(objectTwo)).isEqualTo(equality); + } + } + + /** + * Tries to check if hashCode is consistent: whenever it is invoked on the same object + * more than once during an execution of a Java application, the hashCode method must + * consistently return the same integer. + * @param object the reference object to check + */ + public static void assertHashCodeConsistency(Object object) { + int hashCode = object.hashCode(); + for (int i = 0; i < 100; i++) { + assertThat(object.hashCode()).isEqualTo(hashCode); + } + } + + /** + * Checks if equals and hashCode are consistent to each other: if two objects are + * equal according to the equals method, then calling the hashCode method on each of + * the two objects must produce the same integer result. + * @param objectOne a reference object to check + * @param objectTwo another reference object to check + */ + public static void assertEqualsAndHashCodeConsistency(Object objectOne, Object objectTwo) { + assertThat(objectOne.equals(objectTwo)).isTrue(); + assertThat(objectOne.hashCode()).isEqualTo(objectTwo.hashCode()); + } + +} diff --git a/spring-cloud-openfeign-core/src/test/resources/application.yml b/spring-cloud-openfeign-core/src/test/resources/application.yml index 4facc6ed..fe5fe430 100644 --- a/spring-cloud-openfeign-core/src/test/resources/application.yml +++ b/spring-cloud-openfeign-core/src/test/resources/application.yml @@ -34,3 +34,11 @@ feignClient: methodLevelRequestMappingPath: /hello2 myPlaceholderHeader: myPlaceholderHeaderValue management.endpoints.web.expose: '*' + +--- +spring.config.activate.on-profile: no-metrics +feign.metrics.enabled: false + +--- +spring.config.activate.on-profile: no-foo-metrics +feign.client.config.foo.metrics.enabled: false diff --git a/spring-cloud-openfeign-core/src/test/resources/feign-properties.properties b/spring-cloud-openfeign-core/src/test/resources/feign-properties.properties index 02a49414..9c00b4c2 100644 --- a/spring-cloud-openfeign-core/src/test/resources/feign-properties.properties +++ b/spring-cloud-openfeign-core/src/test/resources/feign-properties.properties @@ -8,6 +8,7 @@ feign.client.config.default.loggerLevel=full feign.client.config.default.errorDecoder=org.springframework.cloud.openfeign.FeignClientUsingPropertiesTests.DefaultErrorDecoder feign.client.config.default.retryer=org.springframework.cloud.openfeign.FeignClientUsingPropertiesTests.NoRetryer feign.client.config.default.decode404=true +feign.client.config.default.capabilities=org.springframework.cloud.openfeign.FeignClientUsingPropertiesTests.NoOpCapability feign.client.config.foo.requestInterceptors[0]=org.springframework.cloud.openfeign.FeignClientUsingPropertiesTests.FooRequestInterceptor feign.client.config.foo.requestInterceptors[1]=org.springframework.cloud.openfeign.FeignClientUsingPropertiesTests.BarRequestInterceptor feign.client.config.singleValue.defaultRequestHeaders[singleValueHeaders]=header