diff --git a/docs/src/main/asciidoc/spring-cloud-openfeign.adoc b/docs/src/main/asciidoc/spring-cloud-openfeign.adoc index c27783a9..b498afc2 100644 --- a/docs/src/main/asciidoc/spring-cloud-openfeign.adoc +++ b/docs/src/main/asciidoc/spring-cloud-openfeign.adoc @@ -348,7 +348,22 @@ 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`. +The circuit breaker name follows this pattern `#()`. When calling a `@FeignClient` with `FooClient` interface and the called interface method that has no parameters is `bar` then the circuit breaker name will be `FooClient#bar()`. + +NOTE: As of 2020.0.2, the circuit breaker name pattern has changed from `_`. +Using `CircuitBreakerNameResolver` introduced in 2020.0.4, circuit breaker names can retain the old pattern. + +Providing a bean of `CircuitBreakerNameResolver`, you can change the circuit breaker name pattern. +[source,java,indent=0] +---- +@Configuration +public class FooConfiguration { + @Bean + public CircuitBreakerNameResolver circuitBreakerNameResolver() { + return (String feignClientName, Target target, Method method) -> feignClientName + "_" + method.getName(); + } +} +---- To enable Spring Cloud CircuitBreaker group set the `feign.circuitbreaker.group.enabled` property to `true` (by default `false`). diff --git a/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/CircuitBreakerNameResolver.java b/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/CircuitBreakerNameResolver.java new file mode 100644 index 00000000..a7425250 --- /dev/null +++ b/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/CircuitBreakerNameResolver.java @@ -0,0 +1,34 @@ +/* + * Copyright 2013-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.lang.reflect.Method; + +import feign.Target; + +/** + * Used to resolve a circuitbreaker name which will be used in + * {@link org.springframework.cloud.client.circuitbreaker.CircuitBreakerFactory}. + * + * @author Kwangyong Kim + * @since 2020.0.4 + */ +public interface CircuitBreakerNameResolver { + + String resolveCircuitBreakerName(String feignClientName, Target target, Method method); + +} 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 fd2dd7be..373cd9e8 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 @@ -17,6 +17,7 @@ package org.springframework.cloud.openfeign; import java.io.IOException; +import java.lang.reflect.Method; import java.util.ArrayList; import java.util.List; import java.util.Timer; @@ -29,6 +30,7 @@ import com.fasterxml.jackson.databind.Module; import feign.Client; import feign.Feign; import feign.RequestInterceptor; +import feign.Target; import feign.hc5.ApacheHttp5Client; import feign.httpclient.ApacheHttpClient; import feign.okhttp.OkHttpClient; @@ -79,6 +81,7 @@ import org.springframework.security.oauth2.client.resource.OAuth2ProtectedResour * @author Olga Maciaszek-Sharma * @author Nguyen Ky Thanh * @author Andrii Bohutskyi + * @author Kwangyong Kim */ @Configuration(proxyBeanMethods = false) @ConditionalOnClass(Feign.class) @@ -146,12 +149,29 @@ public class FeignAutoConfiguration { return new DefaultTargeter(); } + @Bean + @ConditionalOnMissingBean(CircuitBreakerNameResolver.class) + public CircuitBreakerNameResolver circuitBreakerNameResolver() { + return new DefaultCircuitBreakerNameResolver(); + } + @Bean @ConditionalOnMissingBean @ConditionalOnBean(CircuitBreakerFactory.class) public Targeter circuitBreakerFeignTargeter(CircuitBreakerFactory circuitBreakerFactory, - @Value("${feign.circuitbreaker.group.enabled:false}") boolean circuitBreakerGroupEnabled) { - return new FeignCircuitBreakerTargeter(circuitBreakerFactory, circuitBreakerGroupEnabled); + @Value("${feign.circuitbreaker.group.enabled:false}") boolean circuitBreakerGroupEnabled, + CircuitBreakerNameResolver circuitBreakerNameResolver) { + return new FeignCircuitBreakerTargeter(circuitBreakerFactory, circuitBreakerGroupEnabled, + circuitBreakerNameResolver); + } + + static class DefaultCircuitBreakerNameResolver implements CircuitBreakerNameResolver { + + @Override + public String resolveCircuitBreakerName(String feignClientName, Target target, Method method) { + return Feign.configKey(target.getClass(), method); + } + } } 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 979d079b..f7d55b4b 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 @@ -27,6 +27,7 @@ import org.springframework.cloud.client.circuitbreaker.CircuitBreakerFactory; * * @author Marcin Grzejszczak * @author Andrii Bohutskyi + * @author Kwangyong Kim * @since 3.0.0 */ public final class FeignCircuitBreaker { @@ -53,6 +54,8 @@ public final class FeignCircuitBreaker { private boolean circuitBreakerGroupEnabled; + private CircuitBreakerNameResolver circuitBreakerNameResolver; + Builder circuitBreakerFactory(CircuitBreakerFactory circuitBreakerFactory) { this.circuitBreakerFactory = circuitBreakerFactory; return this; @@ -68,6 +71,11 @@ public final class FeignCircuitBreaker { return this; } + Builder circuitBreakerNameResolver(CircuitBreakerNameResolver circuitBreakerNameResolver) { + this.circuitBreakerNameResolver = circuitBreakerNameResolver; + return this; + } + public T target(Target target, T fallback) { return build(fallback != null ? new FallbackFactory.Default(fallback) : null).newInstance(target); } @@ -82,9 +90,9 @@ public final class FeignCircuitBreaker { } public Feign build(final FallbackFactory nullableFallbackFactory) { - super.invocationHandlerFactory( - (target, dispatch) -> new FeignCircuitBreakerInvocationHandler(circuitBreakerFactory, - feignClientName, target, dispatch, nullableFallbackFactory, circuitBreakerGroupEnabled)); + super.invocationHandlerFactory((target, dispatch) -> new FeignCircuitBreakerInvocationHandler( + circuitBreakerFactory, feignClientName, target, dispatch, nullableFallbackFactory, + circuitBreakerGroupEnabled, circuitBreakerNameResolver)); 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 b31374a7..531e3c78 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 @@ -24,7 +24,6 @@ import java.util.Map; import java.util.function.Function; import java.util.function.Supplier; -import feign.Feign; import feign.InvocationHandlerFactory; import feign.Target; @@ -51,9 +50,11 @@ class FeignCircuitBreakerInvocationHandler implements InvocationHandler { private final boolean circuitBreakerGroupEnabled; + private final CircuitBreakerNameResolver circuitBreakerNameResolver; + FeignCircuitBreakerInvocationHandler(CircuitBreakerFactory factory, String feignClientName, Target target, Map dispatch, FallbackFactory nullableFallbackFactory, - boolean circuitBreakerGroupEnabled) { + boolean circuitBreakerGroupEnabled, CircuitBreakerNameResolver circuitBreakerNameResolver) { this.factory = factory; this.feignClientName = feignClientName; this.target = checkNotNull(target, "target"); @@ -61,6 +62,7 @@ class FeignCircuitBreakerInvocationHandler implements InvocationHandler { this.fallbackMethodMap = toFallbackMethod(dispatch); this.nullableFallbackFactory = nullableFallbackFactory; this.circuitBreakerGroupEnabled = circuitBreakerGroupEnabled; + this.circuitBreakerNameResolver = circuitBreakerNameResolver; } @Override @@ -82,7 +84,8 @@ class FeignCircuitBreakerInvocationHandler implements InvocationHandler { else if ("toString".equals(method.getName())) { return toString(); } - String circuitName = Feign.configKey(target.type(), method); + + String circuitName = circuitBreakerNameResolver.resolveCircuitBreakerName(feignClientName, target, method); CircuitBreaker circuitBreaker = circuitBreakerGroupEnabled ? factory.create(circuitName, feignClientName) : factory.create(circuitName); Supplier supplier = asSupplier(method, args); 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 0a122856..f1ac0238 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 @@ -29,9 +29,13 @@ class FeignCircuitBreakerTargeter implements Targeter { private final boolean circuitBreakerGroupEnabled; - FeignCircuitBreakerTargeter(CircuitBreakerFactory circuitBreakerFactory, boolean circuitBreakerGroupEnabled) { + private final CircuitBreakerNameResolver circuitBreakerNameResolver; + + FeignCircuitBreakerTargeter(CircuitBreakerFactory circuitBreakerFactory, boolean circuitBreakerGroupEnabled, + CircuitBreakerNameResolver circuitBreakerNameResolver) { this.circuitBreakerFactory = circuitBreakerFactory; this.circuitBreakerGroupEnabled = circuitBreakerGroupEnabled; + this.circuitBreakerNameResolver = circuitBreakerNameResolver; } @Override @@ -85,7 +89,8 @@ class FeignCircuitBreakerTargeter implements Targeter { private FeignCircuitBreaker.Builder builder(String feignClientName, FeignCircuitBreaker.Builder builder) { return builder.circuitBreakerFactory(circuitBreakerFactory).feignClientName(feignClientName) - .circuitBreakerGroupEnabled(circuitBreakerGroupEnabled); + .circuitBreakerGroupEnabled(circuitBreakerGroupEnabled) + .circuitBreakerNameResolver(circuitBreakerNameResolver); } } 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 0ba44516..9d9568ef 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 @@ -16,12 +16,16 @@ package org.springframework.cloud.openfeign; +import java.lang.reflect.Method; + +import feign.Target; import org.assertj.core.api.Condition; import org.junit.jupiter.api.Test; import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.cloud.client.circuitbreaker.CircuitBreakerFactory; +import org.springframework.cloud.openfeign.FeignAutoConfiguration.CircuitBreakerPresentFeignTargeterConfiguration.DefaultCircuitBreakerNameResolver; import org.springframework.context.ConfigurableApplicationContext; import static org.assertj.core.api.Assertions.assertThat; @@ -31,6 +35,7 @@ import static org.mockito.Mockito.mock; * @author Tim Peeters * @author Olga Maciaszek-Sharma * @author Andrii Bohutskyi + * @author Kwangyong Kim */ class FeignAutoConfigurationTests { @@ -50,6 +55,8 @@ class FeignAutoConfigurationTests { .withPropertyValues("feign.circuitbreaker.enabled=true").run(ctx -> { assertOnlyOneTargeterPresent(ctx, FeignCircuitBreakerTargeter.class); assertThatFeignCircuitBreakerTargeterHasGroupEnabledPropertyWithValue(ctx, false); + assertThatFeignCircuitBreakerTargeterHasSameCircuitBreakerNameResolver(ctx, + DefaultCircuitBreakerNameResolver.class); }); } @@ -63,6 +70,17 @@ class FeignAutoConfigurationTests { }); } + @Test + void shouldInstantiateFeignCircuitBreakerTargeterWhenEnabledWithCustomCircuitBreakerNameResolver() { + runner.withBean(CircuitBreakerFactory.class, () -> mock(CircuitBreakerFactory.class)) + .withBean(CircuitBreakerNameResolver.class, CustomCircuitBreakerNameResolver::new) + .withPropertyValues("feign.circuitbreaker.enabled=true").run(ctx -> { + assertOnlyOneTargeterPresent(ctx, FeignCircuitBreakerTargeter.class); + assertThatFeignCircuitBreakerTargeterHasSameCircuitBreakerNameResolver(ctx, + CustomCircuitBreakerNameResolver.class); + }); + } + 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))); @@ -75,4 +93,19 @@ class FeignAutoConfigurationTests { assertThat(bean).hasFieldOrPropertyWithValue("circuitBreakerGroupEnabled", expectedValue); } + private void assertThatFeignCircuitBreakerTargeterHasSameCircuitBreakerNameResolver( + ConfigurableApplicationContext ctx, Class beanClass) { + final CircuitBreakerNameResolver bean = ctx.getBean(CircuitBreakerNameResolver.class); + assertThat(bean).isExactlyInstanceOf(beanClass); + } + + static class CustomCircuitBreakerNameResolver implements CircuitBreakerNameResolver { + + @Override + public String resolveCircuitBreakerName(String feignClientName, Target target, Method method) { + return feignClientName + "_" + method.getName(); + } + + } + }