Add CircuitBreakerNameResolver (#575)
This commit is contained in:
@@ -348,7 +348,22 @@ public class FooConfiguration {
|
||||
}
|
||||
----
|
||||
|
||||
The circuit breaker name follows this pattern `<feignClientName>#<calledMethod>`. 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 `<feignClientClassName>#<calledMethod>(<parameterTypes>)`. 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 `<feignClientName>_<calledMethod>`.
|
||||
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`).
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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> T target(Target<T> target, T fallback) {
|
||||
return build(fallback != null ? new FallbackFactory.Default<T>(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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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<Method, InvocationHandlerFactory.MethodHandler> 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<Object> supplier = asSupplier(method, args);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user