diff --git a/docs/src/main/asciidoc/_configprops.adoc b/docs/src/main/asciidoc/_configprops.adoc index 6501a9c3..6a46bd84 100644 --- a/docs/src/main/asciidoc/_configprops.adoc +++ b/docs/src/main/asciidoc/_configprops.adoc @@ -3,6 +3,7 @@ |feign.autoconfiguration.jackson.enabled | `false` | If true, PageJacksonModule and SortJacksonModule bean will be provided for Jackson page decoding. |feign.circuitbreaker.enabled | `false` | If true, an OpenFeign client will be wrapped with a Spring Cloud CircuitBreaker circuit breaker. +|feign.circuitbreaker.group.enabled | `false` | If true, an OpenFeign client will be wrapped with a Spring Cloud CircuitBreaker circuit breaker with with group. |feign.client.config | | |feign.client.decode-slash | `true` | Feign clients do not encode slash `/` characters by default. To change this behavior, set the `decodeSlash` to `false`. |feign.client.default-config | `default` | diff --git a/docs/src/main/asciidoc/spring-cloud-openfeign.adoc b/docs/src/main/asciidoc/spring-cloud-openfeign.adoc index 3d1b58f3..30998bd0 100644 --- a/docs/src/main/asciidoc/spring-cloud-openfeign.adoc +++ b/docs/src/main/asciidoc/spring-cloud-openfeign.adoc @@ -345,6 +345,9 @@ public class FooConfiguration { The circuit breaker name follows this pattern `#`. When calling a `@FeignClient` with name `foo` and the called interface method is `bar` then the circuit breaker name will be `foo_bar`. +To enable Spring Cloud CircuitBreaker group set the `feign.circuitbreaker.group.enabled` property to `true` (by default `false`). + + [[spring-cloud-feign-circuitbreaker-fallback]] === Feign Spring Cloud CircuitBreaker Fallbacks diff --git a/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignAutoConfiguration.java b/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignAutoConfiguration.java index 011fba2b..fd2dd7be 100644 --- a/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignAutoConfiguration.java +++ b/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignAutoConfiguration.java @@ -42,6 +42,7 @@ import org.apache.http.conn.HttpClientConnectionManager; import org.apache.http.impl.client.CloseableHttpClient; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; @@ -77,6 +78,7 @@ import org.springframework.security.oauth2.client.resource.OAuth2ProtectedResour * @author Tim Peeters * @author Olga Maciaszek-Sharma * @author Nguyen Ky Thanh + * @author Andrii Bohutskyi */ @Configuration(proxyBeanMethods = false) @ConditionalOnClass(Feign.class) @@ -147,8 +149,9 @@ public class FeignAutoConfiguration { @Bean @ConditionalOnMissingBean @ConditionalOnBean(CircuitBreakerFactory.class) - public Targeter circuitBreakerFeignTargeter(CircuitBreakerFactory circuitBreakerFactory) { - return new FeignCircuitBreakerTargeter(circuitBreakerFactory); + public Targeter circuitBreakerFeignTargeter(CircuitBreakerFactory circuitBreakerFactory, + @Value("${feign.circuitbreaker.group.enabled:false}") boolean circuitBreakerGroupEnabled) { + return new FeignCircuitBreakerTargeter(circuitBreakerFactory, circuitBreakerGroupEnabled); } } diff --git a/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignCircuitBreaker.java b/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignCircuitBreaker.java index 5edf6e6f..67f6164d 100644 --- a/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignCircuitBreaker.java +++ b/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignCircuitBreaker.java @@ -26,6 +26,7 @@ import org.springframework.cloud.client.circuitbreaker.CircuitBreakerFactory; * Allows Feign interfaces to work with {@link CircuitBreaker}. * * @author Marcin Grzejszczak + * @author Andrii Bohutskyi * @since 3.0.0 */ public final class FeignCircuitBreaker { @@ -50,6 +51,8 @@ public final class FeignCircuitBreaker { private String feignClientName; + private boolean circuitBreakerGroupEnabled; + Builder circuitBreakerFactory(CircuitBreakerFactory circuitBreakerFactory) { this.circuitBreakerFactory = circuitBreakerFactory; return this; @@ -60,6 +63,11 @@ public final class FeignCircuitBreaker { return this; } + Builder circuitBreakerGroupEnabled(boolean circuitBreakerGroupEnabled) { + this.circuitBreakerGroupEnabled = circuitBreakerGroupEnabled; + return this; + } + public T target(Target target, T fallback) { return build(fallback != null ? new FallbackFactory.Default(fallback) : null).newInstance(target); } @@ -75,7 +83,7 @@ public final class FeignCircuitBreaker { public Feign build(final FallbackFactory nullableFallbackFactory) { super.invocationHandlerFactory((target, dispatch) -> new FeignCircuitBreakerInvocationHandler( - circuitBreakerFactory, target, dispatch, nullableFallbackFactory)); + circuitBreakerFactory, feignClientName, target, dispatch, nullableFallbackFactory, circuitBreakerGroupEnabled)); return super.build(); } diff --git a/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignCircuitBreakerInvocationHandler.java b/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignCircuitBreakerInvocationHandler.java index 6ca0b912..e07691ab 100644 --- a/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignCircuitBreakerInvocationHandler.java +++ b/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignCircuitBreakerInvocationHandler.java @@ -39,6 +39,8 @@ class FeignCircuitBreakerInvocationHandler implements InvocationHandler { private final CircuitBreakerFactory factory; + private final String feignClientName; + private final Target target; private final Map dispatch; @@ -47,13 +49,18 @@ class FeignCircuitBreakerInvocationHandler implements InvocationHandler { private final Map fallbackMethodMap; - FeignCircuitBreakerInvocationHandler(CircuitBreakerFactory factory, Target target, - Map dispatch, FallbackFactory nullableFallbackFactory) { + private final boolean circuitBreakerGroupEnabled; + + FeignCircuitBreakerInvocationHandler(CircuitBreakerFactory factory, String feignClientName, Target target, + Map dispatch, FallbackFactory nullableFallbackFactory, + boolean circuitBreakerGroupEnabled) { this.factory = factory; + this.feignClientName = feignClientName; this.target = checkNotNull(target, "target"); this.dispatch = checkNotNull(dispatch, "dispatch"); this.fallbackMethodMap = toFallbackMethod(dispatch); this.nullableFallbackFactory = nullableFallbackFactory; + this.circuitBreakerGroupEnabled = circuitBreakerGroupEnabled; } @Override @@ -76,7 +83,8 @@ class FeignCircuitBreakerInvocationHandler implements InvocationHandler { return toString(); } String circuitName = Feign.configKey(target.type(), method); - CircuitBreaker circuitBreaker = this.factory.create(circuitName); + CircuitBreaker circuitBreaker = circuitBreakerGroupEnabled ? factory.create(circuitName, feignClientName) + : factory.create(circuitName); Supplier supplier = asSupplier(method, args); if (this.nullableFallbackFactory != null) { Function fallbackFunction = throwable -> { diff --git a/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignCircuitBreakerTargeter.java b/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignCircuitBreakerTargeter.java index f6048e95..0a122856 100644 --- a/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignCircuitBreakerTargeter.java +++ b/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignCircuitBreakerTargeter.java @@ -27,8 +27,11 @@ class FeignCircuitBreakerTargeter implements Targeter { private final CircuitBreakerFactory circuitBreakerFactory; - FeignCircuitBreakerTargeter(CircuitBreakerFactory circuitBreakerFactory) { + private final boolean circuitBreakerGroupEnabled; + + FeignCircuitBreakerTargeter(CircuitBreakerFactory circuitBreakerFactory, boolean circuitBreakerGroupEnabled) { this.circuitBreakerFactory = circuitBreakerFactory; + this.circuitBreakerGroupEnabled = circuitBreakerGroupEnabled; } @Override @@ -81,7 +84,8 @@ class FeignCircuitBreakerTargeter implements Targeter { } private FeignCircuitBreaker.Builder builder(String feignClientName, FeignCircuitBreaker.Builder builder) { - return builder.circuitBreakerFactory(this.circuitBreakerFactory).feignClientName(feignClientName); + return builder.circuitBreakerFactory(circuitBreakerFactory).feignClientName(feignClientName) + .circuitBreakerGroupEnabled(circuitBreakerGroupEnabled); } } 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 e97d8015..c7de5d77 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 @@ -14,6 +14,12 @@ "description": "If true, an OpenFeign client will be wrapped with a Spring Cloud CircuitBreaker circuit breaker.", "defaultValue": "false" }, + { + "name": "feign.circuitbreaker.group.enabled", + "type": "java.lang.Boolean", + "description": "If true, an OpenFeign client will be wrapped with a Spring Cloud CircuitBreaker circuit breaker with with group.", + "defaultValue": "false" + }, { "name": "feign.httpclient.enabled", "type": "java.lang.Boolean", diff --git a/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/FeignAutoConfigurationTests.java b/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/FeignAutoConfigurationTests.java index 72e450b6..0ba44516 100644 --- a/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/FeignAutoConfigurationTests.java +++ b/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/FeignAutoConfigurationTests.java @@ -30,6 +30,7 @@ import static org.mockito.Mockito.mock; /** * @author Tim Peeters * @author Olga Maciaszek-Sharma + * @author Andrii Bohutskyi */ class FeignAutoConfigurationTests { @@ -45,14 +46,33 @@ class FeignAutoConfigurationTests { @Test void shouldInstantiateFeignCircuitBreakerTargeterWhenEnabled() { + runner.withBean(CircuitBreakerFactory.class, () -> mock(CircuitBreakerFactory.class)) + .withPropertyValues("feign.circuitbreaker.enabled=true").run(ctx -> { + assertOnlyOneTargeterPresent(ctx, FeignCircuitBreakerTargeter.class); + assertThatFeignCircuitBreakerTargeterHasGroupEnabledPropertyWithValue(ctx, false); + }); + } + + @Test + void shouldInstantiateFeignCircuitBreakerTargeterWithEnabledGroup() { runner.withBean(CircuitBreakerFactory.class, () -> mock(CircuitBreakerFactory.class)) .withPropertyValues("feign.circuitbreaker.enabled=true") - .run(ctx -> assertOnlyOneTargeterPresent(ctx, FeignCircuitBreakerTargeter.class)); + .withPropertyValues("feign.circuitbreaker.group.enabled=true").run(ctx -> { + assertOnlyOneTargeterPresent(ctx, FeignCircuitBreakerTargeter.class); + assertThatFeignCircuitBreakerTargeterHasGroupEnabledPropertyWithValue(ctx, true); + }); } private void assertOnlyOneTargeterPresent(ConfigurableApplicationContext ctx, Class beanClass) { assertThat(ctx.getBeansOfType(Targeter.class)).hasSize(1).hasValueSatisfying(new Condition<>( beanClass::isInstance, String.format("Targeter should be an instance of %s", beanClass))); + + } + + private void assertThatFeignCircuitBreakerTargeterHasGroupEnabledPropertyWithValue( + ConfigurableApplicationContext ctx, boolean expectedValue) { + final FeignCircuitBreakerTargeter bean = ctx.getBean(FeignCircuitBreakerTargeter.class); + assertThat(bean).hasFieldOrPropertyWithValue("circuitBreakerGroupEnabled", expectedValue); } }