diff --git a/docs/src/main/asciidoc/spring-cloud-circuitbreaker.adoc b/docs/src/main/asciidoc/spring-cloud-circuitbreaker.adoc index 9a9404b7..179a1b40 100755 --- a/docs/src/main/asciidoc/spring-cloud-circuitbreaker.adoc +++ b/docs/src/main/asciidoc/spring-cloud-circuitbreaker.adoc @@ -89,3 +89,19 @@ the following documentation: * link:../../../../spring-cloud-circuitbreaker/current/reference/html/spring-cloud-circuitbreaker.html#configuring-resilience4j-circuit-breakers[Resilience4J] * link:https://github.com/alibaba/spring-cloud-alibaba/blob/master/spring-cloud-alibaba-docs/src/main/asciidoc/circuitbreaker-sentinel.adoc#circuit-breaker-spring-cloud-circuit-breaker-with-sentinel--configuring-sentinel-circuit-breakers[Sentinal] * link:../../../../spring-cloud-circuitbreaker/current/reference/html/spring-cloud-circuitbreaker.html#configuring-spring-retry-circuit-breakers[Spring Retry] + +Some `CircuitBreaker` implementations such as `Resilience4JCircuitBreaker` call `customize` method every time `CircuitBreaker#run` is called. +It can be inefficient. In that case, you can use `CircuitBreaker#once` method. It is useful where calling `customize` many times doesn't make sense, +for example, in case of https://resilience4j.readme.io/docs/circuitbreaker#section-consume-emitted-circuitbreakerevents[consuming Resilience4j's events]. + +The following example shows the way for each `io.github.resilience4j.circuitbreaker.CircuitBreaker` to consume events. + +==== +[source,java] +---- +Customizer.once(circuitBreaker -> { + circuitBreaker.getEventPublisher() + .onStateTransition(event -> log.info("{}: {}", event.getCircuitBreakerName(), event.getStateTransition())); +}, CircuitBreaker::getName) +---- +==== diff --git a/spring-cloud-commons/src/main/java/org/springframework/cloud/client/circuitbreaker/Customizer.java b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/circuitbreaker/Customizer.java index bda6d7b1..a041ab17 100644 --- a/spring-cloud-commons/src/main/java/org/springframework/cloud/client/circuitbreaker/Customizer.java +++ b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/circuitbreaker/Customizer.java @@ -16,13 +16,39 @@ package org.springframework.cloud.client.circuitbreaker; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.function.Function; + /** * Customizes the parameterized class. * * @author Ryan Baxter + * @author Toshiaki Maki */ public interface Customizer { void customize(TOCUSTOMIZE tocustomize); + /** + * Create a wrapped customizer that guarantees that the {@link #customize(Object)} + * method of the delegated customizer is called at most once per target. + * @param customizer a customizer to be delegated + * @param keyMapper a mapping function to produce the identifier of the target + * @param the type of the target to customize + * @param the type of the identifier of the target + * @return a wrapped customizer + */ + static Customizer once(Customizer customizer, + Function keyMapper) { + final ConcurrentMap customized = new ConcurrentHashMap<>(); + return t -> { + final K key = keyMapper.apply(t); + customized.computeIfAbsent(key, k -> { + customizer.customize(t); + return true; + }); + }; + } + } diff --git a/spring-cloud-commons/src/test/java/org/springframework/cloud/client/circuitbreaker/CustomizerTests.java b/spring-cloud-commons/src/test/java/org/springframework/cloud/client/circuitbreaker/CustomizerTests.java new file mode 100644 index 00000000..54b95132 --- /dev/null +++ b/spring-cloud-commons/src/test/java/org/springframework/cloud/client/circuitbreaker/CustomizerTests.java @@ -0,0 +1,42 @@ +/* + * Copyright 2012-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.client.circuitbreaker; + +import java.util.concurrent.atomic.AtomicInteger; + +import org.assertj.core.api.Assertions; +import org.junit.Test; + +public class CustomizerTests { + + @Test + public void testCustomizedOnlyOnce() { + AtomicInteger counter = new AtomicInteger(0); + final Customizer customizer = Customizer + .once(AtomicInteger::incrementAndGet, Object::hashCode); + customizer.customize(counter); + customizer.customize(counter); + customizer.customize(counter); + Assertions.assertThat(counter.get()).isEqualTo(1); + + AtomicInteger anotherCounter = new AtomicInteger(0); + customizer.customize(anotherCounter); + customizer.customize(anotherCounter); + Assertions.assertThat(counter.get()).isEqualTo(1); + } + +}