From 61cf042f6b499c11a76bdaed76494becc2ce2ea3 Mon Sep 17 00:00:00 2001 From: MRomeh Date: Wed, 24 Apr 2019 13:15:05 +0200 Subject: [PATCH] Issue #268 and #291: Allow sharing of CircuitBreaker configurations and overriding of Spring beans. --- .gitignore | 5 + build.gradle | 9 +- libraries.gradle | 3 +- .../circuitbreaker/CircuitBreakerConfig.java | 126 ++-- .../CircuitBreakerRegistry.java | 99 +-- .../InMemoryCircuitBreakerRegistry.java | 122 ++-- .../CircuitBreakerConfigTest.java | 49 +- .../CircuitBreakerRegistryTest.java | 15 +- .../InMemoryCircuitBreakerRegistryTest.java | 94 +++ .../resilience4j/core/AbstractRegistry.java | 78 +++ .../io/github/resilience4j/core/Registry.java | 59 ++ .../core/AbstractRegistryTest.java | 41 ++ .../asciidoc/addon_guides/springboot.adoc | 53 ++ .../asciidoc/addon_guides/springboot2.adoc | 52 ++ .../micrometer/BulkheadMetrics.java | 1 + .../micrometer/CircuitBreakerMetrics.java | 6 +- .../micrometer/RateLimiterMetrics.java | 1 + .../tagged/TaggedBulkheadMetrics.java | 23 +- .../tagged/TaggedCircuitBreakerMetrics.java | 46 +- .../tagged/TaggedRateLimiterMetrics.java | 29 +- .../micrometer/tagged/TaggedRetryMetrics.java | 20 +- .../ratelimiter/RateLimiterConfig.java | 1 + .../resilience4j/retry/RetryConfig.java | 4 + resilience4j-spring-boot-common/README.adoc | 12 + resilience4j-spring-boot-common/build.gradle | 22 + ...actBulkheadConfigurationOnMissingBean.java | 79 +++ ...cuitBreakerConfigurationOnMissingBean.java | 90 +++ ...RateLimiterConfigurationOnMissingBean.java | 73 ++ ...stractRetryConfigurationOnMissingBean.java | 88 +++ .../common/SpringBootCommonTest.java | 87 +++ resilience4j-spring-boot/build.gradle | 6 +- .../BulkheadAutoConfiguration.java | 11 +- .../BulkheadConfigurationOnMissingBean.java | 43 ++ .../BulkheadMetricsAutoConfiguration.java | 2 + .../BulkheadPrometheusAutoConfiguration.java | 16 +- .../CircuitBreakerAutoConfiguration.java | 46 +- ...cuitBreakerConfigurationOnMissingBean.java | 57 ++ ...ircuitBreakerMetricsAutoConfiguration.java | 7 +- ...uitBreakerPrometheusAutoConfiguration.java | 3 + .../RateLimiterAutoConfiguration.java | 20 +- ...RateLimiterConfigurationOnMissingBean.java | 31 + .../RateLimiterMetricsAutoConfiguration.java | 8 +- ...ateLimiterPrometheusAutoConfiguration.java | 3 + .../autoconfigure/RetryAutoConfiguration.java | 6 +- .../RetryConfigurationOnMissingBean.java | 44 ++ .../RetryMetricsAutoConfiguration.java | 2 + ...ulkheadConfigurationOnMissingBeanTest.java | 118 ++++ .../CircuitBreakerAutoConfigurationTest.java | 187 ++++-- ...BreakerConfigurationOnMissingBeanTest.java | 118 ++++ .../RateLimiterAutoConfigurationTest.java | 8 + ...LimiterConfigurationOnMissingBeanTest.java | 120 ++++ .../RetryConfigurationOnMissingBeanTest.java | 118 ++++ .../src/test/resources/application.yaml | 13 + resilience4j-spring-boot2/build.gradle | 7 +- .../BulkheadAutoConfiguration.java | 36 +- .../BulkheadConfigurationOnMissingBean.java | 46 ++ .../BulkheadMetricsAutoConfiguration.java | 9 +- .../CircuitBreakerAutoConfiguration.java | 38 +- ...cuitBreakerConfigurationOnMissingBean.java | 87 +++ ...ircuitBreakerMetricsAutoConfiguration.java | 3 + .../CircuitBreakerEventDTOBuilder.java | 6 +- .../health/CircuitBreakerHealthIndicator.java | 6 +- .../RateLimiterAutoConfiguration.java | 7 +- ...RateLimiterConfigurationOnMissingBean.java | 34 + .../RateLimiterMetricsAutoConfiguration.java | 3 + .../autoconfigure/RetryAutoConfiguration.java | 3 +- .../RetryConfigurationOnMissingBean.java | 46 ++ .../RetryMetricsAutoConfiguration.java | 16 +- .../retry/autoconfigure/package-info.java | 24 + .../io/github/resilience4j/TestUtils.java | 24 + ...ulkheadConfigurationOnMissingBeanTest.java | 103 +++ ...itBreakerAutoConfigurationRxJava2Test.java | 2 +- .../CircuitBreakerAutoConfigurationTest.java | 79 ++- ...BreakerConfigurationOnMissingBeanTest.java | 88 +++ ...LimiterConfigurationOnMissingBeanTest.java | 90 +++ .../RetryConfigurationOnMissingBeanTest.java | 103 +++ .../src/test/resources/application.yaml | 13 + resilience4j-spring/build.gradle | 13 +- .../configure/RxJava2BulkheadAspectExt.java | 3 +- .../CircuitBreakerConfiguration.java | 71 +- ...CircuitBreakerConfigurationProperties.java | 625 ++++++++++-------- .../RxJava2CircuitBreakerAspectExt.java | 2 +- .../configure/RateLimiterConfiguration.java | 10 +- ....java => RxJava2RateLimiterAspectExt.java} | 5 +- .../RetryConfigurationProperties.java | 1 + .../configure/RxJava2RetryAspectExt.java | 3 +- .../utils/AnnotationExtractor.java | 1 + .../resilience4j/utils/CommonUtils.java | 74 +++ .../ReactorBulkheadAspectExtTest.java | 63 ++ .../RxJava2BulkheadAspectExtTest.java | 63 ++ ...CircuitBreakerConfigurationSpringTest.java | 113 ++++ .../CircuitBreakerConfigurationTest.java | 52 ++ .../ReactorCircuitBreakerAspectExtTest.java | 63 ++ .../RxJava2CircuitBreakerAspectExtTest.java | 60 ++ .../ReactorRateLimiterAspectExtTest.java | 63 ++ .../RxJava2RateLimiterAspectExtTest.java | 74 +++ .../configure/ReactorRetryAspectExtTest.java | 63 ++ .../configure/RxJava2RetryAspectExtTest.java | 60 ++ .../resilience4j/utils/CommonUtilsTest.java | 61 ++ settings.gradle | 4 +- 100 files changed, 4048 insertions(+), 746 deletions(-) create mode 100644 resilience4j-circuitbreaker/src/test/java/io/github/resilience4j/circuitbreaker/internal/InMemoryCircuitBreakerRegistryTest.java create mode 100644 resilience4j-core/src/main/java/io/github/resilience4j/core/AbstractRegistry.java create mode 100644 resilience4j-core/src/main/java/io/github/resilience4j/core/Registry.java create mode 100644 resilience4j-core/src/test/java/io/github/resilience4j/core/AbstractRegistryTest.java create mode 100644 resilience4j-spring-boot-common/README.adoc create mode 100644 resilience4j-spring-boot-common/build.gradle create mode 100644 resilience4j-spring-boot-common/src/main/java/io/github/resilience4j/springboot/common/bulkhead/autoconfigure/AbstractBulkheadConfigurationOnMissingBean.java create mode 100644 resilience4j-spring-boot-common/src/main/java/io/github/resilience4j/springboot/common/circuitbreaker/autoconfigure/AbstractCircuitBreakerConfigurationOnMissingBean.java create mode 100644 resilience4j-spring-boot-common/src/main/java/io/github/resilience4j/springboot/common/ratelimiter/autoconfigure/AbstractRateLimiterConfigurationOnMissingBean.java create mode 100644 resilience4j-spring-boot-common/src/main/java/io/github/resilience4j/springboot/common/retry/autoconfigure/AbstractRetryConfigurationOnMissingBean.java create mode 100644 resilience4j-spring-boot-common/src/test/java/io/github/resilience4j/springboot/common/SpringBootCommonTest.java create mode 100644 resilience4j-spring-boot/src/main/java/io/github/resilience4j/bulkhead/autoconfigure/BulkheadConfigurationOnMissingBean.java create mode 100644 resilience4j-spring-boot/src/main/java/io/github/resilience4j/circuitbreaker/autoconfigure/CircuitBreakerConfigurationOnMissingBean.java create mode 100644 resilience4j-spring-boot/src/main/java/io/github/resilience4j/ratelimiter/autoconfigure/RateLimiterConfigurationOnMissingBean.java create mode 100644 resilience4j-spring-boot/src/main/java/io/github/resilience4j/retry/autoconfigure/RetryConfigurationOnMissingBean.java create mode 100644 resilience4j-spring-boot/src/test/java/io/github/resilience4j/bulkhead/autoconfigure/BulkheadConfigurationOnMissingBeanTest.java create mode 100644 resilience4j-spring-boot/src/test/java/io/github/resilience4j/circuitbreaker/autoconfigure/CircuitBreakerConfigurationOnMissingBeanTest.java create mode 100644 resilience4j-spring-boot/src/test/java/io/github/resilience4j/ratelimiter/autoconfigure/RateLimiterConfigurationOnMissingBeanTest.java create mode 100644 resilience4j-spring-boot/src/test/java/io/github/resilience4j/retry/autoconfigure/RetryConfigurationOnMissingBeanTest.java create mode 100644 resilience4j-spring-boot2/src/main/java/io/github/resilience4j/bulkhead/autoconfigure/BulkheadConfigurationOnMissingBean.java create mode 100644 resilience4j-spring-boot2/src/main/java/io/github/resilience4j/circuitbreaker/autoconfigure/CircuitBreakerConfigurationOnMissingBean.java create mode 100644 resilience4j-spring-boot2/src/main/java/io/github/resilience4j/ratelimiter/autoconfigure/RateLimiterConfigurationOnMissingBean.java create mode 100644 resilience4j-spring-boot2/src/main/java/io/github/resilience4j/retry/autoconfigure/RetryConfigurationOnMissingBean.java create mode 100644 resilience4j-spring-boot2/src/main/java/io/github/resilience4j/retry/autoconfigure/package-info.java create mode 100644 resilience4j-spring-boot2/src/test/java/io/github/resilience4j/TestUtils.java create mode 100644 resilience4j-spring-boot2/src/test/java/io/github/resilience4j/bulkhead/autoconfigure/BulkheadConfigurationOnMissingBeanTest.java create mode 100644 resilience4j-spring-boot2/src/test/java/io/github/resilience4j/circuitbreaker/autoconfigure/CircuitBreakerConfigurationOnMissingBeanTest.java create mode 100644 resilience4j-spring-boot2/src/test/java/io/github/resilience4j/ratelimiter/autoconfigure/RateLimiterConfigurationOnMissingBeanTest.java create mode 100644 resilience4j-spring-boot2/src/test/java/io/github/resilience4j/retry/autoconfigure/RetryConfigurationOnMissingBeanTest.java rename resilience4j-spring/src/main/java/io/github/resilience4j/ratelimiter/configure/{RxJava2RateLimterAspectExt.java => RxJava2RateLimiterAspectExt.java} (93%) create mode 100644 resilience4j-spring/src/main/java/io/github/resilience4j/utils/CommonUtils.java create mode 100644 resilience4j-spring/src/test/java/io/github/resilience4j/bulkhead/configure/ReactorBulkheadAspectExtTest.java create mode 100644 resilience4j-spring/src/test/java/io/github/resilience4j/bulkhead/configure/RxJava2BulkheadAspectExtTest.java create mode 100644 resilience4j-spring/src/test/java/io/github/resilience4j/circuitbreaker/configure/CircuitBreakerConfigurationSpringTest.java create mode 100644 resilience4j-spring/src/test/java/io/github/resilience4j/circuitbreaker/configure/CircuitBreakerConfigurationTest.java create mode 100644 resilience4j-spring/src/test/java/io/github/resilience4j/circuitbreaker/configure/ReactorCircuitBreakerAspectExtTest.java create mode 100644 resilience4j-spring/src/test/java/io/github/resilience4j/circuitbreaker/configure/RxJava2CircuitBreakerAspectExtTest.java create mode 100644 resilience4j-spring/src/test/java/io/github/resilience4j/ratelimiter/configure/ReactorRateLimiterAspectExtTest.java create mode 100644 resilience4j-spring/src/test/java/io/github/resilience4j/ratelimiter/configure/RxJava2RateLimiterAspectExtTest.java create mode 100644 resilience4j-spring/src/test/java/io/github/resilience4j/retry/configure/ReactorRetryAspectExtTest.java create mode 100644 resilience4j-spring/src/test/java/io/github/resilience4j/retry/configure/RxJava2RetryAspectExtTest.java create mode 100644 resilience4j-spring/src/test/java/io/github/resilience4j/utils/CommonUtilsTest.java diff --git a/.gitignore b/.gitignore index c52b6e1771..34efbc96f3 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,8 @@ build classes */out +.classpath +.project +.settings +*/bin +*.orig \ No newline at end of file diff --git a/build.gradle b/build.gradle index 8ebd77fb16..99131487f6 100644 --- a/build.gradle +++ b/build.gradle @@ -79,14 +79,10 @@ sonarqube { properties { property "sonar.projectName", "resilience4j" property "sonar.projectKey", "resilience4j_resilience4j" - property "sonar.links.homepage","https://github.com/resilience4j/resilience4j" property "sonar.links.ci","https://travis-ci.org/resilience4j/resilience4j" property "sonar.links.scm","https://github.com/resilience4j/resilience4j" property "sonar.links.issue","https://github.com/resilience4j/resilience4j/issues" - -// property "sonar.jacoco.reportPaths","build/reports/jacoco/test" - property "sonar.language","java" } } @@ -108,8 +104,9 @@ subprojects { } } afterEvaluate { - // exclude subprojects that don't produce a jar file. - if(!project.name.equals('resilience4j-bom') && !project.name.equals('resilience4j-documentation') && !project.name.equals('resilience4j-test')) { + // exclude subprojects that don't produce a jar file or by design. + if(!project.name.equals('resilience4j-bom') && !project.name.equals('resilience4j-documentation') + && !project.name.equals('resilience4j-test') && !project.name.equals('resilience4j-spring-boot-common')) { jar { inputs.property('moduleName', moduleName) manifest.attributes( diff --git a/libraries.gradle b/libraries.gradle index c52580bde8..40b41a5564 100644 --- a/libraries.gradle +++ b/libraries.gradle @@ -55,7 +55,8 @@ ext { // Aspectj for Spring addon aspectj: "org.aspectj:aspectjrt:${aspectjVersion}", - + // spring test + spring_test: "org.springframework:spring-test:${springVersion}", // Spring Boot addon spring_core: "org.springframework:spring-core:${springVersion}", spring_context: "org.springframework:spring-context:${springVersion}", diff --git a/resilience4j-circuitbreaker/src/main/java/io/github/resilience4j/circuitbreaker/CircuitBreakerConfig.java b/resilience4j-circuitbreaker/src/main/java/io/github/resilience4j/circuitbreaker/CircuitBreakerConfig.java index 92d29b6923..31d4a7dc83 100644 --- a/resilience4j-circuitbreaker/src/main/java/io/github/resilience4j/circuitbreaker/CircuitBreakerConfig.java +++ b/resilience4j-circuitbreaker/src/main/java/io/github/resilience4j/circuitbreaker/CircuitBreakerConfig.java @@ -18,13 +18,13 @@ */ package io.github.resilience4j.circuitbreaker; -import io.github.resilience4j.core.lang.Nullable; - import java.time.Duration; import java.util.Arrays; import java.util.Optional; import java.util.function.Predicate; +import io.github.resilience4j.core.lang.Nullable; + /** * A {@link CircuitBreakerConfig} configures a {@link CircuitBreaker} @@ -44,8 +44,27 @@ public class CircuitBreakerConfig { // The default exception predicate counts all exceptions as failures. private Predicate recordFailurePredicate = DEFAULT_RECORD_FAILURE_PREDICATE; private boolean automaticTransitionFromOpenToHalfOpenEnabled = false; + private String configurationName; - private CircuitBreakerConfig(){ + private CircuitBreakerConfig() { + } + + /** + * Returns a builder to create a custom CircuitBreakerConfig. + * + * @return a {@link Builder} + */ + public static Builder custom() { + return new Builder(); + } + + /** + * Creates a default CircuitBreaker configuration. + * + * @return a default CircuitBreaker configuration. + */ + public static CircuitBreakerConfig ofDefaults() { + return new Builder().build(); } public float getFailureRateThreshold() { @@ -72,27 +91,16 @@ public boolean isAutomaticTransitionFromOpenToHalfOpenEnabled() { return automaticTransitionFromOpenToHalfOpenEnabled; } - /** - * Returns a builder to create a custom CircuitBreakerConfig. - * - * @return a {@link Builder} - */ - public static Builder custom(){ - return new Builder(); - } - - /** - * Creates a default CircuitBreaker configuration. - * - * @return a default CircuitBreaker configuration. - */ - public static CircuitBreakerConfig ofDefaults(){ - return new Builder().build(); + @Nullable + public String getConfigurationName() { + return configurationName; } public static class Builder { - @Nullable private Predicate recordFailurePredicate; - @Nullable private Predicate errorRecordingPredicate; + @Nullable + private Predicate recordFailurePredicate; + @Nullable + private Predicate errorRecordingPredicate; @SuppressWarnings("unchecked") private Class[] recordExceptions = new Class[0]; @SuppressWarnings("unchecked") @@ -102,10 +110,17 @@ public static class Builder { private int ringBufferSizeInClosedState = DEFAULT_RING_BUFFER_SIZE_IN_CLOSED_STATE; private Duration waitDurationInOpenState = Duration.ofSeconds(DEFAULT_WAIT_DURATION_IN_OPEN_STATE); private boolean automaticTransitionFromOpenToHalfOpenEnabled = false; + @Nullable + private String configurationName; + + static Predicate makePredicate(Class exClass) { + + return (Throwable e) -> exClass.isAssignableFrom(e.getClass()); + } /** * Configures the failure rate threshold in percentage above which the CircuitBreaker should trip open and start short-circuiting calls. - * + *

* The threshold must be greater than 0 and not greater than 100. Default value is 50 percentage. * * @param failureRateThreshold the failure rate threshold in percentage @@ -138,14 +153,14 @@ public Builder waitDurationInOpenState(Duration waitDurationInOpenState) { * Configures the size of the ring buffer when the CircuitBreaker is half open. The CircuitBreaker stores the success/failure success / failure status of the latest calls in a ring buffer. * For example, if {@code ringBufferSizeInClosedState} is 10, then at least 10 calls must be evaluated, before the failure rate can be calculated. * If only 9 calls have been evaluated the CircuitBreaker will not trip back to closed or open even if all 9 calls have failed. - * + *

* The size must be greater than 0. Default size is 10. * * @param ringBufferSizeInHalfOpenState the size of the ring buffer when the CircuitBreaker is is half open * @return the CircuitBreakerConfig.Builder */ public Builder ringBufferSizeInHalfOpenState(int ringBufferSizeInHalfOpenState) { - if (ringBufferSizeInHalfOpenState < 1 ) { + if (ringBufferSizeInHalfOpenState < 1) { throw new IllegalArgumentException("ringBufferSizeInHalfOpenState must be greater than 0"); } this.ringBufferSizeInHalfOpenState = ringBufferSizeInHalfOpenState; @@ -156,7 +171,7 @@ public Builder ringBufferSizeInHalfOpenState(int ringBufferSizeInHalfOpenState) * Configures the size of the ring buffer when the CircuitBreaker is closed. The CircuitBreaker stores the success/failure success / failure status of the latest calls in a ring buffer. * For example, if {@code ringBufferSizeInClosedState} is 100, then at least 100 calls must be evaluated, before the failure rate can be calculated. * If only 99 calls have been evaluated the CircuitBreaker will not trip open even if all 99 calls have failed. - * + *

* The size must be greater than 0. Default size is 100. * * @param ringBufferSizeInClosedState the size of the ring buffer when the CircuitBreaker is closed. @@ -185,18 +200,19 @@ public Builder recordFailure(Predicate predicate) { /** * Configures a list of error classes that are recorded as a failure and thus increase the failure rate. * Any exception matching or inheriting from one of the list should count as a failure, unless ignored via - * @see #ignoreExceptions(Class[]) ). Ignoring an exception has priority over recording an exception. - * - * Example: - * recordExceptions(Throwable.class) and ignoreExceptions(RuntimeException.class) - * would capture all Errors and checked Exceptions, and ignore unchecked - * - * For a more sophisticated exception management use the - * @see #recordFailure(Predicate) method * * @param errorClasses the error classes that are recorded * @return the CircuitBreakerConfig.Builder + * @see #ignoreExceptions(Class[]) ). Ignoring an exception has priority over recording an exception. + *

+ * Example: + * recordExceptions(Throwable.class) and ignoreExceptions(RuntimeException.class) + * would capture all Errors and checked Exceptions, and ignore unchecked + *

+ * For a more sophisticated exception management use the + * @see #recordFailure(Predicate) method */ + @SuppressWarnings("unchecked") @SafeVarargs public final Builder recordExceptions(Class... errorClasses) { this.recordExceptions = errorClasses != null ? errorClasses : new Class[0]; @@ -206,22 +222,23 @@ public final Builder recordExceptions(Class... errorClasses /** * Configures a list of error classes that are ignored as a failure and thus do not increase the failure rate. * Any exception matching or inheriting from one of the list will not count as a failure, even if marked via - * @see #recordExceptions(Class[]) . Ignoring an exception has priority over recording an exception. - * - * Example: - * ignoreExceptions(Throwable.class) and recordExceptions(Exception.class) - * would capture nothing - * - * Example: - * ignoreExceptions(Exception.class) and recordExceptions(Throwable.class) - * would capture Errors - * - * For a more sophisticated exception management use the - * @see #recordFailure(Predicate) method * * @param errorClasses the error classes that are recorded * @return the CircuitBreakerConfig.Builder + * @see #recordExceptions(Class[]) . Ignoring an exception has priority over recording an exception. + *

+ * Example: + * ignoreExceptions(Throwable.class) and recordExceptions(Exception.class) + * would capture nothing + *

+ * Example: + * ignoreExceptions(Exception.class) and recordExceptions(Throwable.class) + * would capture Errors + *

+ * For a more sophisticated exception management use the + * @see #recordFailure(Predicate) method */ + @SuppressWarnings("unchecked") @SafeVarargs public final Builder ignoreExceptions(Class... errorClasses) { this.ignoreExceptions = errorClasses != null ? errorClasses : new Class[0]; @@ -238,6 +255,17 @@ public Builder enableAutomaticTransitionFromOpenToHalfOpen() { return this; } + /** + * A name for referencing the configuration. This is not required, but can be used for referencing configurations if set. + * + * @param configurationName A name for referencing the configuration. + * @return The CircuitBreakerConfig.Builder + */ + public Builder configurationName(String configurationName) { + this.configurationName = configurationName; + return this; + } + /** * Builds a CircuitBreakerConfig * @@ -250,10 +278,11 @@ public CircuitBreakerConfig build() { config.failureRateThreshold = failureRateThreshold; config.ringBufferSizeInClosedState = ringBufferSizeInClosedState; config.ringBufferSizeInHalfOpenState = ringBufferSizeInHalfOpenState; - if(errorRecordingPredicate != null) { + if (errorRecordingPredicate != null) { config.recordFailurePredicate = errorRecordingPredicate; } config.automaticTransitionFromOpenToHalfOpenEnabled = automaticTransitionFromOpenToHalfOpenEnabled; + config.configurationName = configurationName; return config; } @@ -284,10 +313,5 @@ private Optional> buildIgnoreExceptionsPredicate() { .reduce(Predicate::or) .map(Predicate::negate); } - - static Predicate makePredicate(Class exClass) { - - return (Throwable e) -> exClass.isAssignableFrom(e.getClass()); - } } } diff --git a/resilience4j-circuitbreaker/src/main/java/io/github/resilience4j/circuitbreaker/CircuitBreakerRegistry.java b/resilience4j-circuitbreaker/src/main/java/io/github/resilience4j/circuitbreaker/CircuitBreakerRegistry.java index 7cfc7b2bc1..03b7926228 100644 --- a/resilience4j-circuitbreaker/src/main/java/io/github/resilience4j/circuitbreaker/CircuitBreakerRegistry.java +++ b/resilience4j-circuitbreaker/src/main/java/io/github/resilience4j/circuitbreaker/CircuitBreakerRegistry.java @@ -19,65 +19,66 @@ package io.github.resilience4j.circuitbreaker; +import java.util.function.Supplier; + import io.github.resilience4j.circuitbreaker.internal.InMemoryCircuitBreakerRegistry; +import io.github.resilience4j.core.Registry; import io.vavr.collection.Seq; -import java.util.function.Supplier; - /** * The {@link CircuitBreakerRegistry} is a factory to create CircuitBreaker instances which stores all CircuitBreaker instances in a registry. */ -public interface CircuitBreakerRegistry { +public interface CircuitBreakerRegistry extends Registry { + /** + * Returns all managed {@link CircuitBreaker} instances. + * + * @return all managed {@link CircuitBreaker} instances. + */ + Seq getAllCircuitBreakers(); - /** - * Returns all managed {@link CircuitBreaker} instances. - * - * @return all managed {@link CircuitBreaker} instances. - */ - Seq getAllCircuitBreakers(); + /** + * Returns a managed {@link CircuitBreaker} or creates a new one with the default CircuitBreaker configuration. + * + * @param name the name of the CircuitBreaker + * @return The {@link CircuitBreaker} + */ + CircuitBreaker circuitBreaker(String name); - /** - * Returns a managed {@link CircuitBreaker} or creates a new one with the default CircuitBreaker configuration. - * - * @param name the name of the CircuitBreaker - * @return The {@link CircuitBreaker} - */ - CircuitBreaker circuitBreaker(String name); + /** + * Returns a managed {@link CircuitBreaker} or creates a new one with a custom CircuitBreaker configuration. + * + * @param name the name of the CircuitBreaker + * @param circuitBreakerConfig a custom CircuitBreaker configuration + * @return The {@link CircuitBreaker} + */ + CircuitBreaker circuitBreaker(String name, CircuitBreakerConfig circuitBreakerConfig); - /** - * Returns a managed {@link CircuitBreaker} or creates a new one with a custom CircuitBreaker configuration. - * - * @param name the name of the CircuitBreaker - * @param circuitBreakerConfig a custom CircuitBreaker configuration - * @return The {@link CircuitBreaker} - */ - CircuitBreaker circuitBreaker(String name, CircuitBreakerConfig circuitBreakerConfig); + /** + * Returns a managed {@link CircuitBreaker} or creates a new one with a custom CircuitBreaker configuration. + * + * @param name the name of the CircuitBreaker + * @param circuitBreakerConfigSupplier a supplier of a custom CircuitBreaker configuration + * @return The {@link CircuitBreaker} + */ + CircuitBreaker circuitBreaker(String name, Supplier circuitBreakerConfigSupplier); - /** - * Returns a managed {@link CircuitBreaker} or creates a new one with a custom CircuitBreaker configuration. - * - * @param name the name of the CircuitBreaker - * @param circuitBreakerConfigSupplier a supplier of a custom CircuitBreaker configuration - * @return The {@link CircuitBreaker} - */ - CircuitBreaker circuitBreaker(String name, Supplier circuitBreakerConfigSupplier); + /** + * Creates a CircuitBreakerRegistry with a custom CircuitBreaker configuration. + * + * @param circuitBreakerConfig a custom CircuitBreaker configuration + * @return a CircuitBreakerRegistry with a custom CircuitBreaker configuration. + */ + static CircuitBreakerRegistry of(CircuitBreakerConfig circuitBreakerConfig) { + return new InMemoryCircuitBreakerRegistry(circuitBreakerConfig); + } - /** - * Creates a CircuitBreakerRegistry with a custom CircuitBreaker configuration. - * - * @param circuitBreakerConfig a custom CircuitBreaker configuration - * @return a CircuitBreakerRegistry with a custom CircuitBreaker configuration. - */ - static CircuitBreakerRegistry of(CircuitBreakerConfig circuitBreakerConfig){ - return new InMemoryCircuitBreakerRegistry(circuitBreakerConfig); - } + /** + * Creates a CircuitBreakerRegistry with a default CircuitBreaker configuration. + * + * @return a CircuitBreakerRegistry with a default CircuitBreaker configuration. + */ + static CircuitBreakerRegistry ofDefaults() { + return new InMemoryCircuitBreakerRegistry(); + } - /** - * Creates a CircuitBreakerRegistry with a default CircuitBreaker configuration. - * - * @return a CircuitBreakerRegistry with a default CircuitBreaker configuration. - */ - static CircuitBreakerRegistry ofDefaults(){ - return new InMemoryCircuitBreakerRegistry(); - } } diff --git a/resilience4j-circuitbreaker/src/main/java/io/github/resilience4j/circuitbreaker/internal/InMemoryCircuitBreakerRegistry.java b/resilience4j-circuitbreaker/src/main/java/io/github/resilience4j/circuitbreaker/internal/InMemoryCircuitBreakerRegistry.java index 194078b7ae..b775b2aedf 100644 --- a/resilience4j-circuitbreaker/src/main/java/io/github/resilience4j/circuitbreaker/internal/InMemoryCircuitBreakerRegistry.java +++ b/resilience4j-circuitbreaker/src/main/java/io/github/resilience4j/circuitbreaker/internal/InMemoryCircuitBreakerRegistry.java @@ -18,74 +18,92 @@ */ package io.github.resilience4j.circuitbreaker.internal; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.function.Supplier; + import io.github.resilience4j.circuitbreaker.CircuitBreaker; import io.github.resilience4j.circuitbreaker.CircuitBreakerConfig; import io.github.resilience4j.circuitbreaker.CircuitBreakerRegistry; +import io.github.resilience4j.core.AbstractRegistry; import io.vavr.collection.Array; import io.vavr.collection.Seq; -import java.util.Objects; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentMap; -import java.util.function.Supplier; - /** * Backend circuitBreaker manager. * Constructs backend circuitBreakers according to configuration values. */ -public final class InMemoryCircuitBreakerRegistry implements CircuitBreakerRegistry { +public final class InMemoryCircuitBreakerRegistry extends AbstractRegistry implements CircuitBreakerRegistry { + + private static final String NAME_MUST_NOT_BE_NULL = "Name must not be null"; + private final CircuitBreakerConfig defaultCircuitBreakerConfig; + /** + * The circuitBreakers, indexed by name of the backend. + */ + private final ConcurrentMap circuitBreakers; + + + /** + * The constructor with default circuitBreaker properties. + */ + public InMemoryCircuitBreakerRegistry() { + this(CircuitBreakerConfig.ofDefaults()); + } + + public InMemoryCircuitBreakerRegistry(Map configs) { + this(configs.getOrDefault(DEFAULT_CONFIG, CircuitBreakerConfig.ofDefaults())); + this.configurations.putAll(configs); + } - private final CircuitBreakerConfig defaultCircuitBreakerConfig; + /** + * The constructor with custom default circuitBreaker properties. + * + * @param defaultCircuitBreakerConfig The BackendMonitor service properties. + */ + public InMemoryCircuitBreakerRegistry(CircuitBreakerConfig defaultCircuitBreakerConfig) { + super(); + this.defaultCircuitBreakerConfig = Objects.requireNonNull(defaultCircuitBreakerConfig, "CircuitBreakerConfig must not be null"); + this.circuitBreakers = new ConcurrentHashMap<>(); + this.configurations.put(DEFAULT_CONFIG, defaultCircuitBreakerConfig); + } - /** - * The circuitBreakers, indexed by name of the backend. - */ - private final ConcurrentMap circuitBreakers; + @Override + public Seq getAllCircuitBreakers() { + return Array.ofAll(circuitBreakers.values()); + } - /** - * The constructor with default circuitBreaker properties. - */ - public InMemoryCircuitBreakerRegistry() { - this.defaultCircuitBreakerConfig = CircuitBreakerConfig.ofDefaults(); - this.circuitBreakers = new ConcurrentHashMap<>(); - } + /** + * {@inheritDoc} + */ + @Override + public CircuitBreaker circuitBreaker(String name) { + return circuitBreakers.computeIfAbsent(Objects.requireNonNull(name, NAME_MUST_NOT_BE_NULL), + k -> notifyPostCreationConsumers(CircuitBreaker.of(name, defaultCircuitBreakerConfig))); + } - /** - * The constructor with custom default circuitBreaker properties. - * - * @param defaultCircuitBreakerConfig The BackendMonitor service properties. - */ - public InMemoryCircuitBreakerRegistry(CircuitBreakerConfig defaultCircuitBreakerConfig) { - this.defaultCircuitBreakerConfig = Objects.requireNonNull(defaultCircuitBreakerConfig, "CircuitBreakerConfig must not be null"); - this.circuitBreakers = new ConcurrentHashMap<>(); - } + /** + * {@inheritDoc} + */ + @Override + public CircuitBreaker circuitBreaker(String name, CircuitBreakerConfig customCircuitBreakerConfig) { + return circuitBreakers.computeIfAbsent(Objects.requireNonNull(name, NAME_MUST_NOT_BE_NULL), + k -> notifyPostCreationConsumers(CircuitBreaker.of(name, customCircuitBreakerConfig))); + } - @Override - public Seq getAllCircuitBreakers() { - return Array.ofAll(circuitBreakers.values()); - } + /** + * {@inheritDoc} + */ + @Override + public CircuitBreaker circuitBreaker(String name, Supplier circuitBreakerConfigSupplier) { + return circuitBreakers.computeIfAbsent(Objects.requireNonNull(name, NAME_MUST_NOT_BE_NULL), + k -> { + CircuitBreakerConfig config = circuitBreakerConfigSupplier.get(); + return notifyPostCreationConsumers(CircuitBreaker.of(name, config)); + }); + } - /** - * {@inheritDoc} - */ - @Override - public CircuitBreaker circuitBreaker(String name) { - return circuitBreakers.computeIfAbsent(Objects.requireNonNull(name, "Name must not be null"), (k) -> CircuitBreaker.of(name, - defaultCircuitBreakerConfig)); - } - /** - * {@inheritDoc} - */ - @Override - public CircuitBreaker circuitBreaker(String name, CircuitBreakerConfig customCircuitBreakerConfig) { - return circuitBreakers.computeIfAbsent(Objects.requireNonNull(name, "Name must not be null"), (k) -> CircuitBreaker.of(name, - customCircuitBreakerConfig)); - } - @Override - public CircuitBreaker circuitBreaker(String name, Supplier circuitBreakerConfigSupplier) { - return circuitBreakers.computeIfAbsent(Objects.requireNonNull(name, "Name must not be null"), (k) -> CircuitBreaker.of(name, - circuitBreakerConfigSupplier.get())); - } } diff --git a/resilience4j-circuitbreaker/src/test/java/io/github/resilience4j/circuitbreaker/CircuitBreakerConfigTest.java b/resilience4j-circuitbreaker/src/test/java/io/github/resilience4j/circuitbreaker/CircuitBreakerConfigTest.java index fe767b074f..d8040be270 100644 --- a/resilience4j-circuitbreaker/src/test/java/io/github/resilience4j/circuitbreaker/CircuitBreakerConfigTest.java +++ b/resilience4j-circuitbreaker/src/test/java/io/github/resilience4j/circuitbreaker/CircuitBreakerConfigTest.java @@ -18,13 +18,13 @@ */ package io.github.resilience4j.circuitbreaker; -import org.junit.Test; +import static org.assertj.core.api.BDDAssertions.then; +import static org.assertj.core.api.Java6Assertions.assertThat; import java.time.Duration; import java.util.function.Predicate; -import static org.assertj.core.api.BDDAssertions.then; -import static org.assertj.core.api.Java6Assertions.assertThat; +import org.junit.Test; public class CircuitBreakerConfigTest { @@ -60,7 +60,7 @@ public void failureRateThresholdAboveHundredShouldFail() { CircuitBreakerConfig.custom().failureRateThreshold(101).build(); } - @Test() + @Test public void shouldSetDefaultSettings() { CircuitBreakerConfig circuitBreakerConfig = CircuitBreakerConfig.ofDefaults(); then(circuitBreakerConfig.getFailureRateThreshold()).isEqualTo(CircuitBreakerConfig.DEFAULT_MAX_FAILURE_THRESHOLD); @@ -70,37 +70,37 @@ public void shouldSetDefaultSettings() { then(circuitBreakerConfig.getRecordFailurePredicate()).isNotNull(); } - @Test() + @Test public void shouldSetFailureRateThreshold() { CircuitBreakerConfig circuitBreakerConfig = CircuitBreakerConfig.custom().failureRateThreshold(25).build(); then(circuitBreakerConfig.getFailureRateThreshold()).isEqualTo(25); } - @Test() + @Test public void shouldSetLowFailureRateThreshold() { CircuitBreakerConfig circuitBreakerConfig = CircuitBreakerConfig.custom().failureRateThreshold(0.001f).build(); then(circuitBreakerConfig.getFailureRateThreshold()).isEqualTo(0.001f); } - @Test() + @Test public void shouldSetRingBufferSizeInClosedState() { CircuitBreakerConfig circuitBreakerConfig = CircuitBreakerConfig.custom().ringBufferSizeInClosedState(1000).build(); then(circuitBreakerConfig.getRingBufferSizeInClosedState()).isEqualTo(1000); } - @Test() + @Test public void shouldSetRingBufferSizeInHalfOpenState() { CircuitBreakerConfig circuitBreakerConfig = CircuitBreakerConfig.custom().ringBufferSizeInHalfOpenState(100).build(); then(circuitBreakerConfig.getRingBufferSizeInHalfOpenState()).isEqualTo(100); } - @Test() + @Test public void shouldSetWaitInterval() { CircuitBreakerConfig circuitBreakerConfig = CircuitBreakerConfig.custom().waitDurationInOpenState(Duration.ofSeconds(1)).build(); then(circuitBreakerConfig.getWaitDurationInOpenState().getSeconds()).isEqualTo(1); } - @Test() + @Test public void shouldUseRecordFailureThrowablePredicate() { CircuitBreakerConfig circuitBreakerConfig = CircuitBreakerConfig.custom() .recordFailure(TEST_PREDICATE).build(); @@ -120,7 +120,7 @@ private static class ExtendsExtendsException extends ExtendsException {} private static class ExtendsException2 extends Exception {} private static class ExtendsError extends Error {} - @Test() + @Test public void shouldUseIgnoreExceptionToBuildPredicate() { CircuitBreakerConfig circuitBreakerConfig = CircuitBreakerConfig.custom() .ignoreExceptions(RuntimeException.class, ExtendsExtendsException.class).build(); @@ -134,7 +134,7 @@ public void shouldUseIgnoreExceptionToBuildPredicate() { then(failurePredicate.test(new ExtendsExtendsException())).isEqualTo(false); // explicitly excluded } - @Test() + @Test public void shouldUseRecordExceptionToBuildPredicate() { CircuitBreakerConfig circuitBreakerConfig = CircuitBreakerConfig.custom() .recordExceptions(RuntimeException.class, ExtendsExtendsException.class).build(); @@ -148,7 +148,7 @@ public void shouldUseRecordExceptionToBuildPredicate() { then(failurePredicate.test(new ExtendsExtendsException())).isEqualTo(true); // explicitly included } - @Test() + @Test public void shouldUseIgnoreExceptionOverRecordToBuildPredicate() { CircuitBreakerConfig circuitBreakerConfig = CircuitBreakerConfig.custom() .recordExceptions(RuntimeException.class, ExtendsExtendsException.class) @@ -164,7 +164,24 @@ public void shouldUseIgnoreExceptionOverRecordToBuildPredicate() { then(failurePredicate.test(new ExtendsExtendsException())).isEqualTo(false); // inherits excluded from ExtendsException } - @Test() + @Test + public void shouldUseSharedConfiguration() { + CircuitBreakerConfig circuitBreakerConfig = CircuitBreakerConfig.custom().configurationName("sharedConfig") + .recordExceptions(RuntimeException.class, ExtendsExtendsException.class) + .ignoreExceptions(ExtendsException.class, ExtendsRuntimeException.class) + .build(); + final Predicate failurePredicate = circuitBreakerConfig.getRecordFailurePredicate(); + then(failurePredicate.test(new Exception())).isEqualTo(false); // not explicitly included + then(failurePredicate.test(new ExtendsError())).isEqualTo(false); // not explicitly included + then(failurePredicate.test(new ExtendsException())).isEqualTo(false); // explicitly excluded + then(failurePredicate.test(new ExtendsException2())).isEqualTo(false); // not explicitly included + then(failurePredicate.test(new RuntimeException())).isEqualTo(true); // explicitly included + then(failurePredicate.test(new ExtendsRuntimeException())).isEqualTo(false); // explicitly excluded + then(failurePredicate.test(new ExtendsExtendsException())).isEqualTo(false); // inherits excluded from ExtendsException + then(circuitBreakerConfig.getConfigurationName()).isEqualTo("sharedConfig"); + } + + @Test public void shouldUseBothRecordToBuildPredicate() { CircuitBreakerConfig circuitBreakerConfig = CircuitBreakerConfig.custom() .recordFailure(TEST_PREDICATE) //1 @@ -183,7 +200,7 @@ public void shouldUseBothRecordToBuildPredicate() { then(failurePredicate.test(new ExtendsExtendsException())).isEqualTo(false); // inherits excluded from ExtendsException by 3 } - @Test() + @Test public void builderMakePredicateShouldBuildPredicateAcceptingChildClass() { final Predicate predicate = CircuitBreakerConfig.Builder.makePredicate(RuntimeException.class); then(predicate.test(new RuntimeException())).isEqualTo(true); @@ -197,7 +214,7 @@ public void builderMakePredicateShouldBuildPredicateAcceptingChildClass() { } - @Test() + @Test public void shouldBuilderCreateConfigEveryTime() { final CircuitBreakerConfig.Builder builder = CircuitBreakerConfig.custom(); builder.ringBufferSizeInClosedState(5); diff --git a/resilience4j-circuitbreaker/src/test/java/io/github/resilience4j/circuitbreaker/CircuitBreakerRegistryTest.java b/resilience4j-circuitbreaker/src/test/java/io/github/resilience4j/circuitbreaker/CircuitBreakerRegistryTest.java index d74ec98533..5a22d07b91 100644 --- a/resilience4j-circuitbreaker/src/test/java/io/github/resilience4j/circuitbreaker/CircuitBreakerRegistryTest.java +++ b/resilience4j-circuitbreaker/src/test/java/io/github/resilience4j/circuitbreaker/CircuitBreakerRegistryTest.java @@ -18,11 +18,11 @@ */ package io.github.resilience4j.circuitbreaker; +import static org.assertj.core.api.BDDAssertions.assertThat; + import org.junit.Before; import org.junit.Test; -import static org.assertj.core.api.BDDAssertions.assertThat; - public class CircuitBreakerRegistryTest { @@ -56,4 +56,15 @@ public void shouldBeNotTheSameCircuitBreaker() { assertThat(circuitBreakerRegistry.getAllCircuitBreakers()).hasSize(2); } + + + @Test + public void testCreateWithDefaultConfiguration() { + CircuitBreakerRegistry circuitBreakerRegistry = CircuitBreakerRegistry.of(CircuitBreakerConfig.ofDefaults()); + CircuitBreaker circuitBreaker = circuitBreakerRegistry.circuitBreaker("testName"); + CircuitBreaker circuitBreaker2 = circuitBreakerRegistry.circuitBreaker("otherTestName"); + assertThat(circuitBreaker).isNotSameAs(circuitBreaker2); + + assertThat(circuitBreakerRegistry.getAllCircuitBreakers()).hasSize(2); + } } diff --git a/resilience4j-circuitbreaker/src/test/java/io/github/resilience4j/circuitbreaker/internal/InMemoryCircuitBreakerRegistryTest.java b/resilience4j-circuitbreaker/src/test/java/io/github/resilience4j/circuitbreaker/internal/InMemoryCircuitBreakerRegistryTest.java new file mode 100644 index 0000000000..6d1798c888 --- /dev/null +++ b/resilience4j-circuitbreaker/src/test/java/io/github/resilience4j/circuitbreaker/internal/InMemoryCircuitBreakerRegistryTest.java @@ -0,0 +1,94 @@ +package io.github.resilience4j.circuitbreaker.internal; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; +import static org.mockito.BDDMockito.then; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; + +import java.util.HashMap; +import java.util.Map; +import java.util.function.Consumer; + +import org.assertj.core.api.Assertions; +import org.junit.Before; +import org.junit.Test; +import org.slf4j.Logger; + +import io.github.resilience4j.circuitbreaker.CircuitBreaker; +import io.github.resilience4j.circuitbreaker.CircuitBreakerConfig; +import io.github.resilience4j.circuitbreaker.CircuitBreakerRegistry; + + +public class InMemoryCircuitBreakerRegistryTest { + + private Logger LOGGER; + + @Before + public void setUp() { + LOGGER = mock(Logger.class); + } + + @Test + public void testPostConsumerBeingCalled() { + CircuitBreakerRegistry circuitBreakerRegistry = CircuitBreakerRegistry.ofDefaults(); + Consumer consumer1 = circuitBreaker -> LOGGER.info("invoking the post consumer1"); + Consumer consumer2 = circuitBreaker -> LOGGER.info("invoking the post consumer2"); + + circuitBreakerRegistry.registerPostCreationConsumer(consumer1); + + circuitBreakerRegistry.circuitBreaker("testCircuitBreaker"); + circuitBreakerRegistry.circuitBreaker("testCircuitBreaker2", CircuitBreakerConfig.ofDefaults()); + circuitBreakerRegistry.circuitBreaker("testCircuitBreaker3", CircuitBreakerConfig::ofDefaults); + + then(LOGGER).should(times(3)).info("invoking the post consumer1"); + + circuitBreakerRegistry.registerPostCreationConsumer(consumer2); + circuitBreakerRegistry.unregisterPostCreationConsumer(consumer1); + circuitBreakerRegistry.circuitBreaker("testCircuitBreaker4"); + then(LOGGER).should(times(1)).info("invoking the post consumer2"); + } + + @Test + public void testAddCircuitBreakerRegistry() { + CircuitBreakerRegistry circuitBreakerRegistry = CircuitBreakerRegistry.ofDefaults(); + circuitBreakerRegistry.addConfiguration("testConfig", CircuitBreakerConfig.ofDefaults()); + assertThat(circuitBreakerRegistry.getConfiguration("testConfig")).isNotNull(); + } + + @Test + public void testGetNotFoundCircuitBreakerRegistry() { + InMemoryCircuitBreakerRegistry circuitBreakerRegistry = (InMemoryCircuitBreakerRegistry) CircuitBreakerRegistry.ofDefaults(); + assertThat(circuitBreakerRegistry.getConfiguration("testNotFound")).isEmpty(); + } + + @Test + public void testUpdateDefaultCircuitBreakerRegistry() { + CircuitBreakerRegistry circuitBreakerRegistry = CircuitBreakerRegistry.ofDefaults(); + Assertions.assertThatThrownBy(() -> circuitBreakerRegistry.addConfiguration("default", CircuitBreakerConfig.custom().build())) + .isExactlyInstanceOf(IllegalArgumentException.class).hasMessageStartingWith("you can not use 'default'"); + + } + + @Test + public void testCreateCircuitBreakerWithSharedConfiguration() { + CircuitBreakerRegistry circuitBreakerRegistry = CircuitBreakerRegistry.ofDefaults(); + circuitBreakerRegistry.addConfiguration("testConfig", CircuitBreakerConfig.ofDefaults()); + final CircuitBreaker circuitBreaker = circuitBreakerRegistry.circuitBreaker("circuitBreaker", + circuitBreakerRegistry.getConfiguration("testConfig").get()); + assertThat(circuitBreaker).isNotNull(); + } + + + @Test + public void testCreateCircuitBreakerWitMapConstructor() { + Map map = new HashMap<>(); + map.put("testBreaker", CircuitBreakerConfig.ofDefaults()); + CircuitBreakerRegistry circuitBreakerRegistry = new InMemoryCircuitBreakerRegistry(map); + circuitBreakerRegistry.addConfiguration("testConfig", CircuitBreakerConfig.ofDefaults()); + final CircuitBreaker circuitBreaker = circuitBreakerRegistry.circuitBreaker("circuitBreaker", + circuitBreakerRegistry.getConfiguration("testConfig").get()); + assertThat(circuitBreaker).isNotNull(); + } + + +} \ No newline at end of file diff --git a/resilience4j-core/src/main/java/io/github/resilience4j/core/AbstractRegistry.java b/resilience4j-core/src/main/java/io/github/resilience4j/core/AbstractRegistry.java new file mode 100644 index 0000000000..b02db0a85c --- /dev/null +++ b/resilience4j-core/src/main/java/io/github/resilience4j/core/AbstractRegistry.java @@ -0,0 +1,78 @@ +/* + * + * Copyright 2019 Mahmoud Romeh + * + * 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 + * + * http://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 io.github.resilience4j.core; + +import java.util.List; +import java.util.Optional; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.function.Consumer; + +/** + * Abstract registry to be shared with all resilience4j registries + */ +public class AbstractRegistry implements Registry { + protected static final String DEFAULT_CONFIG = "default"; + /** + * The list of consumer functions to execute after a target is created. + */ + protected final List> postCreationConsumers; + + /** + * The map of shared configuration by name + */ + protected final ConcurrentMap configurations; + + public AbstractRegistry() { + this.postCreationConsumers = new CopyOnWriteArrayList<>(); + this.configurations = new ConcurrentHashMap<>(); + } + + @Override + public void addConfiguration(String configName, Config configuration) { + if (configName.equals(DEFAULT_CONFIG)) { + throw new IllegalArgumentException("you can not use 'default' as a configuration name as it is preserved for default configuration"); + } + this.configurations.put(configName, configuration); + } + + @Override + public Optional getConfiguration(String configName) { + return Optional.ofNullable(this.configurations.get(configName)); + } + + @Override + public void registerPostCreationConsumer(Consumer postCreationConsumer) { + postCreationConsumers.add(postCreationConsumer); + } + + @Override + public void unregisterPostCreationConsumer(Consumer postCreationConsumer) { + postCreationConsumers.remove(postCreationConsumer); + } + + protected Target notifyPostCreationConsumers(Target target) { + if (!postCreationConsumers.isEmpty()) { + postCreationConsumers.forEach(consumer -> consumer.accept(target)); + } + return target; + } + +} diff --git a/resilience4j-core/src/main/java/io/github/resilience4j/core/Registry.java b/resilience4j-core/src/main/java/io/github/resilience4j/core/Registry.java new file mode 100644 index 0000000000..c90b48277a --- /dev/null +++ b/resilience4j-core/src/main/java/io/github/resilience4j/core/Registry.java @@ -0,0 +1,59 @@ +/* + * + * Copyright 2019 Mahmoud romeh + * + * 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 + * + * http://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 io.github.resilience4j.core; + +import java.util.Optional; +import java.util.function.Consumer; + +/** + * root resilience4j registry to be used by resilience types registries for common functionality + */ +public interface Registry { + + /** + * @param configName the configuration name + * @param configuration the added configuration + */ + void addConfiguration(String configName, Config configuration); + + + /** + * @param configName the configuration name + * @return the found configuration if any + */ + Optional getConfiguration(String configName); + + + /** + * Allows for configuring some functionality to be executed when a new target is created. + * + * @param postCreationConsumer A consumer function to execute for a target that was created. + */ + void registerPostCreationConsumer(Consumer postCreationConsumer); + + + /** + * Allows for configuring some functionality to be executed when a new target is created. + * + * @param postCreationConsumer A consumer function to execute for a target that was created. + */ + void unregisterPostCreationConsumer(Consumer postCreationConsumer); + + +} diff --git a/resilience4j-core/src/test/java/io/github/resilience4j/core/AbstractRegistryTest.java b/resilience4j-core/src/test/java/io/github/resilience4j/core/AbstractRegistryTest.java new file mode 100644 index 0000000000..532820e2d1 --- /dev/null +++ b/resilience4j-core/src/test/java/io/github/resilience4j/core/AbstractRegistryTest.java @@ -0,0 +1,41 @@ +package io.github.resilience4j.core; + +import static org.junit.Assert.assertEquals; +import static org.mockito.BDDMockito.then; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; + +import java.util.function.Consumer; + +import org.junit.Before; +import org.junit.Test; +import org.slf4j.Logger; + + +public class AbstractRegistryTest { + + private Logger LOGGER; + + @Before + public void setUp() { + LOGGER = mock(Logger.class); + } + + @Test + public void testAbstractRegistryActions() { + Consumer consumer1 = circuitBreaker -> LOGGER.info("invoking the post consumer1"); + TestRegistry testRegistry = new TestRegistry(); + testRegistry.registerPostCreationConsumer(consumer1); + testRegistry.addConfiguration("test", "test"); + assertEquals(testRegistry.getConfiguration("test").get(), "test"); + testRegistry.notifyPostCreationConsumers("test"); + then(LOGGER).should(times(1)).info("invoking the post consumer1"); + testRegistry.unregisterPostCreationConsumer(consumer1); + testRegistry.notifyPostCreationConsumers("test"); + then(LOGGER).shouldHaveZeroInteractions(); + } + + class TestRegistry extends AbstractRegistry { + } + +} \ No newline at end of file diff --git a/resilience4j-documentation/src/docs/asciidoc/addon_guides/springboot.adoc b/resilience4j-documentation/src/docs/asciidoc/addon_guides/springboot.adoc index 2e71058157..27e4b3be2d 100644 --- a/resilience4j-documentation/src/docs/asciidoc/addon_guides/springboot.adoc +++ b/resilience4j-documentation/src/docs/asciidoc/addon_guides/springboot.adoc @@ -237,6 +237,18 @@ public Flowable doSomethingFlowable(boolean throwException) { } ---- + +Beyond the config file configuration, the Spring Bean configuration is now using @ConditionalOnMissingBean to allow for overriding default behavior. +These Beans can be overridden in your application's Spring Bean configuration: + +* RetryRegistry +* RetryAspect +* RxJava2RetryAspectExt +* ReactorRetryAspectExt + +Note that in Spring Boot 1.x, EventConsumerRegistry cannot be overridden because of limitations of the @ConditionalOnMissingBean implementation. +To override this, go to at least Spring Boot 2.1.x. + ===== CircuitBreaker You can configure your CircuitBreakers in Spring Boot's `application.yml` config file. For example @@ -271,6 +283,38 @@ resilience4j.circuitbreaker: - org.springframework.web.client.HttpClientErrorException ---- +You can also override/partial-override/share default configuration for your CircuitBreakers in Spring Boot's `application.yml` config file. +For example + +[source,yaml] +---- +resilience4j.circuitbreaker: + configs: + default: + ringBufferSizeInClosedState: 100 + ringBufferSizeInHalfOpenState: 10 + waitInterval: 10000 + failureRateThreshold: 60 + eventConsumerBufferSize: 10 + registerHealthIndicator: true + backends: + backendA: + baseConfig: default + backendB: + baseConfig: default +---- + +Beyond the config file configuration, the Spring Bean configuration is now using @ConditionalOnMissingBean to allow for overriding default behavior. +These Beans can be overridden in your application's Spring Bean configuration: + +* CircuitBreakerRegistry +* CircuitBreakerAspect +* RxJava2CircuitBreakerAspectExt +* ReactorCircuitBreakerAspectExt + +Note that in Spring Boot 1.x, EventConsumerRegistry cannot be overridden because of limitations of the @ConditionalOnMissingBean implementation. +To override this, go to at least Spring Boot 2.1.x. + ===== RateLimiter You can configure your CircuitBreakers in Spring Boot's `application.yml` config file. For example @@ -292,6 +336,15 @@ resilience4j.ratelimiter: timeoutInMillis: 3000 ---- +Beyond the config file configuration, the Spring Bean configuration is now using @ConditionalOnMissingBean to allow for overriding default behavior. +These Beans can be overridden in your application's Spring Bean configuration: + +* RateLimiterRegistry +* RateLimiterAspect + +Note that in Spring Boot 1.x, EventConsumerRegistry cannot be overridden because of limitations of the @ConditionalOnMissingBean implementation. +To override this, go to at least Spring Boot 2.1.x. + ===== Explicit ordering for CircuitBreaker and RateLimiter aspects You can adjust `RateLimiterProperties.rateLimiterAspectOrder` and `CircuitBreakerProperties.circuitBreakerAspectOrder` and explicitly define `CircuitBreaker` and `RateLimiter` execution sequence. diff --git a/resilience4j-documentation/src/docs/asciidoc/addon_guides/springboot2.adoc b/resilience4j-documentation/src/docs/asciidoc/addon_guides/springboot2.adoc index d238d8b0a3..395c5f254c 100644 --- a/resilience4j-documentation/src/docs/asciidoc/addon_guides/springboot2.adoc +++ b/resilience4j-documentation/src/docs/asciidoc/addon_guides/springboot2.adoc @@ -237,6 +237,18 @@ public Flowable doSomethingFlowable(boolean throwException) { } ---- +Beyond the config file configuration, the Spring Bean configuration is now using @ConditionalOnMissingBean to allow for overriding default behavior. +These Beans can be overridden in your application's Spring Bean configuration: + +* RetryRegistry +* RetryAspect +* EventConsumerRegistry +* RxJava2RetryAspectExt +* ReactorRetryAspectExt + +Note that in Spring Boot 1.x, EventConsumerRegistry cannot be overridden because of limitations of the @ConditionalOnMissingBean implementation. +To override this, go to at least Spring Boot 2.1.x. + ===== CircuitBreaker You can configure your CircuitBreakers in Spring Boot's `application.yml` config file. For example @@ -271,6 +283,36 @@ resilience4j.circuitbreaker: - org.springframework.web.client.HttpClientErrorException ---- +You can also override/partial-override/share default configuration for your CircuitBreakers in Spring Boot's `application.yml` config file. +For example + +[source,yaml] +---- +resilience4j.circuitbreaker: + configs: + default: + ringBufferSizeInClosedState: 100 + ringBufferSizeInHalfOpenState: 10 + waitInterval: 10000 + failureRateThreshold: 60 + eventConsumerBufferSize: 10 + registerHealthIndicator: true + backends: + backendA: + baseConfig: default + backendB: + baseConfig: default +---- + +Beyond the config file configuration, the Spring Bean configuration is now using @ConditionalOnMissingBean to allow for overriding default behavior. +These Beans can be overridden in your application's Spring Bean configuration: + +* CircuitBreakerRegistry +* CircuitBreakerAspect +* RxJava2CircuitBreakerAspectExt +* ReactorCircuitBreakerAspectExt +* EventConsumerRegistry + ===== RateLimiter You can configure your CircuitBreakers in Spring Boot's `application.yml` config file. For example @@ -292,6 +334,16 @@ resilience4j.ratelimiter: timeoutInMillis: 3000 ---- +Beyond the config file configuration, the Spring Bean configuration is now using @ConditionalOnMissingBean to allow for overriding default behavior. +These Beans can be overridden in your application's Spring Bean configuration: + +* RateLimiterRegistry +* RateLimiterAspect +* EventConsumerRegistry +* RxJava2RateLimiterAspectExt +* ReactorRateLimiterAspectExt + + ===== Explicit ordering for CircuitBreaker and RateLimiter aspects You can adjust `RateLimiterProperties.rateLimiterAspectOrder` and `CircuitBreakerProperties.circuitBreakerAspectOrder` and explicitly define `CircuitBreaker` and `RateLimiter` execution sequence. diff --git a/resilience4j-micrometer/src/main/java/io/github/resilience4j/micrometer/BulkheadMetrics.java b/resilience4j-micrometer/src/main/java/io/github/resilience4j/micrometer/BulkheadMetrics.java index 1090092b19..b5068376f8 100644 --- a/resilience4j-micrometer/src/main/java/io/github/resilience4j/micrometer/BulkheadMetrics.java +++ b/resilience4j-micrometer/src/main/java/io/github/resilience4j/micrometer/BulkheadMetrics.java @@ -46,6 +46,7 @@ private BulkheadMetrics(Iterable bulkheads, String prefix) { * a {@link BulkheadRegistry} as a source. * * @param bulkheadRegistry the registry of bulkheads + * @return The BulkheadMetrics {@link BulkheadMetrics}. */ public static BulkheadMetrics ofBulkheadRegistry(BulkheadRegistry bulkheadRegistry) { return new BulkheadMetrics(bulkheadRegistry.getAllBulkheads()); diff --git a/resilience4j-micrometer/src/main/java/io/github/resilience4j/micrometer/CircuitBreakerMetrics.java b/resilience4j-micrometer/src/main/java/io/github/resilience4j/micrometer/CircuitBreakerMetrics.java index 8aa2baa191..6473f2d69e 100644 --- a/resilience4j-micrometer/src/main/java/io/github/resilience4j/micrometer/CircuitBreakerMetrics.java +++ b/resilience4j-micrometer/src/main/java/io/github/resilience4j/micrometer/CircuitBreakerMetrics.java @@ -44,6 +44,7 @@ private CircuitBreakerMetrics(Iterable circuitBreakers, String p * a {@link CircuitBreakerRegistry} as a source. * * @param circuitBreakerRegistry the registry of circuit breakers + * @return The CircuitBreakerMetrics {@link CircuitBreakerMetrics}. */ public static CircuitBreakerMetrics ofCircuitBreakerRegistry(CircuitBreakerRegistry circuitBreakerRegistry) { return new CircuitBreakerMetrics(circuitBreakerRegistry.getAllCircuitBreakers()); @@ -54,6 +55,7 @@ public static CircuitBreakerMetrics ofCircuitBreakerRegistry(CircuitBreakerRegis * an {@link Iterable} of circuit breakers as a source. * * @param circuitBreakers the circuit breakers + * @return The CircuitBreakerMetrics {@link CircuitBreakerMetrics}. */ public static CircuitBreakerMetrics ofIterable(Iterable circuitBreakers) { return new CircuitBreakerMetrics(circuitBreakers); @@ -63,7 +65,9 @@ public static CircuitBreakerMetrics ofIterable(Iterable circuitB * Creates a new instance CircuitBreakerMetrics {@link CircuitBreakerMetrics} with * an {@link Iterable} of circuit breakers as a source. * - * @param circuitBreakers the circuit breakers + * @param prefix The prefix. + * @param circuitBreakers the circuit + * @return The CircuitBreakerMetrics {@link CircuitBreakerMetrics}. */ public static CircuitBreakerMetrics ofIterable(String prefix, Iterable circuitBreakers) { return new CircuitBreakerMetrics(circuitBreakers, prefix); diff --git a/resilience4j-micrometer/src/main/java/io/github/resilience4j/micrometer/RateLimiterMetrics.java b/resilience4j-micrometer/src/main/java/io/github/resilience4j/micrometer/RateLimiterMetrics.java index c647618d45..59986b1096 100644 --- a/resilience4j-micrometer/src/main/java/io/github/resilience4j/micrometer/RateLimiterMetrics.java +++ b/resilience4j-micrometer/src/main/java/io/github/resilience4j/micrometer/RateLimiterMetrics.java @@ -46,6 +46,7 @@ private RateLimiterMetrics(Iterable rateLimiters, String prefix) { * a {@link RateLimiterRegistry} as a source. * * @param rateLimiterRegistry the registry of rate limiters + * @return The RateLimiterMetrics {@link RateLimiterMetrics}. */ public static RateLimiterMetrics ofRateLimiterRegistry(RateLimiterRegistry rateLimiterRegistry) { return new RateLimiterMetrics(rateLimiterRegistry.getAllRateLimiters()); diff --git a/resilience4j-micrometer/src/main/java/io/github/resilience4j/micrometer/tagged/TaggedBulkheadMetrics.java b/resilience4j-micrometer/src/main/java/io/github/resilience4j/micrometer/tagged/TaggedBulkheadMetrics.java index 3a48be69de..b240d56022 100644 --- a/resilience4j-micrometer/src/main/java/io/github/resilience4j/micrometer/tagged/TaggedBulkheadMetrics.java +++ b/resilience4j-micrometer/src/main/java/io/github/resilience4j/micrometer/tagged/TaggedBulkheadMetrics.java @@ -36,6 +36,7 @@ public class TaggedBulkheadMetrics implements MeterBinder { * Creates a new binder that uses given {@code registry} as source of bulkheads. * * @param registry the source of bulkheads + * @return The {@link TaggedBulkheadMetrics} instance. */ public static TaggedBulkheadMetrics ofBulkheadRegistry(BulkheadRegistry registry) { return new TaggedBulkheadMetrics(MetricNames.ofDefaults(), registry.getAllBulkheads()); @@ -47,6 +48,7 @@ public static TaggedBulkheadMetrics ofBulkheadRegistry(BulkheadRegistry registry * * @param names custom names of the metrics * @param registry the source of bulkheads + * @return The {@link TaggedBulkheadMetrics} instance. */ public static TaggedBulkheadMetrics ofBulkheadRegistry(MetricNames names, BulkheadRegistry registry) { return new TaggedBulkheadMetrics(names, registry.getAllBulkheads()); @@ -81,12 +83,15 @@ public static class MetricNames { /** * Returns a builder for creating custom metric names. * Note that names have default values, so only desired metrics can be renamed. + * @return The builder. */ public static Builder custom() { return new Builder(); } - /** Returns default metric names. */ + /** Returns default metric names. + * @return The default {@link MetricNames} instance. + */ public static MetricNames ofDefaults() { return new MetricNames(); } @@ -99,6 +104,7 @@ private MetricNames() {} /** * Returns the metric name for bulkhead concurrent calls, * defaults to {@value DEFAULT_BULKHEAD_AVAILABLE_CONCURRENT_CALLS_METRIC_NAME}. + * @return The available concurrent calls metric name. */ public String getAvailableConcurrentCallsMetricName() { return availableConcurrentCallsMetricName; @@ -107,6 +113,7 @@ public String getAvailableConcurrentCallsMetricName() { /** * Returns the metric name for bulkhead max available concurrent calls, * defaults to {@value DEFAULT_BULKHEAD_MAX_ALLOWED_CONCURRENT_CALLS_METRIC_NAME}. + * @return The max allowed concurrent calls metric name. */ public String getMaxAllowedConcurrentCallsMetricName() { return maxAllowedConcurrentCallsMetricName; @@ -117,19 +124,27 @@ public static class Builder { private final MetricNames metricNames = new MetricNames(); - /** Overrides the default metric name {@value MetricNames#DEFAULT_BULKHEAD_AVAILABLE_CONCURRENT_CALLS_METRIC_NAME} with a given one. */ + /** Overrides the default metric name {@value MetricNames#DEFAULT_BULKHEAD_AVAILABLE_CONCURRENT_CALLS_METRIC_NAME} with a given one. + * @param availableConcurrentCallsMetricNames The available concurrent calls metric name. + * @return The builder. + */ public Builder availableConcurrentCallsMetricName(String availableConcurrentCallsMetricNames) { metricNames.availableConcurrentCallsMetricName = requireNonNull(availableConcurrentCallsMetricNames); return this; } - /** Overrides the default metric name {@value MetricNames#DEFAULT_BULKHEAD_MAX_ALLOWED_CONCURRENT_CALLS_METRIC_NAME} with a given one. */ + /** Overrides the default metric name {@value MetricNames#DEFAULT_BULKHEAD_MAX_ALLOWED_CONCURRENT_CALLS_METRIC_NAME} with a given one. + * @param maxAllowedConcurrentCallsMetricName The max allowed concurrent calls metric name. + * @return The builder. + */ public Builder maxAllowedConcurrentCallsMetricName(String maxAllowedConcurrentCallsMetricName) { metricNames.maxAllowedConcurrentCallsMetricName = requireNonNull(maxAllowedConcurrentCallsMetricName); return this; } - /** Builds {@link MetricNames} instance. */ + /** Builds {@link MetricNames} instance. + * @return The built {@link MetricNames} instance. + */ public MetricNames build() { return metricNames; } diff --git a/resilience4j-micrometer/src/main/java/io/github/resilience4j/micrometer/tagged/TaggedCircuitBreakerMetrics.java b/resilience4j-micrometer/src/main/java/io/github/resilience4j/micrometer/tagged/TaggedCircuitBreakerMetrics.java index 1d6bb97a0c..649d3013e8 100644 --- a/resilience4j-micrometer/src/main/java/io/github/resilience4j/micrometer/tagged/TaggedCircuitBreakerMetrics.java +++ b/resilience4j-micrometer/src/main/java/io/github/resilience4j/micrometer/tagged/TaggedCircuitBreakerMetrics.java @@ -37,6 +37,7 @@ public class TaggedCircuitBreakerMetrics implements MeterBinder { * * @param metricNames custom metric names * @param registry the source of circuit breakers + * @return The {@link TaggedCircuitBreakerMetrics} instance. */ public static TaggedCircuitBreakerMetrics ofCircuitBreakerRegistry(MetricNames metricNames, CircuitBreakerRegistry registry) { return new TaggedCircuitBreakerMetrics(metricNames, registry.getAllCircuitBreakers()); @@ -46,6 +47,7 @@ public static TaggedCircuitBreakerMetrics ofCircuitBreakerRegistry(MetricNames m * Creates a new binder that uses given {@code registry} as source of circuit breakers. * * @param registry the source of circuit breakers + * @return The {@link TaggedCircuitBreakerMetrics} instance. */ public static TaggedCircuitBreakerMetrics ofCircuitBreakerRegistry(CircuitBreakerRegistry registry) { return ofCircuitBreakerRegistry(MetricNames.ofDefaults(), registry); @@ -100,12 +102,15 @@ public static class MetricNames { /** * Returns a builder for creating custom metric names. * Note that names have default values, so only desired metrics can be renamed. + * @return The builder. */ public static Builder custom() { return new Builder(); } - /** Returns default metric names. */ + /** Returns default metric names. + * @return The default {@link MetricNames} instance. + */ public static MetricNames ofDefaults() { return new MetricNames(); } @@ -117,22 +122,30 @@ public static MetricNames ofDefaults() { private MetricNames() {} - /** Returns the metric name for circuit breaker calls, defaults to {@value DEFAULT_CIRCUIT_BREAKER_STATE_METRIC_NAME}. */ + /** Returns the metric name for circuit breaker calls, defaults to {@value DEFAULT_CIRCUIT_BREAKER_STATE_METRIC_NAME}. + * @return The circuit breaker calls metric name. + */ public String getCallsMetricName() { return callsMetricName; } - /** Returns the metric name for currently buffered calls, defaults to {@value DEFAULT_CIRCUIT_BREAKER_STATE_METRIC_NAME}. */ + /** Returns the metric name for currently buffered calls, defaults to {@value DEFAULT_CIRCUIT_BREAKER_STATE_METRIC_NAME}. + * @return The buffered calls metric name. + */ public String getBufferedCallsMetricName() { return bufferedCallsMetricName; } - /** Returns the metric name for max buffered calls, defaults to {@value DEFAULT_CIRCUIT_BREAKER_STATE_METRIC_NAME}. */ + /** Returns the metric name for max buffered calls, defaults to {@value DEFAULT_CIRCUIT_BREAKER_STATE_METRIC_NAME}. + * @return The max buffered calls metric name. + */ public String getMaxBufferedCallsMetricName() { return maxBufferedCallsMetricName; } - /** Returns the metric name for state, defaults to {@value DEFAULT_CIRCUIT_BREAKER_STATE_METRIC_NAME}. */ + /** Returns the metric name for state, defaults to {@value DEFAULT_CIRCUIT_BREAKER_STATE_METRIC_NAME}. + * @return The state metric name. + */ public String getStateMetricName() { return stateMetricName; } @@ -141,31 +154,44 @@ public String getStateMetricName() { public static class Builder { private final MetricNames metricNames = new MetricNames(); - /** Overrides the default metric name {@value MetricNames#DEFAULT_CIRCUIT_BREAKER_CALLS_METRIC_NAME} with a given one. */ + /** Overrides the default metric name {@value MetricNames#DEFAULT_CIRCUIT_BREAKER_CALLS_METRIC_NAME} with a given one. + * @param callsMetricName The calls metric name. + * @return The builder.*/ public Builder callsMetricName(String callsMetricName) { metricNames.callsMetricName = requireNonNull(callsMetricName); return this; } - /** Overrides the default metric name {@value MetricNames#DEFAULT_CIRCUIT_BREAKER_STATE_METRIC_NAME} with a given one. */ + /** Overrides the default metric name {@value MetricNames#DEFAULT_CIRCUIT_BREAKER_STATE_METRIC_NAME} with a given one. + * @param stateMetricName The state metric name. + * @return The builder. + */ public Builder stateMetricName(String stateMetricName) { metricNames.stateMetricName = requireNonNull(stateMetricName); return this; } - /** Overrides the default metric name {@value MetricNames#DEFAULT_CIRCUIT_BREAKER_BUFFERED_CALLS} with a given one. */ + /** Overrides the default metric name {@value MetricNames#DEFAULT_CIRCUIT_BREAKER_BUFFERED_CALLS} with a given one. + * @param bufferedCallsMetricName The bufferd calls metric name. + * @return The builder. + */ public Builder bufferedCallsMetricName(String bufferedCallsMetricName) { metricNames.bufferedCallsMetricName = requireNonNull(bufferedCallsMetricName); return this; } - /** Overrides the default metric name {@value MetricNames#DEFAULT_CIRCUIT_BREAKER_MAX_BUFFERED_CALLS} with a given one. */ + /** Overrides the default metric name {@value MetricNames#DEFAULT_CIRCUIT_BREAKER_MAX_BUFFERED_CALLS} with a given one. + * @param maxBufferedCallsMetricName The max buffered calls metric name. + * @return The builder. + */ public Builder maxBufferedCallsMetricName(String maxBufferedCallsMetricName) { metricNames.maxBufferedCallsMetricName = requireNonNull(maxBufferedCallsMetricName); return this; } - /** Builds {@link MetricNames} instance. */ + /** Builds {@link MetricNames} instance. + * @return The built {@link MetricNames} instance. + */ public MetricNames build() { return metricNames; } diff --git a/resilience4j-micrometer/src/main/java/io/github/resilience4j/micrometer/tagged/TaggedRateLimiterMetrics.java b/resilience4j-micrometer/src/main/java/io/github/resilience4j/micrometer/tagged/TaggedRateLimiterMetrics.java index 00d0674ef7..05c8fdf166 100644 --- a/resilience4j-micrometer/src/main/java/io/github/resilience4j/micrometer/tagged/TaggedRateLimiterMetrics.java +++ b/resilience4j-micrometer/src/main/java/io/github/resilience4j/micrometer/tagged/TaggedRateLimiterMetrics.java @@ -38,6 +38,7 @@ public class TaggedRateLimiterMetrics implements MeterBinder { * Creates a new binder that uses given {@code registry} as source of retries. * * @param registry the source of retries + * @return The {@link TaggedRateLimiterMetrics} instance. */ public static TaggedRateLimiterMetrics ofRateLimiterRegistry(RateLimiterRegistry registry) { return new TaggedRateLimiterMetrics(MetricNames.ofDefaults(), registry.getAllRateLimiters()); @@ -48,6 +49,7 @@ public static TaggedRateLimiterMetrics ofRateLimiterRegistry(RateLimiterRegistry * * @param names custom metric names * @param registry the source of rate limiters + * @return The {@link TaggedRateLimiterMetrics} instance. */ public static TaggedRateLimiterMetrics ofRateLimiterRegistry(MetricNames names, RateLimiterRegistry registry) { return new TaggedRateLimiterMetrics(names, registry.getAllRateLimiters()); @@ -82,12 +84,15 @@ public static class MetricNames { /** * Returns a builder for creating custom metric names. * Note that names have default values, so only desired metrics can be renamed. + * @return The builder. */ public static Builder custom() { return new Builder(); } - /** Returns default metric names. */ + /** Returns default metric names. + * @return The default {@link MetricNames} instance. + */ public static MetricNames ofDefaults() { return new MetricNames(); } @@ -95,12 +100,16 @@ public static MetricNames ofDefaults() { private String availablePermissionsMetricName = DEFAULT_AVAILABLE_PERMISSIONS_METRIC_NAME; private String waitingThreadsMetricName = DEFAULT_WAITING_THREADS_METRIC_NAME; - /** Returns the metric name for available permissions, defaults to {@value DEFAULT_AVAILABLE_PERMISSIONS_METRIC_NAME}. */ + /** Returns the metric name for available permissions, defaults to {@value DEFAULT_AVAILABLE_PERMISSIONS_METRIC_NAME}. + * @return The available permissions metric name. + */ public String getAvailablePermissionsMetricName() { return availablePermissionsMetricName; } - /** Returns the metric name for waiting threads, defaults to {@value DEFAULT_WAITING_THREADS_METRIC_NAME}. */ + /** Returns the metric name for waiting threads, defaults to {@value DEFAULT_WAITING_THREADS_METRIC_NAME}. + * @return The waiting threads metric name. + */ public String getWaitingThreadsMetricName() { return waitingThreadsMetricName; } @@ -110,19 +119,27 @@ public static class Builder { private final MetricNames metricNames = new MetricNames(); - /** Overrides the default metric name {@value MetricNames#DEFAULT_AVAILABLE_PERMISSIONS_METRIC_NAME} with a given one. */ + /** Overrides the default metric name {@value MetricNames#DEFAULT_AVAILABLE_PERMISSIONS_METRIC_NAME} with a given one. + * @param availablePermissionsMetricName The available permissions metric name. + * @return The builder. + */ public Builder availablePermissionsMetricName(String availablePermissionsMetricName) { metricNames.availablePermissionsMetricName = requireNonNull(availablePermissionsMetricName); return this; } - /** Overrides the default metric name {@value MetricNames#DEFAULT_WAITING_THREADS_METRIC_NAME} with a given one. */ + /** Overrides the default metric name {@value MetricNames#DEFAULT_WAITING_THREADS_METRIC_NAME} with a given one. + * @param waitingThreadsMetricName The waiting threads metric name. + * @return The builder. + */ public Builder waitingThreadsMetricName(String waitingThreadsMetricName) { metricNames.waitingThreadsMetricName = requireNonNull(waitingThreadsMetricName); return this; } - /** Builds {@link MetricNames} instance. */ + /** Builds {@link MetricNames} instance. + * @return The built {@link MetricNames} instance. + */ public MetricNames build() { return metricNames; } diff --git a/resilience4j-micrometer/src/main/java/io/github/resilience4j/micrometer/tagged/TaggedRetryMetrics.java b/resilience4j-micrometer/src/main/java/io/github/resilience4j/micrometer/tagged/TaggedRetryMetrics.java index 4314b4ad65..3f7dbb7cec 100644 --- a/resilience4j-micrometer/src/main/java/io/github/resilience4j/micrometer/tagged/TaggedRetryMetrics.java +++ b/resilience4j-micrometer/src/main/java/io/github/resilience4j/micrometer/tagged/TaggedRetryMetrics.java @@ -36,6 +36,7 @@ public class TaggedRetryMetrics implements MeterBinder { * Creates a new binder that uses given {@code registry} as source of retries. * * @param registry the source of retries + * @return The {@link TaggedRetryMetrics} instance. */ public static TaggedRetryMetrics ofRetryRegistry(RetryRegistry registry) { return new TaggedRetryMetrics(MetricNames.ofDefaults(), registry.getAllRetries()); @@ -46,6 +47,7 @@ public static TaggedRetryMetrics ofRetryRegistry(RetryRegistry registry) { * * @param names custom metric names * @param registry the source of retries + * @return The {@link TaggedRetryMetrics} instance. */ public static TaggedRetryMetrics ofRetryRegistry(MetricNames names, RetryRegistry registry) { return new TaggedRetryMetrics(names, registry.getAllRetries()); @@ -89,12 +91,15 @@ public static class MetricNames { /** * Returns a builder for creating custom metric names. * Note that names have default values, so only desired metrics can be renamed. + * @return The builder. */ public static Builder custom() { return new Builder(); } - /** Returns default metric names. */ + /** Returns default metric names. + * @return The default {@link MetricNames} instance. + */ public static MetricNames ofDefaults() { return new MetricNames(); } @@ -103,7 +108,9 @@ public static MetricNames ofDefaults() { private MetricNames() {} - /** Returns the metric name for retry calls, defaults to {@value DEFAULT_RETRY_CALLS}. */ + /** Returns the metric name for retry calls, defaults to {@value DEFAULT_RETRY_CALLS}. + * @return The metric name for retry calls. + */ public String getCallsMetricName() { return callsMetricName; } @@ -112,13 +119,18 @@ public String getCallsMetricName() { public static class Builder { private final MetricNames metricNames = new MetricNames(); - /** Overrides the default metric name {@value MetricNames#DEFAULT_RETRY_CALLS} with a given one. */ + /** Overrides the default metric name {@value MetricNames#DEFAULT_RETRY_CALLS} with a given one. + * @param callsMetricName The metric name for retry calls. + * @return The builder. + */ public Builder callsMetricName(String callsMetricName) { metricNames.callsMetricName = requireNonNull(callsMetricName); return this; } - /** Builds {@link MetricNames} instance. */ + /** Builds {@link MetricNames} instance. + * @return The built {@link MetricNames} instance. + */ public MetricNames build() { return metricNames; } diff --git a/resilience4j-ratelimiter/src/main/java/io/github/resilience4j/ratelimiter/RateLimiterConfig.java b/resilience4j-ratelimiter/src/main/java/io/github/resilience4j/ratelimiter/RateLimiterConfig.java index 2ad4cee894..0684b19d17 100644 --- a/resilience4j-ratelimiter/src/main/java/io/github/resilience4j/ratelimiter/RateLimiterConfig.java +++ b/resilience4j-ratelimiter/src/main/java/io/github/resilience4j/ratelimiter/RateLimiterConfig.java @@ -53,6 +53,7 @@ public static Builder custom() { /** * Returns a builder to create a custom RateLimiterConfig using specified config as prototype * + * @param prototype A {@link RateLimiterConfig} prototype. * @return a {@link RateLimiterConfig.Builder} */ public static Builder from(RateLimiterConfig prototype) { diff --git a/resilience4j-retry/src/main/java/io/github/resilience4j/retry/RetryConfig.java b/resilience4j-retry/src/main/java/io/github/resilience4j/retry/RetryConfig.java index a500ed7aea..4b1374e612 100644 --- a/resilience4j-retry/src/main/java/io/github/resilience4j/retry/RetryConfig.java +++ b/resilience4j-retry/src/main/java/io/github/resilience4j/retry/RetryConfig.java @@ -63,6 +63,7 @@ public Predicate getExceptionPredicate() { * Return the Predicate which evaluates if an result should be retried. * The Predicate must return true if the result should be retried, otherwise it must return false. * + * @param The type of result. * @return the resultPredicate */ @SuppressWarnings("unchecked") @@ -74,6 +75,7 @@ public Predicate getResultPredicate() { /** * Returns a builder to create a custom RetryConfig. * + * @param The type being built. * @return a {@link Builder} */ public static Builder custom() { @@ -171,6 +173,7 @@ public Builder retryOnException(Predicate predicate) { * For a more sophisticated exception management use the * @see #retryOnException(Predicate) method */ + @SuppressWarnings("unchecked") @SafeVarargs public final Builder retryExceptions(@Nullable Class... errorClasses) { this.retryExceptions = errorClasses != null ? errorClasses : new Class[0]; @@ -196,6 +199,7 @@ public final Builder retryExceptions(@Nullable Class... * For a more sophisticated exception management use the * @see #retryOnException(Predicate) method */ + @SuppressWarnings("unchecked") @SafeVarargs public final Builder ignoreExceptions(@Nullable Class... errorClasses) { this.ignoreExceptions = errorClasses != null ? errorClasses : new Class[0]; diff --git a/resilience4j-spring-boot-common/README.adoc b/resilience4j-spring-boot-common/README.adoc new file mode 100644 index 0000000000..4cd04c111c --- /dev/null +++ b/resilience4j-spring-boot-common/README.adoc @@ -0,0 +1,12 @@ += resilience4j-spring-boot-common +This module contain the common configuration between spring boot 1 and 2 starters + +== License + +Copyright 2019 Robert Winkler , Mahmoud Romeh + +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 + + http://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. diff --git a/resilience4j-spring-boot-common/build.gradle b/resilience4j-spring-boot-common/build.gradle new file mode 100644 index 0000000000..64852fcdae --- /dev/null +++ b/resilience4j-spring-boot-common/build.gradle @@ -0,0 +1,22 @@ +dependencies { + compileOnly(libraries.spring_boot2_aop) + compileOnly(libraries.spring_boot2_config_processor) + compileOnly(libraries.spring_boot2_autoconfigure_processor) + + compile project(':resilience4j-annotations') + compile project(':resilience4j-spring') + compile project(':resilience4j-micrometer') + compile project(':resilience4j-circuitbreaker') + compile project(':resilience4j-ratelimiter') + compile project(':resilience4j-consumer') + + testCompile project(':resilience4j-reactor') + testCompile project(':resilience4j-rxjava2') + testCompile(libraries.rxjava2) + testCompile(libraries.spring_boot2_test) + testCompile(libraries.spring_boot2_aop) + +} + +compileJava.dependsOn(processResources) +ext.moduleName='io.github.resilience4j.spring-boot-common' \ No newline at end of file diff --git a/resilience4j-spring-boot-common/src/main/java/io/github/resilience4j/springboot/common/bulkhead/autoconfigure/AbstractBulkheadConfigurationOnMissingBean.java b/resilience4j-spring-boot-common/src/main/java/io/github/resilience4j/springboot/common/bulkhead/autoconfigure/AbstractBulkheadConfigurationOnMissingBean.java new file mode 100644 index 0000000000..bacd051118 --- /dev/null +++ b/resilience4j-spring-boot-common/src/main/java/io/github/resilience4j/springboot/common/bulkhead/autoconfigure/AbstractBulkheadConfigurationOnMissingBean.java @@ -0,0 +1,79 @@ +/* + * Copyright 2019 Mahmoud Romeh + * + * 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 + * + * http://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 io.github.resilience4j.springboot.common.bulkhead.autoconfigure; + +import java.util.List; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Conditional; +import org.springframework.context.annotation.Configuration; + +import io.github.resilience4j.bulkhead.BulkheadRegistry; +import io.github.resilience4j.bulkhead.configure.BulkheadAspect; +import io.github.resilience4j.bulkhead.configure.BulkheadAspectExt; +import io.github.resilience4j.bulkhead.configure.BulkheadConfiguration; +import io.github.resilience4j.bulkhead.configure.BulkheadConfigurationProperties; +import io.github.resilience4j.bulkhead.configure.ReactorBulkheadAspectExt; +import io.github.resilience4j.bulkhead.configure.RxJava2BulkheadAspectExt; +import io.github.resilience4j.bulkhead.event.BulkheadEvent; +import io.github.resilience4j.consumer.EventConsumerRegistry; +import io.github.resilience4j.utils.ReactorOnClasspathCondition; +import io.github.resilience4j.utils.RxJava2OnClasspathCondition; + +/** + * {@link Configuration + * Configuration} for resilience4j-bulkhead. + */ +@Configuration +public abstract class AbstractBulkheadConfigurationOnMissingBean { + + protected final BulkheadConfiguration bulkheadConfiguration; + + public AbstractBulkheadConfigurationOnMissingBean() { + this.bulkheadConfiguration = new BulkheadConfiguration(); + } + + @Bean + @ConditionalOnMissingBean + public BulkheadRegistry bulkheadRegistry(BulkheadConfigurationProperties bulkheadConfigurationProperties, + EventConsumerRegistry bulkheadEventConsumerRegistry) { + return bulkheadConfiguration.bulkheadRegistry(bulkheadConfigurationProperties, bulkheadEventConsumerRegistry); + } + + @Bean + @ConditionalOnMissingBean + public BulkheadAspect bulkheadAspect(BulkheadConfigurationProperties bulkheadConfigurationProperties, + BulkheadRegistry bulkheadRegistry, @Autowired(required = false) List bulkHeadAspectExtList) { + return bulkheadConfiguration.bulkheadAspect(bulkheadConfigurationProperties, bulkheadRegistry, bulkHeadAspectExtList); + } + + @Bean + @Conditional(value = {RxJava2OnClasspathCondition.class}) + @ConditionalOnMissingBean + public RxJava2BulkheadAspectExt rxJava2BulkHeadAspectExt() { + return bulkheadConfiguration.rxJava2BulkHeadAspectExt(); + } + + @Bean + @Conditional(value = {ReactorOnClasspathCondition.class}) + @ConditionalOnMissingBean + public ReactorBulkheadAspectExt reactorBulkHeadAspectExt() { + return bulkheadConfiguration.reactorBulkHeadAspectExt(); + } + +} diff --git a/resilience4j-spring-boot-common/src/main/java/io/github/resilience4j/springboot/common/circuitbreaker/autoconfigure/AbstractCircuitBreakerConfigurationOnMissingBean.java b/resilience4j-spring-boot-common/src/main/java/io/github/resilience4j/springboot/common/circuitbreaker/autoconfigure/AbstractCircuitBreakerConfigurationOnMissingBean.java new file mode 100644 index 0000000000..f5225e4e5a --- /dev/null +++ b/resilience4j-spring-boot-common/src/main/java/io/github/resilience4j/springboot/common/circuitbreaker/autoconfigure/AbstractCircuitBreakerConfigurationOnMissingBean.java @@ -0,0 +1,90 @@ +/* + * Copyright 2019 Mahmoud Romeh + * + * 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 + * + * http://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 io.github.resilience4j.springboot.common.circuitbreaker.autoconfigure; + +import java.util.List; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Conditional; +import org.springframework.context.annotation.Configuration; + +import io.github.resilience4j.circuitbreaker.CircuitBreaker; +import io.github.resilience4j.circuitbreaker.CircuitBreakerRegistry; +import io.github.resilience4j.circuitbreaker.configure.CircuitBreakerAspect; +import io.github.resilience4j.circuitbreaker.configure.CircuitBreakerAspectExt; +import io.github.resilience4j.circuitbreaker.configure.CircuitBreakerConfiguration; +import io.github.resilience4j.circuitbreaker.configure.CircuitBreakerConfigurationProperties; +import io.github.resilience4j.circuitbreaker.configure.ReactorCircuitBreakerAspectExt; +import io.github.resilience4j.circuitbreaker.configure.RxJava2CircuitBreakerAspectExt; +import io.github.resilience4j.circuitbreaker.event.CircuitBreakerEvent; +import io.github.resilience4j.consumer.EventConsumerRegistry; +import io.github.resilience4j.utils.ReactorOnClasspathCondition; +import io.github.resilience4j.utils.RxJava2OnClasspathCondition; + +@Configuration +public abstract class AbstractCircuitBreakerConfigurationOnMissingBean { + + protected final CircuitBreakerConfiguration circuitBreakerConfiguration; + protected final CircuitBreakerConfigurationProperties circuitBreakerProperties; + + public AbstractCircuitBreakerConfigurationOnMissingBean(CircuitBreakerConfigurationProperties circuitBreakerProperties) { + this.circuitBreakerProperties = circuitBreakerProperties; + this.circuitBreakerConfiguration = new CircuitBreakerConfiguration(circuitBreakerProperties); + } + + @Bean + @ConditionalOnMissingBean + public CircuitBreakerRegistry circuitBreakerRegistry(EventConsumerRegistry eventConsumerRegistry) { + CircuitBreakerRegistry circuitBreakerRegistry = CircuitBreakerRegistry.ofDefaults(); + + // Register the event consumers + circuitBreakerConfiguration.registerPostCreationEventConsumer(circuitBreakerRegistry, eventConsumerRegistry); + // Register a consumer to hook up any health indicators for circuit breakers after creation. This will catch ones that get + // created beyond initially configured backends. + circuitBreakerRegistry.registerPostCreationConsumer(this::createHeathIndicatorForCircuitBreaker); + + // Initialize backends that were initially configured. + circuitBreakerConfiguration.initializeBackends(circuitBreakerRegistry); + + return circuitBreakerRegistry; + } + + protected abstract void createHeathIndicatorForCircuitBreaker(CircuitBreaker circuitBreaker); + + @Bean + @ConditionalOnMissingBean + public CircuitBreakerAspect circuitBreakerAspect(CircuitBreakerRegistry circuitBreakerRegistry, + @Autowired(required = false) List circuitBreakerAspectExtList) { + return circuitBreakerConfiguration.circuitBreakerAspect(circuitBreakerRegistry, circuitBreakerAspectExtList); + } + + @Bean + @Conditional(value = {RxJava2OnClasspathCondition.class}) + @ConditionalOnMissingBean + public RxJava2CircuitBreakerAspectExt rxJava2CircuitBreakerAspect() { + return circuitBreakerConfiguration.rxJava2CircuitBreakerAspect(); + } + + @Bean + @Conditional(value = {ReactorOnClasspathCondition.class}) + @ConditionalOnMissingBean + public ReactorCircuitBreakerAspectExt reactorCircuitBreakerAspect() { + return circuitBreakerConfiguration.reactorCircuitBreakerAspect(); + } + +} diff --git a/resilience4j-spring-boot-common/src/main/java/io/github/resilience4j/springboot/common/ratelimiter/autoconfigure/AbstractRateLimiterConfigurationOnMissingBean.java b/resilience4j-spring-boot-common/src/main/java/io/github/resilience4j/springboot/common/ratelimiter/autoconfigure/AbstractRateLimiterConfigurationOnMissingBean.java new file mode 100644 index 0000000000..08c0e6a00f --- /dev/null +++ b/resilience4j-spring-boot-common/src/main/java/io/github/resilience4j/springboot/common/ratelimiter/autoconfigure/AbstractRateLimiterConfigurationOnMissingBean.java @@ -0,0 +1,73 @@ +/* + * Copyright 2019 Mahmoud Romeh + * + * 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 + * + * http://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 io.github.resilience4j.springboot.common.ratelimiter.autoconfigure; + +import java.util.List; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Conditional; +import org.springframework.context.annotation.Configuration; + +import io.github.resilience4j.consumer.EventConsumerRegistry; +import io.github.resilience4j.ratelimiter.RateLimiterRegistry; +import io.github.resilience4j.ratelimiter.configure.RateLimiterAspect; +import io.github.resilience4j.ratelimiter.configure.RateLimiterAspectExt; +import io.github.resilience4j.ratelimiter.configure.RateLimiterConfiguration; +import io.github.resilience4j.ratelimiter.configure.RateLimiterConfigurationProperties; +import io.github.resilience4j.ratelimiter.configure.ReactorRateLimiterAspectExt; +import io.github.resilience4j.ratelimiter.configure.RxJava2RateLimiterAspectExt; +import io.github.resilience4j.ratelimiter.event.RateLimiterEvent; +import io.github.resilience4j.utils.ReactorOnClasspathCondition; +import io.github.resilience4j.utils.RxJava2OnClasspathCondition; + +@Configuration +public abstract class AbstractRateLimiterConfigurationOnMissingBean { + protected final RateLimiterConfiguration rateLimiterConfiguration; + + public AbstractRateLimiterConfigurationOnMissingBean() { + this.rateLimiterConfiguration = new RateLimiterConfiguration(); + } + + @Bean + @ConditionalOnMissingBean + public RateLimiterRegistry rateLimiterRegistry(RateLimiterConfigurationProperties rateLimiterProperties, + EventConsumerRegistry rateLimiterEventsConsumerRegistry) { + return rateLimiterConfiguration.rateLimiterRegistry(rateLimiterProperties, rateLimiterEventsConsumerRegistry); + } + + @Bean + @ConditionalOnMissingBean + public RateLimiterAspect rateLimiterAspect(RateLimiterConfigurationProperties rateLimiterProperties, RateLimiterRegistry rateLimiterRegistry, @Autowired(required = false) List rateLimiterAspectExtList) { + return rateLimiterConfiguration.rateLimiterAspect(rateLimiterProperties, rateLimiterRegistry, rateLimiterAspectExtList); + } + + @Bean + @Conditional(value = {RxJava2OnClasspathCondition.class}) + @ConditionalOnMissingBean + public RxJava2RateLimiterAspectExt rxJava2RateLimterAspectExt() { + return rateLimiterConfiguration.rxJava2RateLimterAspectExt(); + } + + @Bean + @Conditional(value = {ReactorOnClasspathCondition.class}) + @ConditionalOnMissingBean + public ReactorRateLimiterAspectExt reactorRateLimiterAspectExt() { + return rateLimiterConfiguration.reactorRateLimiterAspectExt(); + } + +} diff --git a/resilience4j-spring-boot-common/src/main/java/io/github/resilience4j/springboot/common/retry/autoconfigure/AbstractRetryConfigurationOnMissingBean.java b/resilience4j-spring-boot-common/src/main/java/io/github/resilience4j/springboot/common/retry/autoconfigure/AbstractRetryConfigurationOnMissingBean.java new file mode 100644 index 0000000000..abe65e5f9f --- /dev/null +++ b/resilience4j-spring-boot-common/src/main/java/io/github/resilience4j/springboot/common/retry/autoconfigure/AbstractRetryConfigurationOnMissingBean.java @@ -0,0 +1,88 @@ +/* + * Copyright 2019 Mahmoud Romeh + * + * 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 + * + * http://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 io.github.resilience4j.springboot.common.retry.autoconfigure; + +import java.util.List; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Conditional; +import org.springframework.context.annotation.Configuration; + +import io.github.resilience4j.consumer.EventConsumerRegistry; +import io.github.resilience4j.retry.RetryRegistry; +import io.github.resilience4j.retry.configure.ReactorRetryAspectExt; +import io.github.resilience4j.retry.configure.RetryAspect; +import io.github.resilience4j.retry.configure.RetryAspectExt; +import io.github.resilience4j.retry.configure.RetryConfiguration; +import io.github.resilience4j.retry.configure.RetryConfigurationProperties; +import io.github.resilience4j.retry.configure.RxJava2RetryAspectExt; +import io.github.resilience4j.retry.event.RetryEvent; +import io.github.resilience4j.utils.ReactorOnClasspathCondition; +import io.github.resilience4j.utils.RxJava2OnClasspathCondition; + +/** + * {@link Configuration + * Configuration} for resilience4j-retry. + */ +@Configuration +public abstract class AbstractRetryConfigurationOnMissingBean { + + protected final RetryConfiguration retryConfiguration; + + public AbstractRetryConfigurationOnMissingBean() { + this.retryConfiguration = new RetryConfiguration(); + } + + /** + * @param retryConfigurationProperties retryConfigurationProperties retry configuration spring properties + * @param retryEventConsumerRegistry the event retry registry + * @return the retry definition registry + */ + @Bean + @ConditionalOnMissingBean + public RetryRegistry retryRegistry(RetryConfigurationProperties retryConfigurationProperties, EventConsumerRegistry retryEventConsumerRegistry) { + return retryConfiguration.retryRegistry(retryConfigurationProperties, retryEventConsumerRegistry); + } + + /** + * @param retryConfigurationProperties retry configuration spring properties + * @param retryRegistry retry in memory registry + * @return the spring retry AOP aspect + */ + @Bean + @ConditionalOnMissingBean + public RetryAspect retryAspect(RetryConfigurationProperties retryConfigurationProperties, + RetryRegistry retryRegistry, @Autowired(required = false) List retryAspectExtList) { + return retryConfiguration.retryAspect(retryConfigurationProperties, retryRegistry, retryAspectExtList); + } + + @Bean + @Conditional(value = {RxJava2OnClasspathCondition.class}) + @ConditionalOnMissingBean + public RxJava2RetryAspectExt rxJava2RetryAspectExt() { + return retryConfiguration.rxJava2RetryAspectExt(); + } + + @Bean + @Conditional(value = {ReactorOnClasspathCondition.class}) + @ConditionalOnMissingBean + public ReactorRetryAspectExt reactorRetryAspectExt() { + return retryConfiguration.reactorRetryAspectExt(); + } + +} diff --git a/resilience4j-spring-boot-common/src/test/java/io/github/resilience4j/springboot/common/SpringBootCommonTest.java b/resilience4j-spring-boot-common/src/test/java/io/github/resilience4j/springboot/common/SpringBootCommonTest.java new file mode 100644 index 0000000000..cd1b5aec13 --- /dev/null +++ b/resilience4j-spring-boot-common/src/test/java/io/github/resilience4j/springboot/common/SpringBootCommonTest.java @@ -0,0 +1,87 @@ +package io.github.resilience4j.springboot.common; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; + +import java.util.Collections; + +import org.junit.Test; + +import io.github.resilience4j.bulkhead.BulkheadRegistry; +import io.github.resilience4j.bulkhead.configure.BulkheadConfigurationProperties; +import io.github.resilience4j.circuitbreaker.CircuitBreaker; +import io.github.resilience4j.circuitbreaker.CircuitBreakerRegistry; +import io.github.resilience4j.circuitbreaker.configure.CircuitBreakerConfigurationProperties; +import io.github.resilience4j.consumer.DefaultEventConsumerRegistry; +import io.github.resilience4j.ratelimiter.RateLimiterRegistry; +import io.github.resilience4j.ratelimiter.configure.RateLimiterConfigurationProperties; +import io.github.resilience4j.retry.RetryRegistry; +import io.github.resilience4j.retry.configure.RetryConfigurationProperties; +import io.github.resilience4j.springboot.common.bulkhead.autoconfigure.AbstractBulkheadConfigurationOnMissingBean; +import io.github.resilience4j.springboot.common.circuitbreaker.autoconfigure.AbstractCircuitBreakerConfigurationOnMissingBean; +import io.github.resilience4j.springboot.common.ratelimiter.autoconfigure.AbstractRateLimiterConfigurationOnMissingBean; +import io.github.resilience4j.springboot.common.retry.autoconfigure.AbstractRetryConfigurationOnMissingBean; + +/** + * @author romeh + */ +public class SpringBootCommonTest { + + @Test + public void testBulkHeadCommonConfig() { + BulkheadConfigurationOnMissingBean bulkheadConfigurationOnMissingBean = new BulkheadConfigurationOnMissingBean(); + assertThat(bulkheadConfigurationOnMissingBean.bulkheadRegistry(new BulkheadConfigurationProperties(), new DefaultEventConsumerRegistry<>())).isNotNull(); + assertThat(bulkheadConfigurationOnMissingBean.reactorBulkHeadAspectExt()).isNotNull(); + assertThat(bulkheadConfigurationOnMissingBean.rxJava2BulkHeadAspectExt()).isNotNull(); + assertThat(bulkheadConfigurationOnMissingBean.bulkheadAspect(new BulkheadConfigurationProperties(), BulkheadRegistry.ofDefaults(), Collections.emptyList())); + } + + @Test + public void testCircuitBreakerCommonConfig() { + CircuitBreakerConfig circuitBreakerConfig = new CircuitBreakerConfig(new CircuitBreakerConfigurationProperties()); + assertThat(circuitBreakerConfig.reactorCircuitBreakerAspect()).isNotNull(); + assertThat(circuitBreakerConfig.rxJava2CircuitBreakerAspect()).isNotNull(); + assertThat(circuitBreakerConfig.circuitBreakerRegistry(new DefaultEventConsumerRegistry<>())).isNotNull(); + assertThat(circuitBreakerConfig.circuitBreakerAspect(CircuitBreakerRegistry.ofDefaults(), Collections.emptyList())); + } + + @Test + public void testRetryCommonConfig() { + RetryConfigurationOnMissingBean retryConfigurationOnMissingBean = new RetryConfigurationOnMissingBean(); + assertThat(retryConfigurationOnMissingBean.reactorRetryAspectExt()).isNotNull(); + assertThat(retryConfigurationOnMissingBean.rxJava2RetryAspectExt()).isNotNull(); + assertThat(retryConfigurationOnMissingBean.retryRegistry(new RetryConfigurationProperties(), new DefaultEventConsumerRegistry<>())).isNotNull(); + assertThat(retryConfigurationOnMissingBean.retryAspect(new RetryConfigurationProperties(), RetryRegistry.ofDefaults(), Collections.emptyList())); + } + + @Test + public void testRateLimiterCommonConfig() { + RateLimiterConfigurationOnMissingBean rateLimiterConfigurationOnMissingBean = new RateLimiterConfigurationOnMissingBean(); + assertThat(rateLimiterConfigurationOnMissingBean.reactorRateLimiterAspectExt()).isNotNull(); + assertThat(rateLimiterConfigurationOnMissingBean.rxJava2RateLimterAspectExt()).isNotNull(); + assertThat(rateLimiterConfigurationOnMissingBean.rateLimiterRegistry(new RateLimiterConfigurationProperties(), new DefaultEventConsumerRegistry<>())).isNotNull(); + assertThat(rateLimiterConfigurationOnMissingBean.rateLimiterAspect(new RateLimiterConfigurationProperties(), RateLimiterRegistry.ofDefaults(), Collections.emptyList())); + } + + + // testing config samples + class BulkheadConfigurationOnMissingBean extends AbstractBulkheadConfigurationOnMissingBean { + } + + class CircuitBreakerConfig extends AbstractCircuitBreakerConfigurationOnMissingBean { + + public CircuitBreakerConfig(CircuitBreakerConfigurationProperties circuitBreakerProperties) { + super(circuitBreakerProperties); + } + + @Override + protected void createHeathIndicatorForCircuitBreaker(CircuitBreaker circuitBreaker) { + + } + } + + class RetryConfigurationOnMissingBean extends AbstractRetryConfigurationOnMissingBean { + } + + class RateLimiterConfigurationOnMissingBean extends AbstractRateLimiterConfigurationOnMissingBean { + } +} diff --git a/resilience4j-spring-boot/build.gradle b/resilience4j-spring-boot/build.gradle index 9871e60291..abddd03625 100644 --- a/resilience4j-spring-boot/build.gradle +++ b/resilience4j-spring-boot/build.gradle @@ -8,11 +8,7 @@ dependencies { compileOnly(libraries.spring_boot_config_processor) compileOnly(libraries.spring_boot_autoconfigure_processor) - compile project(':resilience4j-annotations') - compile project(':resilience4j-spring') - compile project(':resilience4j-circuitbreaker') - compile project(':resilience4j-ratelimiter') - compile project(':resilience4j-consumer') + compile project(':resilience4j-spring-boot-common') compileOnly project(':resilience4j-prometheus') compileOnly project(':resilience4j-metrics') diff --git a/resilience4j-spring-boot/src/main/java/io/github/resilience4j/bulkhead/autoconfigure/BulkheadAutoConfiguration.java b/resilience4j-spring-boot/src/main/java/io/github/resilience4j/bulkhead/autoconfigure/BulkheadAutoConfiguration.java index 55deca5059..763ee015e0 100644 --- a/resilience4j-spring-boot/src/main/java/io/github/resilience4j/bulkhead/autoconfigure/BulkheadAutoConfiguration.java +++ b/resilience4j-spring-boot/src/main/java/io/github/resilience4j/bulkhead/autoconfigure/BulkheadAutoConfiguration.java @@ -25,7 +25,6 @@ import io.github.resilience4j.bulkhead.Bulkhead; import io.github.resilience4j.bulkhead.BulkheadRegistry; -import io.github.resilience4j.bulkhead.configure.BulkheadConfiguration; import io.github.resilience4j.bulkhead.event.BulkheadEvent; import io.github.resilience4j.bulkhead.monitoring.endpoint.BulkheadEndpoint; import io.github.resilience4j.bulkhead.monitoring.endpoint.BulkheadEventsEndpoint; @@ -38,14 +37,14 @@ @Configuration @ConditionalOnClass(Bulkhead.class) @EnableConfigurationProperties(BulkheadProperties.class) -@Import(BulkheadConfiguration.class) +@Import(BulkheadConfigurationOnMissingBean.class) @AutoConfigureBefore(EndpointAutoConfiguration.class) public class BulkheadAutoConfiguration { - @Bean - public BulkheadEndpoint bulkheadEndpoint(BulkheadRegistry bulkheadRegistry) { - return new BulkheadEndpoint(bulkheadRegistry); - } + @Bean + public BulkheadEndpoint bulkheadEndpoint(BulkheadRegistry bulkheadRegistry) { + return new BulkheadEndpoint(bulkheadRegistry); + } @Bean public BulkheadEventsEndpoint bulkheadEventsEndpoint(EventConsumerRegistry eventConsumerRegistry) { diff --git a/resilience4j-spring-boot/src/main/java/io/github/resilience4j/bulkhead/autoconfigure/BulkheadConfigurationOnMissingBean.java b/resilience4j-spring-boot/src/main/java/io/github/resilience4j/bulkhead/autoconfigure/BulkheadConfigurationOnMissingBean.java new file mode 100644 index 0000000000..8ff3c2644d --- /dev/null +++ b/resilience4j-spring-boot/src/main/java/io/github/resilience4j/bulkhead/autoconfigure/BulkheadConfigurationOnMissingBean.java @@ -0,0 +1,43 @@ +/* + * Copyright 2019 Mahmoud Romeh + * + * 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 + * + * http://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 io.github.resilience4j.bulkhead.autoconfigure; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import io.github.resilience4j.bulkhead.event.BulkheadEvent; +import io.github.resilience4j.consumer.DefaultEventConsumerRegistry; +import io.github.resilience4j.consumer.EventConsumerRegistry; +import io.github.resilience4j.springboot.common.bulkhead.autoconfigure.AbstractBulkheadConfigurationOnMissingBean; + +/** + * {@link Configuration + * Configuration} for resilience4j-bulkhead. + */ +@Configuration +public class BulkheadConfigurationOnMissingBean extends AbstractBulkheadConfigurationOnMissingBean { + /** + * The EventConsumerRegistry is used to manage EventConsumer instances. + * The EventConsumerRegistry is used by the BulkheadHealthIndicator to show the latest Bulkhead events + * for each Bulkhead instance. + * + * @return a default EventConsumerRegistry {@link DefaultEventConsumerRegistry} + */ + @Bean + public EventConsumerRegistry bulkheadEventConsumerRegistry() { + return bulkheadConfiguration.bulkheadEventConsumerRegistry(); + } +} diff --git a/resilience4j-spring-boot/src/main/java/io/github/resilience4j/bulkhead/autoconfigure/BulkheadMetricsAutoConfiguration.java b/resilience4j-spring-boot/src/main/java/io/github/resilience4j/bulkhead/autoconfigure/BulkheadMetricsAutoConfiguration.java index f6bb97270e..1ee59f0f95 100644 --- a/resilience4j-spring-boot/src/main/java/io/github/resilience4j/bulkhead/autoconfigure/BulkheadMetricsAutoConfiguration.java +++ b/resilience4j-spring-boot/src/main/java/io/github/resilience4j/bulkhead/autoconfigure/BulkheadMetricsAutoConfiguration.java @@ -20,6 +20,7 @@ import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.AutoConfigureBefore; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -38,6 +39,7 @@ @AutoConfigureBefore(MetricRepositoryAutoConfiguration.class) public class BulkheadMetricsAutoConfiguration { @Bean + @ConditionalOnMissingBean public BulkheadMetrics registerBulkheadMetrics(BulkheadRegistry bulkheadRegistry, MetricRegistry metricRegistry) { BulkheadMetrics bulkheadMetrics = BulkheadMetrics.ofBulkheadRegistry(bulkheadRegistry); metricRegistry.registerAll(bulkheadMetrics); diff --git a/resilience4j-spring-boot/src/main/java/io/github/resilience4j/bulkhead/autoconfigure/BulkheadPrometheusAutoConfiguration.java b/resilience4j-spring-boot/src/main/java/io/github/resilience4j/bulkhead/autoconfigure/BulkheadPrometheusAutoConfiguration.java index 940f8ba52b..cd5951c765 100644 --- a/resilience4j-spring-boot/src/main/java/io/github/resilience4j/bulkhead/autoconfigure/BulkheadPrometheusAutoConfiguration.java +++ b/resilience4j-spring-boot/src/main/java/io/github/resilience4j/bulkhead/autoconfigure/BulkheadPrometheusAutoConfiguration.java @@ -15,15 +15,17 @@ */ package io.github.resilience4j.bulkhead.autoconfigure; -import io.github.resilience4j.bulkhead.BulkheadRegistry; -import io.github.resilience4j.prometheus.BulkheadExports; -import io.github.resilience4j.prometheus.collectors.BulkheadMetricsCollector; import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import io.github.resilience4j.bulkhead.BulkheadRegistry; +import io.github.resilience4j.prometheus.BulkheadExports; +import io.github.resilience4j.prometheus.collectors.BulkheadMetricsCollector; + /** * {@link org.springframework.boot.autoconfigure.EnableAutoConfiguration * Auto-configuration} for resilience4j-metrics. @@ -35,6 +37,7 @@ public class BulkheadPrometheusAutoConfiguration { @Bean @ConditionalOnProperty(value = "resilience4j.bulkhead.metrics.use_legacy_collector", havingValue = "true") + @ConditionalOnMissingBean public BulkheadExports legacyBulkheadPrometheusCollector(BulkheadRegistry bulkheadRegistry) { BulkheadExports collector = BulkheadExports.ofBulkheadRegistry(bulkheadRegistry); collector.register(); @@ -43,10 +46,11 @@ public BulkheadExports legacyBulkheadPrometheusCollector(BulkheadRegistry bulkhe @Bean @ConditionalOnProperty( - value = "resilience4j.bulkhead.metrics.use_legacy_collector", - havingValue = "false", - matchIfMissing = true + value = "resilience4j.bulkhead.metrics.use_legacy_collector", + havingValue = "false", + matchIfMissing = true ) + @ConditionalOnMissingBean public BulkheadMetricsCollector bulkheadPrometheusCollector(BulkheadRegistry bulkheadRegistry) { BulkheadMetricsCollector collector = BulkheadMetricsCollector.ofBulkheadRegistry(bulkheadRegistry); collector.register(); diff --git a/resilience4j-spring-boot/src/main/java/io/github/resilience4j/circuitbreaker/autoconfigure/CircuitBreakerAutoConfiguration.java b/resilience4j-spring-boot/src/main/java/io/github/resilience4j/circuitbreaker/autoconfigure/CircuitBreakerAutoConfiguration.java index 7930da4dd4..0dffc92c10 100644 --- a/resilience4j-spring-boot/src/main/java/io/github/resilience4j/circuitbreaker/autoconfigure/CircuitBreakerAutoConfiguration.java +++ b/resilience4j-spring-boot/src/main/java/io/github/resilience4j/circuitbreaker/autoconfigure/CircuitBreakerAutoConfiguration.java @@ -15,15 +15,6 @@ */ package io.github.resilience4j.circuitbreaker.autoconfigure; -import io.github.resilience4j.circuitbreaker.CircuitBreaker; -import io.github.resilience4j.circuitbreaker.CircuitBreakerRegistry; -import io.github.resilience4j.circuitbreaker.configure.CircuitBreakerConfiguration; -import io.github.resilience4j.circuitbreaker.event.CircuitBreakerEvent; -import io.github.resilience4j.circuitbreaker.monitoring.endpoint.CircuitBreakerEndpoint; -import io.github.resilience4j.circuitbreaker.monitoring.endpoint.CircuitBreakerEventsEndpoint; -import io.github.resilience4j.circuitbreaker.monitoring.health.CircuitBreakerHealthIndicator; -import io.github.resilience4j.consumer.EventConsumerRegistry; -import org.springframework.beans.factory.config.ConfigurableBeanFactory; import org.springframework.boot.actuate.autoconfigure.EndpointAutoConfiguration; import org.springframework.boot.autoconfigure.AutoConfigureBefore; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; @@ -32,7 +23,12 @@ import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; -import javax.annotation.PostConstruct; +import io.github.resilience4j.circuitbreaker.CircuitBreaker; +import io.github.resilience4j.circuitbreaker.CircuitBreakerRegistry; +import io.github.resilience4j.circuitbreaker.event.CircuitBreakerEvent; +import io.github.resilience4j.circuitbreaker.monitoring.endpoint.CircuitBreakerEndpoint; +import io.github.resilience4j.circuitbreaker.monitoring.endpoint.CircuitBreakerEventsEndpoint; +import io.github.resilience4j.consumer.EventConsumerRegistry; /** * {@link org.springframework.boot.autoconfigure.EnableAutoConfiguration @@ -41,20 +37,10 @@ @Configuration @ConditionalOnClass(CircuitBreaker.class) @EnableConfigurationProperties(CircuitBreakerProperties.class) -@Import(CircuitBreakerConfiguration.class) +@Import(CircuitBreakerConfigurationOnMissingBean.class) @AutoConfigureBefore(EndpointAutoConfiguration.class) public class CircuitBreakerAutoConfiguration { - private final CircuitBreakerProperties circuitBreakerProperties; - private final CircuitBreakerRegistry circuitBreakerRegistry; - private final ConfigurableBeanFactory beanFactory; - - public CircuitBreakerAutoConfiguration(CircuitBreakerProperties circuitBreakerProperties, CircuitBreakerRegistry circuitBreakerRegistry, ConfigurableBeanFactory beanFactory) { - this.circuitBreakerProperties = circuitBreakerProperties; - this.circuitBreakerRegistry = circuitBreakerRegistry; - this.beanFactory = beanFactory; - } - @Bean public CircuitBreakerEndpoint circuitBreakerEndpoint(CircuitBreakerRegistry circuitBreakerRegistry) { return new CircuitBreakerEndpoint(circuitBreakerRegistry); @@ -65,20 +51,4 @@ public CircuitBreakerEventsEndpoint circuitBreakerEventsEndpoint(EventConsumerRe return new CircuitBreakerEventsEndpoint(eventConsumerRegistry); } - @PostConstruct - public void configureRegistryWithHealthEndpoint(){ - circuitBreakerProperties.getBackends().forEach( - (name, properties) -> { - if (properties.getRegisterHealthIndicator()) { - CircuitBreaker circuitBreaker = circuitBreakerRegistry.circuitBreaker(name); - CircuitBreakerHealthIndicator healthIndicator = new CircuitBreakerHealthIndicator(circuitBreaker); - beanFactory.registerSingleton( - name + "CircuitBreakerHealthIndicator", - healthIndicator - ); - } - } - ); - } - - } +} diff --git a/resilience4j-spring-boot/src/main/java/io/github/resilience4j/circuitbreaker/autoconfigure/CircuitBreakerConfigurationOnMissingBean.java b/resilience4j-spring-boot/src/main/java/io/github/resilience4j/circuitbreaker/autoconfigure/CircuitBreakerConfigurationOnMissingBean.java new file mode 100644 index 0000000000..14d43276c2 --- /dev/null +++ b/resilience4j-spring-boot/src/main/java/io/github/resilience4j/circuitbreaker/autoconfigure/CircuitBreakerConfigurationOnMissingBean.java @@ -0,0 +1,57 @@ +/* + * Copyright 2019 Mahmoud Romeh + * + * 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 + * + * http://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 io.github.resilience4j.circuitbreaker.autoconfigure; + +import org.springframework.beans.factory.config.ConfigurableBeanFactory; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import io.github.resilience4j.circuitbreaker.CircuitBreaker; +import io.github.resilience4j.circuitbreaker.configure.CircuitBreakerConfigurationProperties; +import io.github.resilience4j.circuitbreaker.configure.CircuitBreakerConfigurationProperties.BackendProperties; +import io.github.resilience4j.circuitbreaker.event.CircuitBreakerEvent; +import io.github.resilience4j.circuitbreaker.monitoring.health.CircuitBreakerHealthIndicator; +import io.github.resilience4j.consumer.EventConsumerRegistry; +import io.github.resilience4j.springboot.common.circuitbreaker.autoconfigure.AbstractCircuitBreakerConfigurationOnMissingBean; + +@Configuration +public class CircuitBreakerConfigurationOnMissingBean extends AbstractCircuitBreakerConfigurationOnMissingBean { + + private final ConfigurableBeanFactory beanFactory; + + public CircuitBreakerConfigurationOnMissingBean(CircuitBreakerConfigurationProperties circuitBreakerProperties, + ConfigurableBeanFactory beanFactory) { + super(circuitBreakerProperties); + this.beanFactory = beanFactory; + } + + @Bean + public EventConsumerRegistry eventConsumerRegistry() { + return circuitBreakerConfiguration.eventConsumerRegistry(); + } + + protected void createHeathIndicatorForCircuitBreaker(CircuitBreaker circuitBreaker) { + BackendProperties backendProperties = circuitBreakerProperties.findCircuitBreakerBackend(circuitBreaker, circuitBreaker.getCircuitBreakerConfig()); + + if (backendProperties != null && backendProperties.getRegisterHealthIndicator()) { + CircuitBreakerHealthIndicator healthIndicator = new CircuitBreakerHealthIndicator(circuitBreaker); + beanFactory.registerSingleton( + circuitBreaker.getName() + "CircuitBreakerHealthIndicator", + healthIndicator + ); + } + } +} diff --git a/resilience4j-spring-boot/src/main/java/io/github/resilience4j/circuitbreaker/autoconfigure/CircuitBreakerMetricsAutoConfiguration.java b/resilience4j-spring-boot/src/main/java/io/github/resilience4j/circuitbreaker/autoconfigure/CircuitBreakerMetricsAutoConfiguration.java index 282b576ad0..0986999fb6 100644 --- a/resilience4j-spring-boot/src/main/java/io/github/resilience4j/circuitbreaker/autoconfigure/CircuitBreakerMetricsAutoConfiguration.java +++ b/resilience4j-spring-boot/src/main/java/io/github/resilience4j/circuitbreaker/autoconfigure/CircuitBreakerMetricsAutoConfiguration.java @@ -15,19 +15,19 @@ */ package io.github.resilience4j.circuitbreaker.autoconfigure; +import io.github.resilience4j.circuitbreaker.CircuitBreakerRegistry; +import io.github.resilience4j.metrics.CircuitBreakerMetrics; import org.springframework.boot.actuate.autoconfigure.MetricRepositoryAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.MetricsDropwizardAutoConfiguration; import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.AutoConfigureBefore; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import com.codahale.metrics.MetricRegistry; -import io.github.resilience4j.circuitbreaker.CircuitBreakerRegistry; -import io.github.resilience4j.metrics.CircuitBreakerMetrics; - /** * {@link org.springframework.boot.autoconfigure.EnableAutoConfiguration * Auto-configuration} for resilience4j-metrics. @@ -38,6 +38,7 @@ @AutoConfigureBefore(MetricRepositoryAutoConfiguration.class) public class CircuitBreakerMetricsAutoConfiguration { @Bean + @ConditionalOnMissingBean public CircuitBreakerMetrics registerCircuitBreakerMetrics(CircuitBreakerRegistry circuitBreakerRegistry, MetricRegistry metricRegistry) { CircuitBreakerMetrics circuitBreakerMetrics = CircuitBreakerMetrics.ofCircuitBreakerRegistry(circuitBreakerRegistry); metricRegistry.registerAll(circuitBreakerMetrics); diff --git a/resilience4j-spring-boot/src/main/java/io/github/resilience4j/circuitbreaker/autoconfigure/CircuitBreakerPrometheusAutoConfiguration.java b/resilience4j-spring-boot/src/main/java/io/github/resilience4j/circuitbreaker/autoconfigure/CircuitBreakerPrometheusAutoConfiguration.java index dcedb1cc58..ed23b4b77d 100644 --- a/resilience4j-spring-boot/src/main/java/io/github/resilience4j/circuitbreaker/autoconfigure/CircuitBreakerPrometheusAutoConfiguration.java +++ b/resilience4j-spring-boot/src/main/java/io/github/resilience4j/circuitbreaker/autoconfigure/CircuitBreakerPrometheusAutoConfiguration.java @@ -20,6 +20,7 @@ import io.github.resilience4j.prometheus.collectors.CircuitBreakerMetricsCollector; import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -34,6 +35,7 @@ public class CircuitBreakerPrometheusAutoConfiguration { @Bean + @ConditionalOnMissingBean @ConditionalOnProperty(value = "resilience4j.circuitbreaker.metrics.use_legacy_collector", havingValue = "true") public CircuitBreakerExports legacyCircuitBreakerPrometheusCollector(CircuitBreakerRegistry circuitBreakerRegistry) { CircuitBreakerExports collector = CircuitBreakerExports.ofCircuitBreakerRegistry(circuitBreakerRegistry); @@ -42,6 +44,7 @@ public CircuitBreakerExports legacyCircuitBreakerPrometheusCollector(CircuitBrea } @Bean + @ConditionalOnMissingBean @ConditionalOnProperty( value = "resilience4j.circuitbreaker.metrics.use_legacy_collector", havingValue = "false", diff --git a/resilience4j-spring-boot/src/main/java/io/github/resilience4j/ratelimiter/autoconfigure/RateLimiterAutoConfiguration.java b/resilience4j-spring-boot/src/main/java/io/github/resilience4j/ratelimiter/autoconfigure/RateLimiterAutoConfiguration.java index cf891aa333..525130d0e9 100644 --- a/resilience4j-spring-boot/src/main/java/io/github/resilience4j/ratelimiter/autoconfigure/RateLimiterAutoConfiguration.java +++ b/resilience4j-spring-boot/src/main/java/io/github/resilience4j/ratelimiter/autoconfigure/RateLimiterAutoConfiguration.java @@ -15,14 +15,8 @@ */ package io.github.resilience4j.ratelimiter.autoconfigure; -import io.github.resilience4j.consumer.EventConsumerRegistry; -import io.github.resilience4j.ratelimiter.RateLimiter; -import io.github.resilience4j.ratelimiter.RateLimiterRegistry; -import io.github.resilience4j.ratelimiter.configure.RateLimiterConfiguration; -import io.github.resilience4j.ratelimiter.event.RateLimiterEvent; -import io.github.resilience4j.ratelimiter.monitoring.endpoint.RateLimiterEndpoint; -import io.github.resilience4j.ratelimiter.monitoring.endpoint.RateLimiterEventsEndpoint; -import io.github.resilience4j.ratelimiter.monitoring.health.RateLimiterHealthIndicator; +import javax.annotation.PostConstruct; + import org.springframework.beans.factory.config.ConfigurableBeanFactory; import org.springframework.boot.actuate.autoconfigure.EndpointAutoConfiguration; import org.springframework.boot.autoconfigure.AutoConfigureBefore; @@ -32,7 +26,13 @@ import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; -import javax.annotation.PostConstruct; +import io.github.resilience4j.consumer.EventConsumerRegistry; +import io.github.resilience4j.ratelimiter.RateLimiter; +import io.github.resilience4j.ratelimiter.RateLimiterRegistry; +import io.github.resilience4j.ratelimiter.event.RateLimiterEvent; +import io.github.resilience4j.ratelimiter.monitoring.endpoint.RateLimiterEndpoint; +import io.github.resilience4j.ratelimiter.monitoring.endpoint.RateLimiterEventsEndpoint; +import io.github.resilience4j.ratelimiter.monitoring.health.RateLimiterHealthIndicator; /** * {@link org.springframework.boot.autoconfigure.EnableAutoConfiguration @@ -41,7 +41,7 @@ @Configuration @ConditionalOnClass(RateLimiter.class) @EnableConfigurationProperties(RateLimiterProperties.class) -@Import(RateLimiterConfiguration.class) +@Import(RateLimiterConfigurationOnMissingBean.class) @AutoConfigureBefore(EndpointAutoConfiguration.class) public class RateLimiterAutoConfiguration { private final RateLimiterProperties rateLimiterProperties; diff --git a/resilience4j-spring-boot/src/main/java/io/github/resilience4j/ratelimiter/autoconfigure/RateLimiterConfigurationOnMissingBean.java b/resilience4j-spring-boot/src/main/java/io/github/resilience4j/ratelimiter/autoconfigure/RateLimiterConfigurationOnMissingBean.java new file mode 100644 index 0000000000..752ba30fa6 --- /dev/null +++ b/resilience4j-spring-boot/src/main/java/io/github/resilience4j/ratelimiter/autoconfigure/RateLimiterConfigurationOnMissingBean.java @@ -0,0 +1,31 @@ +/* + * Copyright 2019 Mahmoud Romeh + * + * 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 + * + * http://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 io.github.resilience4j.ratelimiter.autoconfigure; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import io.github.resilience4j.consumer.EventConsumerRegistry; +import io.github.resilience4j.ratelimiter.event.RateLimiterEvent; +import io.github.resilience4j.springboot.common.ratelimiter.autoconfigure.AbstractRateLimiterConfigurationOnMissingBean; + +@Configuration +public class RateLimiterConfigurationOnMissingBean extends AbstractRateLimiterConfigurationOnMissingBean { + @Bean + public EventConsumerRegistry rateLimiterEventsConsumerRegistry() { + return rateLimiterConfiguration.rateLimiterEventsConsumerRegistry(); + } +} diff --git a/resilience4j-spring-boot/src/main/java/io/github/resilience4j/ratelimiter/autoconfigure/RateLimiterMetricsAutoConfiguration.java b/resilience4j-spring-boot/src/main/java/io/github/resilience4j/ratelimiter/autoconfigure/RateLimiterMetricsAutoConfiguration.java index 3cf3fe4c84..4b8552d42c 100644 --- a/resilience4j-spring-boot/src/main/java/io/github/resilience4j/ratelimiter/autoconfigure/RateLimiterMetricsAutoConfiguration.java +++ b/resilience4j-spring-boot/src/main/java/io/github/resilience4j/ratelimiter/autoconfigure/RateLimiterMetricsAutoConfiguration.java @@ -15,19 +15,19 @@ */ package io.github.resilience4j.ratelimiter.autoconfigure; +import io.github.resilience4j.metrics.RateLimiterMetrics; +import io.github.resilience4j.ratelimiter.RateLimiterRegistry; import org.springframework.boot.actuate.autoconfigure.MetricRepositoryAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.MetricsDropwizardAutoConfiguration; import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.AutoConfigureBefore; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import com.codahale.metrics.MetricRegistry; -import io.github.resilience4j.metrics.RateLimiterMetrics; -import io.github.resilience4j.ratelimiter.RateLimiterRegistry; - /** * {@link org.springframework.boot.autoconfigure.EnableAutoConfiguration * Auto-configuration} for resilience4j-metrics. @@ -37,7 +37,9 @@ @AutoConfigureAfter(value = {RateLimiterAutoConfiguration.class, MetricsDropwizardAutoConfiguration.class}) @AutoConfigureBefore(MetricRepositoryAutoConfiguration.class) public class RateLimiterMetricsAutoConfiguration { + @Bean + @ConditionalOnMissingBean public RateLimiterMetrics registerRateLimiterMetrics(RateLimiterRegistry rateLimiterRegistry, MetricRegistry metricRegistry) { RateLimiterMetrics rateLimiterMetrics = RateLimiterMetrics.ofRateLimiterRegistry(rateLimiterRegistry); metricRegistry.registerAll(rateLimiterMetrics); diff --git a/resilience4j-spring-boot/src/main/java/io/github/resilience4j/ratelimiter/autoconfigure/RateLimiterPrometheusAutoConfiguration.java b/resilience4j-spring-boot/src/main/java/io/github/resilience4j/ratelimiter/autoconfigure/RateLimiterPrometheusAutoConfiguration.java index 663fff8cb3..6d0cf34bf6 100644 --- a/resilience4j-spring-boot/src/main/java/io/github/resilience4j/ratelimiter/autoconfigure/RateLimiterPrometheusAutoConfiguration.java +++ b/resilience4j-spring-boot/src/main/java/io/github/resilience4j/ratelimiter/autoconfigure/RateLimiterPrometheusAutoConfiguration.java @@ -20,6 +20,7 @@ import io.github.resilience4j.ratelimiter.RateLimiterRegistry; import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -35,6 +36,7 @@ public class RateLimiterPrometheusAutoConfiguration { @ConditionalOnProperty(value = "resilience4j.ratelimiter.metrics.use_legacy_collector", havingValue = "true") @Bean + @ConditionalOnMissingBean public RateLimiterExports legacyRateLimiterPrometheusCollector(RateLimiterRegistry rateLimiterRegistry){ RateLimiterExports collector = RateLimiterExports.ofRateLimiterRegistry(rateLimiterRegistry); collector.register(); @@ -42,6 +44,7 @@ public RateLimiterExports legacyRateLimiterPrometheusCollector(RateLimiterRegist } @Bean + @ConditionalOnMissingBean @ConditionalOnProperty( value = "resilience4j.ratelimiter.metrics.use_legacy_collector", havingValue = "false", diff --git a/resilience4j-spring-boot/src/main/java/io/github/resilience4j/retry/autoconfigure/RetryAutoConfiguration.java b/resilience4j-spring-boot/src/main/java/io/github/resilience4j/retry/autoconfigure/RetryAutoConfiguration.java index 5152a1c058..eba2f8822a 100644 --- a/resilience4j-spring-boot/src/main/java/io/github/resilience4j/retry/autoconfigure/RetryAutoConfiguration.java +++ b/resilience4j-spring-boot/src/main/java/io/github/resilience4j/retry/autoconfigure/RetryAutoConfiguration.java @@ -15,6 +15,8 @@ */ package io.github.resilience4j.retry.autoconfigure; +import org.springframework.boot.actuate.autoconfigure.EndpointAutoConfiguration; +import org.springframework.boot.autoconfigure.AutoConfigureBefore; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; @@ -24,7 +26,6 @@ import io.github.resilience4j.consumer.EventConsumerRegistry; import io.github.resilience4j.retry.Retry; import io.github.resilience4j.retry.RetryRegistry; -import io.github.resilience4j.retry.configure.RetryConfiguration; import io.github.resilience4j.retry.event.RetryEvent; import io.github.resilience4j.retry.monitoring.endpoint.RetryEndpoint; import io.github.resilience4j.retry.monitoring.endpoint.RetryEventsEndpoint; @@ -37,7 +38,8 @@ @Configuration @ConditionalOnClass(Retry.class) @EnableConfigurationProperties(RetryProperties.class) -@Import(RetryConfiguration.class) +@Import(RetryConfigurationOnMissingBean.class) +@AutoConfigureBefore(EndpointAutoConfiguration.class) public class RetryAutoConfiguration { @Bean diff --git a/resilience4j-spring-boot/src/main/java/io/github/resilience4j/retry/autoconfigure/RetryConfigurationOnMissingBean.java b/resilience4j-spring-boot/src/main/java/io/github/resilience4j/retry/autoconfigure/RetryConfigurationOnMissingBean.java new file mode 100644 index 0000000000..eba6407d12 --- /dev/null +++ b/resilience4j-spring-boot/src/main/java/io/github/resilience4j/retry/autoconfigure/RetryConfigurationOnMissingBean.java @@ -0,0 +1,44 @@ +/* + * Copyright 2019 Mahmoud Romeh + * + * 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 + * + * http://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 io.github.resilience4j.retry.autoconfigure; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import io.github.resilience4j.consumer.DefaultEventConsumerRegistry; +import io.github.resilience4j.consumer.EventConsumerRegistry; +import io.github.resilience4j.retry.event.RetryEvent; +import io.github.resilience4j.springboot.common.retry.autoconfigure.AbstractRetryConfigurationOnMissingBean; + +/** + * {@link Configuration + * Configuration} for resilience4j-retry. + */ +@Configuration +public class RetryConfigurationOnMissingBean extends AbstractRetryConfigurationOnMissingBean { + /** + * The EventConsumerRegistry is used to manage EventConsumer instances. + * The EventConsumerRegistry is used by the Retry events monitor to show the latest RetryEvent events + * for each Retry instance. + * + * @return a default EventConsumerRegistry {@link DefaultEventConsumerRegistry} + */ + @Bean + public EventConsumerRegistry retryEventConsumerRegistry() { + return retryConfiguration.retryEventConsumerRegistry(); + } + +} diff --git a/resilience4j-spring-boot/src/main/java/io/github/resilience4j/retry/autoconfigure/RetryMetricsAutoConfiguration.java b/resilience4j-spring-boot/src/main/java/io/github/resilience4j/retry/autoconfigure/RetryMetricsAutoConfiguration.java index 7d04c801b1..64e9fca5f9 100644 --- a/resilience4j-spring-boot/src/main/java/io/github/resilience4j/retry/autoconfigure/RetryMetricsAutoConfiguration.java +++ b/resilience4j-spring-boot/src/main/java/io/github/resilience4j/retry/autoconfigure/RetryMetricsAutoConfiguration.java @@ -20,6 +20,7 @@ import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.AutoConfigureBefore; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -40,6 +41,7 @@ public class RetryMetricsAutoConfiguration { @Bean + @ConditionalOnMissingBean public RetryMetrics registerRetryMetrics(RetryRegistry retryRegistry, MetricRegistry metricRegistry) { RetryMetrics retryMetrics = RetryMetrics.ofRetryRegistry(retryRegistry); metricRegistry.registerAll(retryMetrics); diff --git a/resilience4j-spring-boot/src/test/java/io/github/resilience4j/bulkhead/autoconfigure/BulkheadConfigurationOnMissingBeanTest.java b/resilience4j-spring-boot/src/test/java/io/github/resilience4j/bulkhead/autoconfigure/BulkheadConfigurationOnMissingBeanTest.java new file mode 100644 index 0000000000..fa36d9c8ed --- /dev/null +++ b/resilience4j-spring-boot/src/test/java/io/github/resilience4j/bulkhead/autoconfigure/BulkheadConfigurationOnMissingBeanTest.java @@ -0,0 +1,118 @@ +/* + * Copyright 2019 Mahmoud Romeh + * + * 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 + * + * http://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 io.github.resilience4j.bulkhead.autoconfigure; + +import static org.assertj.core.api.BDDAssertions.assertThat; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; + +import java.lang.reflect.Method; +import java.util.List; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +import io.github.resilience4j.bulkhead.BulkheadRegistry; +import io.github.resilience4j.bulkhead.configure.BulkheadAspect; +import io.github.resilience4j.bulkhead.configure.BulkheadAspectExt; +import io.github.resilience4j.bulkhead.configure.BulkheadConfiguration; +import io.github.resilience4j.bulkhead.event.BulkheadEvent; +import io.github.resilience4j.consumer.DefaultEventConsumerRegistry; +import io.github.resilience4j.consumer.EventConsumerRegistry; + +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration(classes = { + BulkheadConfigurationOnMissingBeanTest.ConfigWithOverrides.class, + BulkheadAutoConfiguration.class, + BulkheadConfigurationOnMissingBean.class +}) +@EnableConfigurationProperties(BulkheadProperties.class) +public class BulkheadConfigurationOnMissingBeanTest { + + @Autowired + private ConfigWithOverrides configWithOverrides; + + @Autowired + private BulkheadRegistry bulkheadRegistry; + + @Autowired + private BulkheadAspect bulkheadAspect; + + @Autowired + private EventConsumerRegistry bulkheadEventEventConsumerRegistry; + + @Test + public void testAllBeansFromBulkHeadHasOnMissingBean() throws NoSuchMethodException { + final Class originalClass = BulkheadConfiguration.class; + final Class onMissingBeanClass = BulkheadConfigurationOnMissingBean.class; + + for (Method methodBulkheadConfiguration : originalClass.getMethods()) { + if (methodBulkheadConfiguration.isAnnotationPresent(Bean.class)) { + final Method methodOnMissing = onMissingBeanClass + .getMethod(methodBulkheadConfiguration.getName(), methodBulkheadConfiguration.getParameterTypes()); + + assertThat(methodOnMissing.isAnnotationPresent(Bean.class)).isTrue(); + + if (!methodOnMissing.getName().equals("bulkheadEventConsumerRegistry")) { + assertThat(methodOnMissing.isAnnotationPresent(ConditionalOnMissingBean.class)).isTrue(); + } + } + } + } + + @Test + public void testAllBulkHeadConfigurationBeansOverridden() { + assertEquals(bulkheadRegistry, configWithOverrides.bulkheadRegistry); + assertEquals(bulkheadAspect, configWithOverrides.bulkheadAspect); + assertNotEquals(bulkheadEventEventConsumerRegistry, configWithOverrides.bulkheadEventEventConsumerRegistry); + } + + @Configuration + public static class ConfigWithOverrides { + + private BulkheadRegistry bulkheadRegistry; + + private BulkheadAspect bulkheadAspect; + + private EventConsumerRegistry bulkheadEventEventConsumerRegistry; + + @Bean + public BulkheadRegistry bulkheadRegistry() { + bulkheadRegistry = BulkheadRegistry.ofDefaults(); + return bulkheadRegistry; + } + + @Bean + public BulkheadAspect bulkheadAspect(BulkheadRegistry bulkheadRegistry, + @Autowired(required = false) List bulkheadAspectExts) { + bulkheadAspect = new BulkheadAspect(new BulkheadProperties(), bulkheadRegistry, bulkheadAspectExts); + return bulkheadAspect; + } + + @Bean + public EventConsumerRegistry bulkheadEventConsumerRegistry() { + bulkheadEventEventConsumerRegistry = new DefaultEventConsumerRegistry<>(); + return bulkheadEventEventConsumerRegistry; + } + } +} \ No newline at end of file diff --git a/resilience4j-spring-boot/src/test/java/io/github/resilience4j/circuitbreaker/CircuitBreakerAutoConfigurationTest.java b/resilience4j-spring-boot/src/test/java/io/github/resilience4j/circuitbreaker/CircuitBreakerAutoConfigurationTest.java index 8195faffb1..6f0003cd7d 100644 --- a/resilience4j-spring-boot/src/test/java/io/github/resilience4j/circuitbreaker/CircuitBreakerAutoConfigurationTest.java +++ b/resilience4j-spring-boot/src/test/java/io/github/resilience4j/circuitbreaker/CircuitBreakerAutoConfigurationTest.java @@ -15,97 +15,138 @@ */ package io.github.resilience4j.circuitbreaker; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.Assert.assertEquals; + +import java.io.IOException; +import java.time.Duration; + +import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.web.client.TestRestTemplate; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; import org.springframework.http.ResponseEntity; +import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; -import java.io.IOException; - import io.github.resilience4j.circuitbreaker.autoconfigure.CircuitBreakerProperties; import io.github.resilience4j.circuitbreaker.configure.CircuitBreakerAspect; import io.github.resilience4j.circuitbreaker.monitoring.endpoint.CircuitBreakerEndpointResponse; import io.github.resilience4j.circuitbreaker.monitoring.endpoint.CircuitBreakerEventsEndpointResponse; import io.github.resilience4j.service.test.DummyService; import io.github.resilience4j.service.test.TestApplication; - -import static org.assertj.core.api.Assertions.assertThat; +import io.prometheus.client.CollectorRegistry; @RunWith(SpringJUnit4ClassRunner.class) @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, - classes = TestApplication.class) + classes = TestApplication.class) +@ContextConfiguration(classes = CircuitBreakerAutoConfigurationTest.AdditionalConfiguration.class) public class CircuitBreakerAutoConfigurationTest { - @Autowired - CircuitBreakerRegistry circuitBreakerRegistry; - - @Autowired - CircuitBreakerProperties circuitBreakerProperties; - - @Autowired - CircuitBreakerAspect circuitBreakerAspect; - - @Autowired - @Qualifier("circuitBreakerDummyService") - DummyService dummyService; - - @Autowired - private TestRestTemplate restTemplate; - - /** - * The test verifies that a CircuitBreaker instance is created and configured properly when the DummyService is invoked and - * that the CircuitBreaker records successful and failed calls. - */ - @Test - public void testCircuitBreakerAutoConfiguration() throws IOException { - assertThat(circuitBreakerRegistry).isNotNull(); - assertThat(circuitBreakerProperties).isNotNull(); - - try { - dummyService.doSomething(true); - } catch (IOException ex) { - // Do nothing. The IOException is recorded by the CircuitBreaker as part of the recordFailurePredicate as a failure. - } - // The invocation is recorded by the CircuitBreaker as a success. - dummyService.doSomething(false); - - CircuitBreaker circuitBreaker = circuitBreakerRegistry.circuitBreaker(DummyService.BACKEND); - assertThat(circuitBreaker).isNotNull(); - - assertThat(circuitBreaker.getMetrics().getNumberOfBufferedCalls()).isEqualTo(2); - assertThat(circuitBreaker.getMetrics().getNumberOfSuccessfulCalls()).isEqualTo(1); - assertThat(circuitBreaker.getMetrics().getNumberOfFailedCalls()).isEqualTo(1); - - assertThat(circuitBreaker.getCircuitBreakerConfig().getRingBufferSizeInClosedState()).isEqualTo(6); - assertThat(circuitBreaker.getCircuitBreakerConfig().getRingBufferSizeInHalfOpenState()).isEqualTo(2); - assertThat(circuitBreaker.getCircuitBreakerConfig().getFailureRateThreshold()).isEqualTo(70f); - - // Test Actuator endpoints - - ResponseEntity circuitBreakerList = restTemplate.getForEntity("/circuitbreaker", CircuitBreakerEndpointResponse.class); - assertThat(circuitBreakerList.getBody().getCircuitBreakers()).hasSize(2).containsExactly("backendA", "backendB"); - - - ResponseEntity circuitBreakerEventList = restTemplate.getForEntity("/circuitbreaker/events", CircuitBreakerEventsEndpointResponse.class); - assertThat(circuitBreakerEventList.getBody().getCircuitBreakerEvents()).hasSize(2); - - assertThat(circuitBreaker.getCircuitBreakerConfig().getRecordFailurePredicate().test(new RecordedException())).isTrue(); - assertThat(circuitBreaker.getCircuitBreakerConfig().getRecordFailurePredicate().test(new IgnoredException())).isFalse(); - - // expect no health indicator for backendB, as it is disabled via properties - ResponseEntity healthResponse = restTemplate.getForEntity("/health", String.class); - assertThat(healthResponse.getBody()).isNotNull(); - assertThat(healthResponse.getBody()).contains("backendACircuitBreaker"); - assertThat(healthResponse.getBody()).doesNotContain("backendBCircuitBreaker"); - - // Verify that an exception for which recordFailurePredicate returns false and it is not included in - // recordExceptions evaluates to false. - assertThat(circuitBreaker.getCircuitBreakerConfig().getRecordFailurePredicate().test(new Exception())).isFalse(); - - assertThat(circuitBreakerAspect.getOrder()).isEqualTo(400); - } + @Configuration + public static class AdditionalConfiguration { + + // Shows that a circuit breaker can be created in code and still use the shared configuration. + @Bean + public CircuitBreaker otherCircuitBreaker(CircuitBreakerRegistry registry, CircuitBreakerProperties properties) { + return registry.circuitBreaker("backendSharedC", properties.createCircuitBreakerConfigFrom("default")); + } + } + + @Autowired + CircuitBreakerRegistry circuitBreakerRegistry; + + @Autowired + CircuitBreakerProperties circuitBreakerProperties; + + @Autowired + CircuitBreakerAspect circuitBreakerAspect; + + @Autowired + @Qualifier("circuitBreakerDummyService") + DummyService dummyService; + + @Autowired + private TestRestTemplate restTemplate; + + @BeforeClass + public static void setUp() { + // Need to clear this static registry out since multiple tests register collectors that could collide. + CollectorRegistry.defaultRegistry.clear(); + } + + /** + * The test verifies that a CircuitBreaker instance is created and configured properly when the DummyService is invoked and + * that the CircuitBreaker records successful and failed calls. + */ + @Test + public void testCircuitBreakerAutoConfiguration() throws IOException { + assertThat(circuitBreakerRegistry).isNotNull(); + assertThat(circuitBreakerProperties).isNotNull(); + + try { + dummyService.doSomething(true); + } catch (IOException ex) { + // Do nothing. The IOException is recorded by the CircuitBreaker as part of the recordFailurePredicate as a failure. + } + // The invocation is recorded by the CircuitBreaker as a success. + dummyService.doSomething(false); + + CircuitBreaker circuitBreaker = circuitBreakerRegistry.circuitBreaker(DummyService.BACKEND); + assertThat(circuitBreaker).isNotNull(); + + assertThat(circuitBreaker.getMetrics().getNumberOfBufferedCalls()).isEqualTo(2); + assertThat(circuitBreaker.getMetrics().getNumberOfSuccessfulCalls()).isEqualTo(1); + assertThat(circuitBreaker.getMetrics().getNumberOfFailedCalls()).isEqualTo(1); + + assertThat(circuitBreaker.getCircuitBreakerConfig().getRingBufferSizeInClosedState()).isEqualTo(6); + assertThat(circuitBreaker.getCircuitBreakerConfig().getRingBufferSizeInHalfOpenState()).isEqualTo(2); + assertThat(circuitBreaker.getCircuitBreakerConfig().getFailureRateThreshold()).isEqualTo(70f); + + // Test Actuator endpoints + + ResponseEntity circuitBreakerList = restTemplate.getForEntity("/circuitbreaker", CircuitBreakerEndpointResponse.class); + assertThat(circuitBreakerList.getBody().getCircuitBreakers()).hasSize(5).containsExactly("backendA", "backendB", "backendSharedA", "backendSharedB", "backendSharedC"); + + + ResponseEntity circuitBreakerEventList = restTemplate.getForEntity("/circuitbreaker/events", CircuitBreakerEventsEndpointResponse.class); + assertThat(circuitBreakerEventList.getBody().getCircuitBreakerEvents()).hasSize(2); + + assertThat(circuitBreaker.getCircuitBreakerConfig().getRecordFailurePredicate().test(new RecordedException())).isTrue(); + assertThat(circuitBreaker.getCircuitBreakerConfig().getRecordFailurePredicate().test(new IgnoredException())).isFalse(); + + // expect no health indicator for backendB, as it is disabled via properties + ResponseEntity healthResponse = restTemplate.getForEntity("/health", String.class); + assertThat(healthResponse.getBody()).isNotNull(); + assertThat(healthResponse.getBody()).contains("backendACircuitBreaker"); + assertThat(healthResponse.getBody()).doesNotContain("backendBCircuitBreaker"); + + // Verify that an exception for which recordFailurePredicate returns false and it is not included in + // recordExceptions evaluates to false. + assertThat(circuitBreaker.getCircuitBreakerConfig().getRecordFailurePredicate().test(new Exception())).isFalse(); + + assertThat(circuitBreakerAspect.getOrder()).isEqualTo(400); + + // expect all shared configs share the same values and are from the application.yml file + CircuitBreaker sharedA = circuitBreakerRegistry.circuitBreaker("backendSharedA"); + CircuitBreaker sharedB = circuitBreakerRegistry.circuitBreaker("backendSharedB"); + CircuitBreaker sharedC = circuitBreakerRegistry.circuitBreaker("backendSharedC"); + assertThat(sharedA.getCircuitBreakerConfig().getRingBufferSizeInClosedState()).isEqualTo(6); + assertThat(sharedA.getCircuitBreakerConfig().getRingBufferSizeInHalfOpenState()).isEqualTo(10); + assertThat(sharedA.getCircuitBreakerConfig().getFailureRateThreshold()).isEqualTo(60f); + assertThat(sharedA.getCircuitBreakerConfig().getWaitDurationInOpenState()).isEqualByComparingTo(Duration.ofSeconds(10L)); + assertEquals(sharedA.getCircuitBreakerConfig().getRingBufferSizeInClosedState(), sharedB.getCircuitBreakerConfig().getRingBufferSizeInClosedState()); + assertEquals(sharedA.getCircuitBreakerConfig().getRingBufferSizeInHalfOpenState(), sharedB.getCircuitBreakerConfig().getRingBufferSizeInHalfOpenState()); + assertThat(sharedB.getCircuitBreakerConfig().getFailureRateThreshold()).isEqualTo(60f); + assertEquals(sharedA.getCircuitBreakerConfig().getWaitDurationInOpenState(), sharedB.getCircuitBreakerConfig().getWaitDurationInOpenState()); + assertEquals(sharedA.getCircuitBreakerConfig().getRingBufferSizeInClosedState(), sharedC.getCircuitBreakerConfig().getRingBufferSizeInClosedState()); + assertEquals(sharedA.getCircuitBreakerConfig().getRingBufferSizeInHalfOpenState(), sharedC.getCircuitBreakerConfig().getRingBufferSizeInHalfOpenState()); + assertThat(sharedC.getCircuitBreakerConfig().getFailureRateThreshold()).isEqualTo(60f); + assertEquals(sharedA.getCircuitBreakerConfig().getWaitDurationInOpenState(), sharedC.getCircuitBreakerConfig().getWaitDurationInOpenState()); + } } diff --git a/resilience4j-spring-boot/src/test/java/io/github/resilience4j/circuitbreaker/autoconfigure/CircuitBreakerConfigurationOnMissingBeanTest.java b/resilience4j-spring-boot/src/test/java/io/github/resilience4j/circuitbreaker/autoconfigure/CircuitBreakerConfigurationOnMissingBeanTest.java new file mode 100644 index 0000000000..c90aaededc --- /dev/null +++ b/resilience4j-spring-boot/src/test/java/io/github/resilience4j/circuitbreaker/autoconfigure/CircuitBreakerConfigurationOnMissingBeanTest.java @@ -0,0 +1,118 @@ +/* + * Copyright 2019 Mahmoud Romeh + * + * 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 + * + * http://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 io.github.resilience4j.circuitbreaker.autoconfigure; + +import static org.assertj.core.api.BDDAssertions.assertThat; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; + +import java.lang.reflect.Method; +import java.util.List; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +import io.github.resilience4j.circuitbreaker.CircuitBreakerRegistry; +import io.github.resilience4j.circuitbreaker.configure.CircuitBreakerAspect; +import io.github.resilience4j.circuitbreaker.configure.CircuitBreakerAspectExt; +import io.github.resilience4j.circuitbreaker.configure.CircuitBreakerConfiguration; +import io.github.resilience4j.circuitbreaker.event.CircuitBreakerEvent; +import io.github.resilience4j.consumer.DefaultEventConsumerRegistry; +import io.github.resilience4j.consumer.EventConsumerRegistry; + +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration(classes = { + CircuitBreakerConfigurationOnMissingBeanTest.ConfigWithOverrides.class, + CircuitBreakerAutoConfiguration.class, + CircuitBreakerConfigurationOnMissingBean.class +}) +@EnableConfigurationProperties(CircuitBreakerProperties.class) +public class CircuitBreakerConfigurationOnMissingBeanTest { + + @Autowired + private ConfigWithOverrides configWithOverrides; + + @Autowired + private CircuitBreakerRegistry circuitBreakerRegistry; + + @Autowired + private CircuitBreakerAspect circuitBreakerAspect; + + @Autowired + private EventConsumerRegistry circuitEventConsumerBreakerRegistry; + + @Test + public void testAllBeansFromCircuitBreakerConfigurationHasOnMissingBean() throws NoSuchMethodException { + final Class originalClass = CircuitBreakerConfiguration.class; + final Class onMissingBeanClass = CircuitBreakerConfigurationOnMissingBean.class; + + for (Method methodCircuitBreakerConfiguration : originalClass.getMethods()) { + if (methodCircuitBreakerConfiguration.isAnnotationPresent(Bean.class)) { + final Method methodOnMissing = onMissingBeanClass + .getMethod(methodCircuitBreakerConfiguration.getName(), methodCircuitBreakerConfiguration.getParameterTypes()); + + assertThat(methodOnMissing.isAnnotationPresent(Bean.class)).isTrue(); + + if (!methodOnMissing.getName().equals("eventConsumerRegistry")) { + assertThat(methodOnMissing.isAnnotationPresent(ConditionalOnMissingBean.class)).isTrue(); + } + } + } + } + + @Test + public void testAllCircuitBreakerConfigurationBeansOverridden() { + assertEquals(circuitBreakerRegistry, configWithOverrides.circuitBreakerRegistry); + assertEquals(circuitBreakerAspect, configWithOverrides.circuitBreakerAspect); + assertNotEquals(circuitEventConsumerBreakerRegistry, configWithOverrides.circuitEventConsumerBreakerRegistry); + } + + @Configuration + public static class ConfigWithOverrides { + + private CircuitBreakerRegistry circuitBreakerRegistry; + + private CircuitBreakerAspect circuitBreakerAspect; + + private EventConsumerRegistry circuitEventConsumerBreakerRegistry; + + @Bean + public CircuitBreakerRegistry circuitBreakerRegistry() { + circuitBreakerRegistry = CircuitBreakerRegistry.ofDefaults(); + return circuitBreakerRegistry; + } + + @Bean + public CircuitBreakerAspect circuitBreakerAspect(CircuitBreakerRegistry circuitBreakerRegistry, + @Autowired(required = false) List circuitBreakerAspectExtList) { + circuitBreakerAspect = new CircuitBreakerAspect(new CircuitBreakerProperties(), circuitBreakerRegistry, circuitBreakerAspectExtList); + return circuitBreakerAspect; + } + + @Bean + public EventConsumerRegistry eventConsumerRegistry() { + circuitEventConsumerBreakerRegistry = new DefaultEventConsumerRegistry<>(); + return circuitEventConsumerBreakerRegistry; + } + } +} \ No newline at end of file diff --git a/resilience4j-spring-boot/src/test/java/io/github/resilience4j/ratelimiter/RateLimiterAutoConfigurationTest.java b/resilience4j-spring-boot/src/test/java/io/github/resilience4j/ratelimiter/RateLimiterAutoConfigurationTest.java index e81c3a5ad2..df96f49d1f 100644 --- a/resilience4j-spring-boot/src/test/java/io/github/resilience4j/ratelimiter/RateLimiterAutoConfigurationTest.java +++ b/resilience4j-spring-boot/src/test/java/io/github/resilience4j/ratelimiter/RateLimiterAutoConfigurationTest.java @@ -23,6 +23,8 @@ import io.github.resilience4j.ratelimiter.monitoring.model.RateLimiterEventsEndpointResponse; import io.github.resilience4j.service.test.DummyService; import io.github.resilience4j.service.test.TestApplication; +import io.prometheus.client.CollectorRegistry; +import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; @@ -61,6 +63,12 @@ public class RateLimiterAutoConfigurationTest { @Autowired private TestRestTemplate restTemplate; + @BeforeClass + public static void setUp() { + // Need to clear this static registry out since multiple tests register collectors that could collide. + CollectorRegistry.defaultRegistry.clear(); + } + /** * The test verifies that a RateLimiter instance is created and configured properly when the DummyService is invoked and * that the RateLimiter records successful and failed calls. diff --git a/resilience4j-spring-boot/src/test/java/io/github/resilience4j/ratelimiter/autoconfigure/RateLimiterConfigurationOnMissingBeanTest.java b/resilience4j-spring-boot/src/test/java/io/github/resilience4j/ratelimiter/autoconfigure/RateLimiterConfigurationOnMissingBeanTest.java new file mode 100644 index 0000000000..74fbc359a2 --- /dev/null +++ b/resilience4j-spring-boot/src/test/java/io/github/resilience4j/ratelimiter/autoconfigure/RateLimiterConfigurationOnMissingBeanTest.java @@ -0,0 +1,120 @@ +/* + * Copyright 2019 Mahmoud Romeh + * + * 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 + * + * http://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 io.github.resilience4j.ratelimiter.autoconfigure; + +import static org.assertj.core.api.BDDAssertions.assertThat; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; + +import java.lang.reflect.Method; +import java.util.List; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +import io.github.resilience4j.consumer.DefaultEventConsumerRegistry; +import io.github.resilience4j.consumer.EventConsumerRegistry; +import io.github.resilience4j.ratelimiter.RateLimiterConfig; +import io.github.resilience4j.ratelimiter.RateLimiterRegistry; +import io.github.resilience4j.ratelimiter.configure.RateLimiterAspect; +import io.github.resilience4j.ratelimiter.configure.RateLimiterAspectExt; +import io.github.resilience4j.ratelimiter.configure.RateLimiterConfiguration; +import io.github.resilience4j.ratelimiter.configure.RateLimiterConfigurationProperties; +import io.github.resilience4j.ratelimiter.event.RateLimiterEvent; + +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration(classes = { + RateLimiterConfigurationOnMissingBeanTest.ConfigWithOverrides.class, + RateLimiterAutoConfiguration.class, + RateLimiterConfigurationOnMissingBean.class +}) +@EnableConfigurationProperties(RateLimiterProperties.class) +public class RateLimiterConfigurationOnMissingBeanTest { + + @Autowired + public ConfigWithOverrides configWithOverrides; + + @Autowired + private RateLimiterRegistry rateLimiterRegistry; + + @Autowired + private RateLimiterAspect rateLimiterAspect; + + @Autowired + private EventConsumerRegistry rateLimiterEventsConsumerRegistry; + + @Test + public void testAllBeansFromCircuitBreakerConfigurationHasOnMissingBean() throws NoSuchMethodException { + final Class originalClass = RateLimiterConfiguration.class; + final Class onMissingBeanClass = RateLimiterConfigurationOnMissingBean.class; + + for (Method methodCircuitBreakerConfiguration : originalClass.getMethods()) { + if (methodCircuitBreakerConfiguration.isAnnotationPresent(Bean.class)) { + final Method methodOnMissing = onMissingBeanClass + .getMethod(methodCircuitBreakerConfiguration.getName(), methodCircuitBreakerConfiguration.getParameterTypes()); + + assertThat(methodOnMissing.isAnnotationPresent(Bean.class)).isTrue(); + + if (!methodOnMissing.getName().equals("rateLimiterEventsConsumerRegistry")) { + assertThat(methodOnMissing.isAnnotationPresent(ConditionalOnMissingBean.class)).isTrue(); + } + } + } + } + + @Test + public void testAllCircuitBreakerConfigurationBeansOverridden() { + assertEquals(rateLimiterRegistry, configWithOverrides.rateLimiterRegistry); + assertEquals(rateLimiterAspect, configWithOverrides.rateLimiterAspect); + assertNotEquals(rateLimiterEventsConsumerRegistry, configWithOverrides.rateLimiterEventsConsumerRegistry); + } + + @Configuration + public static class ConfigWithOverrides { + + private RateLimiterRegistry rateLimiterRegistry; + + private RateLimiterAspect rateLimiterAspect; + + private EventConsumerRegistry rateLimiterEventsConsumerRegistry; + + @Bean + public RateLimiterRegistry rateLimiterRegistry() { + rateLimiterRegistry = RateLimiterRegistry.of(RateLimiterConfig.ofDefaults()); + return rateLimiterRegistry; + } + + @Bean + public RateLimiterAspect rateLimiterAspect(RateLimiterRegistry rateLimiterRegistry, @Autowired(required = false) List rateLimiterAspectExtList) { + rateLimiterAspect = new RateLimiterAspect(rateLimiterRegistry, new RateLimiterConfigurationProperties(), rateLimiterAspectExtList); + return rateLimiterAspect; + } + + @Bean + public EventConsumerRegistry rateLimiterEventsConsumerRegistry() { + rateLimiterEventsConsumerRegistry = new DefaultEventConsumerRegistry<>(); + return rateLimiterEventsConsumerRegistry; + } + + } +} \ No newline at end of file diff --git a/resilience4j-spring-boot/src/test/java/io/github/resilience4j/retry/autoconfigure/RetryConfigurationOnMissingBeanTest.java b/resilience4j-spring-boot/src/test/java/io/github/resilience4j/retry/autoconfigure/RetryConfigurationOnMissingBeanTest.java new file mode 100644 index 0000000000..f4e89688cd --- /dev/null +++ b/resilience4j-spring-boot/src/test/java/io/github/resilience4j/retry/autoconfigure/RetryConfigurationOnMissingBeanTest.java @@ -0,0 +1,118 @@ +/* + * Copyright 2019 Mahmoud Romeh + * + * 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 + * + * http://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 io.github.resilience4j.retry.autoconfigure; + +import static org.assertj.core.api.BDDAssertions.assertThat; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; + +import java.lang.reflect.Method; +import java.util.List; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +import io.github.resilience4j.consumer.DefaultEventConsumerRegistry; +import io.github.resilience4j.consumer.EventConsumerRegistry; +import io.github.resilience4j.retry.RetryRegistry; +import io.github.resilience4j.retry.configure.RetryAspect; +import io.github.resilience4j.retry.configure.RetryAspectExt; +import io.github.resilience4j.retry.configure.RetryConfiguration; +import io.github.resilience4j.retry.event.RetryEvent; + +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration(classes = { + RetryConfigurationOnMissingBeanTest.ConfigWithOverrides.class, + RetryAutoConfiguration.class, + RetryConfigurationOnMissingBean.class +}) +@EnableConfigurationProperties(RetryProperties.class) +public class RetryConfigurationOnMissingBeanTest { + + @Autowired + private ConfigWithOverrides configWithOverrides; + + @Autowired + private RetryRegistry retryRegistry; + + @Autowired + private RetryAspect retryAspect; + + @Autowired + private EventConsumerRegistry retryEventConsumerRegistry; + + @Test + public void testAllBeansFromRetryHasOnMissingBean() throws NoSuchMethodException { + final Class originalClass = RetryConfiguration.class; + final Class onMissingBeanClass = RetryConfigurationOnMissingBean.class; + + for (Method methodRetryConfiguration : originalClass.getMethods()) { + if (methodRetryConfiguration.isAnnotationPresent(Bean.class)) { + final Method methodOnMissing = onMissingBeanClass + .getMethod(methodRetryConfiguration.getName(), methodRetryConfiguration.getParameterTypes()); + + assertThat(methodOnMissing.isAnnotationPresent(Bean.class)).isTrue(); + + if (!methodOnMissing.getName().equals("retryEventConsumerRegistry")) { + assertThat(methodOnMissing.isAnnotationPresent(ConditionalOnMissingBean.class)).isTrue(); + } + } + } + } + + @Test + public void testAllRetryConfigurationBeansOverridden() { + assertEquals(retryAspect, configWithOverrides.retryAspect); + assertNotEquals(retryEventConsumerRegistry, configWithOverrides.retryEventConsumerRegistry); + assertEquals(retryRegistry, configWithOverrides.retryRegistry); + } + + @Configuration + public static class ConfigWithOverrides { + + private RetryRegistry retryRegistry; + + private RetryAspect retryAspect; + + private EventConsumerRegistry retryEventConsumerRegistry; + + @Bean + public RetryRegistry retryRegistry() { + this.retryRegistry = RetryRegistry.ofDefaults(); + return retryRegistry; + } + + @Bean + public RetryAspect retryAspect(RetryRegistry retryRegistry, + @Autowired(required = false) List retryAspectExts) { + this.retryAspect = new RetryAspect(new RetryProperties(), retryRegistry, retryAspectExts); + return retryAspect; + } + + @Bean + public EventConsumerRegistry retryEventConsumerRegistry() { + this.retryEventConsumerRegistry = new DefaultEventConsumerRegistry<>(); + return retryEventConsumerRegistry; + } + } +} \ No newline at end of file diff --git a/resilience4j-spring-boot/src/test/resources/application.yaml b/resilience4j-spring-boot/src/test/resources/application.yaml index 26bc85010c..85df50c9ac 100644 --- a/resilience4j-spring-boot/src/test/resources/application.yaml +++ b/resilience4j-spring-boot/src/test/resources/application.yaml @@ -19,6 +19,14 @@ resilience4j.retry: - io.github.resilience4j.circuitbreaker.IgnoredException resilience4j.circuitbreaker: circuitBreakerAspectOrder: 400 + configs: + default: + ringBufferSizeInClosedState: 100 + ringBufferSizeInHalfOpenState: 10 + waitInterval: 10000 + failureRateThreshold: 60 + eventConsumerBufferSize: 10 + registerHealthIndicator: true backends: backendA: ringBufferSizeInClosedState: 6 @@ -39,6 +47,11 @@ resilience4j.circuitbreaker: failureRateThreshold: 50 eventConsumerBufferSize: 10 registerHealthIndicator: false + backendSharedA: + baseConfig: default + ringBufferSizeInClosedState: 6 + backendSharedB: + baseConfig: default resilience4j.ratelimiter: rateLimiterAspectOrder: 401 diff --git a/resilience4j-spring-boot2/build.gradle b/resilience4j-spring-boot2/build.gradle index 2f6f4193a8..492797834f 100644 --- a/resilience4j-spring-boot2/build.gradle +++ b/resilience4j-spring-boot2/build.gradle @@ -5,12 +5,7 @@ dependencies { compileOnly(libraries.spring_boot2_config_processor) compileOnly(libraries.spring_boot2_autoconfigure_processor) - compile project(':resilience4j-annotations') - compile project(':resilience4j-spring') - compile project(':resilience4j-micrometer') - compile project(':resilience4j-circuitbreaker') - compile project(':resilience4j-ratelimiter') - compile project(':resilience4j-consumer') + compile project(':resilience4j-spring-boot-common') testCompile(libraries.spring_boot2_test) testCompile(libraries.spring_boot2_aop) diff --git a/resilience4j-spring-boot2/src/main/java/io/github/resilience4j/bulkhead/autoconfigure/BulkheadAutoConfiguration.java b/resilience4j-spring-boot2/src/main/java/io/github/resilience4j/bulkhead/autoconfigure/BulkheadAutoConfiguration.java index f37d7f01f9..b57011e0f4 100644 --- a/resilience4j-spring-boot2/src/main/java/io/github/resilience4j/bulkhead/autoconfigure/BulkheadAutoConfiguration.java +++ b/resilience4j-spring-boot2/src/main/java/io/github/resilience4j/bulkhead/autoconfigure/BulkheadAutoConfiguration.java @@ -15,13 +15,6 @@ */ package io.github.resilience4j.bulkhead.autoconfigure; -import io.github.resilience4j.bulkhead.Bulkhead; -import io.github.resilience4j.bulkhead.BulkheadRegistry; -import io.github.resilience4j.bulkhead.configure.BulkheadConfiguration; -import io.github.resilience4j.bulkhead.event.BulkheadEvent; -import io.github.resilience4j.bulkhead.monitoring.endpoint.BulkheadEndpoint; -import io.github.resilience4j.bulkhead.monitoring.endpoint.BulkheadEventsEndpoint; -import io.github.resilience4j.consumer.EventConsumerRegistry; import org.springframework.boot.actuate.autoconfigure.endpoint.EndpointAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.endpoint.condition.ConditionalOnEnabledEndpoint; import org.springframework.boot.autoconfigure.AutoConfigureBefore; @@ -31,6 +24,13 @@ import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; +import io.github.resilience4j.bulkhead.Bulkhead; +import io.github.resilience4j.bulkhead.BulkheadRegistry; +import io.github.resilience4j.bulkhead.event.BulkheadEvent; +import io.github.resilience4j.bulkhead.monitoring.endpoint.BulkheadEndpoint; +import io.github.resilience4j.bulkhead.monitoring.endpoint.BulkheadEventsEndpoint; +import io.github.resilience4j.consumer.EventConsumerRegistry; + /** * {@link org.springframework.boot.autoconfigure.EnableAutoConfiguration * Auto-configuration} for resilience4j-bulkhead. @@ -38,18 +38,18 @@ @Configuration @ConditionalOnClass(Bulkhead.class) @EnableConfigurationProperties(BulkheadProperties.class) -@Import(BulkheadConfiguration.class) +@Import(BulkheadConfigurationOnMissingBean.class) @AutoConfigureBefore(EndpointAutoConfiguration.class) public class BulkheadAutoConfiguration { - @Bean - @ConditionalOnEnabledEndpoint - public BulkheadEndpoint bulkheadEndpoint(BulkheadRegistry bulkheadRegistry) { - return new BulkheadEndpoint(bulkheadRegistry); - } + @Bean + @ConditionalOnEnabledEndpoint + public BulkheadEndpoint bulkheadEndpoint(BulkheadRegistry bulkheadRegistry) { + return new BulkheadEndpoint(bulkheadRegistry); + } - @Bean - @ConditionalOnEnabledEndpoint - public BulkheadEventsEndpoint bulkheadEventsEndpoint(EventConsumerRegistry eventConsumerRegistry) { - return new BulkheadEventsEndpoint(eventConsumerRegistry); - } + @Bean + @ConditionalOnEnabledEndpoint + public BulkheadEventsEndpoint bulkheadEventsEndpoint(EventConsumerRegistry eventConsumerRegistry) { + return new BulkheadEventsEndpoint(eventConsumerRegistry); + } } diff --git a/resilience4j-spring-boot2/src/main/java/io/github/resilience4j/bulkhead/autoconfigure/BulkheadConfigurationOnMissingBean.java b/resilience4j-spring-boot2/src/main/java/io/github/resilience4j/bulkhead/autoconfigure/BulkheadConfigurationOnMissingBean.java new file mode 100644 index 0000000000..1cab78163f --- /dev/null +++ b/resilience4j-spring-boot2/src/main/java/io/github/resilience4j/bulkhead/autoconfigure/BulkheadConfigurationOnMissingBean.java @@ -0,0 +1,46 @@ +/* + * Copyright 2019 Mahmoud Romeh + * + * 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 + * + * http://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 io.github.resilience4j.bulkhead.autoconfigure; + +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import io.github.resilience4j.bulkhead.event.BulkheadEvent; +import io.github.resilience4j.consumer.DefaultEventConsumerRegistry; +import io.github.resilience4j.consumer.EventConsumerRegistry; +import io.github.resilience4j.springboot.common.bulkhead.autoconfigure.AbstractBulkheadConfigurationOnMissingBean; + +/** + * {@link Configuration + * Configuration} for resilience4j-bulkhead. + */ +@Configuration +public class BulkheadConfigurationOnMissingBean extends AbstractBulkheadConfigurationOnMissingBean { + + /** + * The EventConsumerRegistry is used to manage EventConsumer instances. + * The EventConsumerRegistry is used by the BulkheadHealthIndicator to show the latest Bulkhead events + * for each Bulkhead instance. + * + * @return a default EventConsumerRegistry {@link DefaultEventConsumerRegistry} + */ + @Bean + @ConditionalOnMissingBean(value = BulkheadEvent.class, parameterizedContainer = EventConsumerRegistry.class) + public EventConsumerRegistry bulkheadEventConsumerRegistry() { + return bulkheadConfiguration.bulkheadEventConsumerRegistry(); + } +} diff --git a/resilience4j-spring-boot2/src/main/java/io/github/resilience4j/bulkhead/autoconfigure/BulkheadMetricsAutoConfiguration.java b/resilience4j-spring-boot2/src/main/java/io/github/resilience4j/bulkhead/autoconfigure/BulkheadMetricsAutoConfiguration.java index 44f16254cc..a07636f300 100644 --- a/resilience4j-spring-boot2/src/main/java/io/github/resilience4j/bulkhead/autoconfigure/BulkheadMetricsAutoConfiguration.java +++ b/resilience4j-spring-boot2/src/main/java/io/github/resilience4j/bulkhead/autoconfigure/BulkheadMetricsAutoConfiguration.java @@ -18,6 +18,7 @@ import org.springframework.boot.actuate.autoconfigure.metrics.MetricsAutoConfiguration; import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -38,16 +39,18 @@ public class BulkheadMetricsAutoConfiguration { @Bean @ConditionalOnProperty(value = "resilience4j.bulkhead.metrics.use_legacy_binder", havingValue = "true") + @ConditionalOnMissingBean public BulkheadMetrics registerLegacyBulkheadMetrics(BulkheadRegistry bulkheadRegistry) { return BulkheadMetrics.ofBulkheadRegistry(bulkheadRegistry); } @Bean @ConditionalOnProperty( - value = "resilience4j.bulkhead.metrics.use_legacy_binder", - havingValue = "false", - matchIfMissing = true + value = "resilience4j.bulkhead.metrics.use_legacy_binder", + havingValue = "false", + matchIfMissing = true ) + @ConditionalOnMissingBean public TaggedBulkheadMetrics registerBulkheadMetrics(BulkheadRegistry bulkheadRegistry) { return TaggedBulkheadMetrics.ofBulkheadRegistry(bulkheadRegistry); } diff --git a/resilience4j-spring-boot2/src/main/java/io/github/resilience4j/circuitbreaker/autoconfigure/CircuitBreakerAutoConfiguration.java b/resilience4j-spring-boot2/src/main/java/io/github/resilience4j/circuitbreaker/autoconfigure/CircuitBreakerAutoConfiguration.java index df3905d125..6f835f6e9f 100644 --- a/resilience4j-spring-boot2/src/main/java/io/github/resilience4j/circuitbreaker/autoconfigure/CircuitBreakerAutoConfiguration.java +++ b/resilience4j-spring-boot2/src/main/java/io/github/resilience4j/circuitbreaker/autoconfigure/CircuitBreakerAutoConfiguration.java @@ -15,7 +15,6 @@ */ package io.github.resilience4j.circuitbreaker.autoconfigure; -import org.springframework.beans.factory.config.ConfigurableBeanFactory; import org.springframework.boot.actuate.autoconfigure.endpoint.EndpointAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.endpoint.condition.ConditionalOnEnabledEndpoint; import org.springframework.boot.autoconfigure.AutoConfigureBefore; @@ -25,19 +24,14 @@ import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; -import javax.annotation.PostConstruct; - import io.github.resilience4j.circuitbreaker.CircuitBreaker; import io.github.resilience4j.circuitbreaker.CircuitBreakerRegistry; -import io.github.resilience4j.circuitbreaker.configure.CircuitBreakerConfiguration; import io.github.resilience4j.circuitbreaker.event.CircuitBreakerEvent; import io.github.resilience4j.circuitbreaker.monitoring.endpoint.CircuitBreakerEndpoint; import io.github.resilience4j.circuitbreaker.monitoring.endpoint.CircuitBreakerEventsEndpoint; -import io.github.resilience4j.circuitbreaker.monitoring.health.CircuitBreakerHealthIndicator; import io.github.resilience4j.consumer.EventConsumerRegistry; - /** * {@link org.springframework.boot.autoconfigure.EnableAutoConfiguration * Auto-configuration} for resilience4j-circuitbreaker. @@ -45,20 +39,10 @@ @Configuration @ConditionalOnClass(CircuitBreaker.class) @EnableConfigurationProperties(CircuitBreakerProperties.class) -@Import(CircuitBreakerConfiguration.class) +@Import(CircuitBreakerConfigurationOnMissingBean.class) @AutoConfigureBefore(EndpointAutoConfiguration.class) public class CircuitBreakerAutoConfiguration { - private final CircuitBreakerProperties circuitBreakerProperties; - private final CircuitBreakerRegistry circuitBreakerRegistry; - private final ConfigurableBeanFactory beanFactory; - - public CircuitBreakerAutoConfiguration(CircuitBreakerProperties circuitBreakerProperties, CircuitBreakerRegistry circuitBreakerRegistry, ConfigurableBeanFactory beanFactory) { - this.circuitBreakerProperties = circuitBreakerProperties; - this.circuitBreakerRegistry = circuitBreakerRegistry; - this.beanFactory = beanFactory; - } - @Bean @ConditionalOnEnabledEndpoint public CircuitBreakerEndpoint circuitBreakerEndpoint(CircuitBreakerRegistry circuitBreakerRegistry) { @@ -71,24 +55,4 @@ public CircuitBreakerEventsEndpoint circuitBreakerEventsEndpoint(EventConsumerRe return new CircuitBreakerEventsEndpoint(eventConsumerRegistry); } - @PostConstruct - public void configureRegistryWithHealthEndpoint(){ - circuitBreakerProperties.getBackends().forEach( - (name, properties) -> { - if (properties.getRegisterHealthIndicator()) { - createHeathIndicatorForCircuitBreaker(name); - } - } - ); - } - - private void createHeathIndicatorForCircuitBreaker(String name) { - CircuitBreaker circuitBreaker = circuitBreakerRegistry.circuitBreaker(name); - CircuitBreakerHealthIndicator healthIndicator = new CircuitBreakerHealthIndicator(circuitBreaker); - beanFactory.registerSingleton( - name + "CircuitBreakerHealthIndicator", - healthIndicator - ); - } - } diff --git a/resilience4j-spring-boot2/src/main/java/io/github/resilience4j/circuitbreaker/autoconfigure/CircuitBreakerConfigurationOnMissingBean.java b/resilience4j-spring-boot2/src/main/java/io/github/resilience4j/circuitbreaker/autoconfigure/CircuitBreakerConfigurationOnMissingBean.java new file mode 100644 index 0000000000..2fa9913bce --- /dev/null +++ b/resilience4j-spring-boot2/src/main/java/io/github/resilience4j/circuitbreaker/autoconfigure/CircuitBreakerConfigurationOnMissingBean.java @@ -0,0 +1,87 @@ +/* + * Copyright 2019 Mahmoud Romeh + * + * 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 + * + * http://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 io.github.resilience4j.circuitbreaker.autoconfigure; + +import java.util.Map; + +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.config.ConfigurableBeanFactory; +import org.springframework.boot.actuate.health.HealthIndicatorRegistry; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import io.github.resilience4j.circuitbreaker.CircuitBreaker; +import io.github.resilience4j.circuitbreaker.configure.CircuitBreakerConfigurationProperties; +import io.github.resilience4j.circuitbreaker.configure.CircuitBreakerConfigurationProperties.BackendProperties; +import io.github.resilience4j.circuitbreaker.event.CircuitBreakerEvent; +import io.github.resilience4j.circuitbreaker.monitoring.health.CircuitBreakerHealthIndicator; +import io.github.resilience4j.consumer.EventConsumerRegistry; +import io.github.resilience4j.springboot.common.circuitbreaker.autoconfigure.AbstractCircuitBreakerConfigurationOnMissingBean; + +@Configuration +public class CircuitBreakerConfigurationOnMissingBean extends AbstractCircuitBreakerConfigurationOnMissingBean implements ApplicationContextAware { + + private final ConfigurableBeanFactory beanFactory; + private ApplicationContext applicationContext; + private HealthIndicatorRegistry healthIndicatorRegistry; + + public CircuitBreakerConfigurationOnMissingBean(CircuitBreakerConfigurationProperties circuitBreakerProperties, + ConfigurableBeanFactory beanFactory) { + super(circuitBreakerProperties); + this.beanFactory = beanFactory; + } + + @Override + public void setApplicationContext(ApplicationContext applicationContext){ + this.applicationContext = applicationContext; + } + + @Bean + @ConditionalOnMissingBean(value = CircuitBreakerEvent.class, parameterizedContainer = EventConsumerRegistry.class) + public EventConsumerRegistry eventConsumerRegistry() { + return circuitBreakerConfiguration.eventConsumerRegistry(); + } + + @Override + protected void createHeathIndicatorForCircuitBreaker(CircuitBreaker circuitBreaker) { + BackendProperties backendProperties = circuitBreakerProperties.findCircuitBreakerBackend(circuitBreaker, circuitBreaker.getCircuitBreakerConfig()); + + if (backendProperties != null && backendProperties.getRegisterHealthIndicator()) { + CircuitBreakerHealthIndicator healthIndicator = new CircuitBreakerHealthIndicator(circuitBreaker); + String circuitBreakerName = circuitBreaker.getName() + "CircuitBreaker"; + beanFactory.registerSingleton( + circuitBreakerName + "HealthIndicator", + healthIndicator + ); + // To support health indicators created after the health registry was created, look up to see if it's in + // the application context. If it is, save it off so we don't need to search for it again, then register + // the new health indicator with the registry. + if (applicationContext != null && healthIndicatorRegistry == null) { + Map healthRegistryBeans = applicationContext.getBeansOfType(HealthIndicatorRegistry.class); + if (healthRegistryBeans.size() > 0) { + healthIndicatorRegistry = healthRegistryBeans.values().iterator().next(); + } + } + + if (healthIndicatorRegistry != null && healthIndicatorRegistry.get(circuitBreakerName) == null) { + healthIndicatorRegistry.register(circuitBreakerName, healthIndicator); + } + } + } +} diff --git a/resilience4j-spring-boot2/src/main/java/io/github/resilience4j/circuitbreaker/autoconfigure/CircuitBreakerMetricsAutoConfiguration.java b/resilience4j-spring-boot2/src/main/java/io/github/resilience4j/circuitbreaker/autoconfigure/CircuitBreakerMetricsAutoConfiguration.java index 79474ce3b7..1a81ff07ea 100644 --- a/resilience4j-spring-boot2/src/main/java/io/github/resilience4j/circuitbreaker/autoconfigure/CircuitBreakerMetricsAutoConfiguration.java +++ b/resilience4j-spring-boot2/src/main/java/io/github/resilience4j/circuitbreaker/autoconfigure/CircuitBreakerMetricsAutoConfiguration.java @@ -21,6 +21,7 @@ import org.springframework.boot.actuate.autoconfigure.metrics.MetricsAutoConfiguration; import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -36,12 +37,14 @@ public class CircuitBreakerMetricsAutoConfiguration { @Bean + @ConditionalOnMissingBean @ConditionalOnProperty(value = "resilience4j.circuitbreaker.metrics.use_legacy_binder", havingValue = "true") public CircuitBreakerMetrics registerLegacyCircuitBreakerMetrics(CircuitBreakerRegistry circuitBreakerRegistry) { return CircuitBreakerMetrics.ofCircuitBreakerRegistry(circuitBreakerRegistry); } @Bean + @ConditionalOnMissingBean @ConditionalOnProperty( value = "resilience4j.circuitbreaker.metrics.use_legacy_binder", havingValue = "false", diff --git a/resilience4j-spring-boot2/src/main/java/io/github/resilience4j/circuitbreaker/monitoring/endpoint/CircuitBreakerEventDTOBuilder.java b/resilience4j-spring-boot2/src/main/java/io/github/resilience4j/circuitbreaker/monitoring/endpoint/CircuitBreakerEventDTOBuilder.java index 5d526e181e..b3ec32903d 100644 --- a/resilience4j-spring-boot2/src/main/java/io/github/resilience4j/circuitbreaker/monitoring/endpoint/CircuitBreakerEventDTOBuilder.java +++ b/resilience4j-spring-boot2/src/main/java/io/github/resilience4j/circuitbreaker/monitoring/endpoint/CircuitBreakerEventDTOBuilder.java @@ -22,9 +22,9 @@ import io.github.resilience4j.core.lang.Nullable; class CircuitBreakerEventDTOBuilder { - private String circuitBreakerName; - private CircuitBreakerEvent.Type type; - private String creationTime; + private final String circuitBreakerName; + private final CircuitBreakerEvent.Type type; + private final String creationTime; @Nullable private String throwable = null; @Nullable diff --git a/resilience4j-spring-boot2/src/main/java/io/github/resilience4j/circuitbreaker/monitoring/health/CircuitBreakerHealthIndicator.java b/resilience4j-spring-boot2/src/main/java/io/github/resilience4j/circuitbreaker/monitoring/health/CircuitBreakerHealthIndicator.java index a89738cfc0..ceae9712ec 100644 --- a/resilience4j-spring-boot2/src/main/java/io/github/resilience4j/circuitbreaker/monitoring/health/CircuitBreakerHealthIndicator.java +++ b/resilience4j-spring-boot2/src/main/java/io/github/resilience4j/circuitbreaker/monitoring/health/CircuitBreakerHealthIndicator.java @@ -16,14 +16,14 @@ package io.github.resilience4j.circuitbreaker.monitoring.health; +import java.util.Optional; + import org.springframework.boot.actuate.health.Health; import org.springframework.boot.actuate.health.HealthIndicator; import io.github.resilience4j.circuitbreaker.CircuitBreaker; import io.github.resilience4j.circuitbreaker.CircuitBreakerConfig; -import java.util.Optional; - /** * A Spring Boot health indicators which adds the state of a CircuitBreaker and it's metrics to the health endpoints */ @@ -36,7 +36,7 @@ public class CircuitBreakerHealthIndicator implements HealthIndicator { private static final String NOT_PERMITTED = "notPermittedCalls"; private static final String MAX_BUFFERED_CALLS = "maxBufferedCalls"; private static final String STATE = "state"; - private CircuitBreaker circuitBreaker; + private final CircuitBreaker circuitBreaker; public CircuitBreakerHealthIndicator(CircuitBreaker circuitBreaker) { this.circuitBreaker = circuitBreaker; diff --git a/resilience4j-spring-boot2/src/main/java/io/github/resilience4j/ratelimiter/autoconfigure/RateLimiterAutoConfiguration.java b/resilience4j-spring-boot2/src/main/java/io/github/resilience4j/ratelimiter/autoconfigure/RateLimiterAutoConfiguration.java index a926dd32bb..0d5056acc7 100644 --- a/resilience4j-spring-boot2/src/main/java/io/github/resilience4j/ratelimiter/autoconfigure/RateLimiterAutoConfiguration.java +++ b/resilience4j-spring-boot2/src/main/java/io/github/resilience4j/ratelimiter/autoconfigure/RateLimiterAutoConfiguration.java @@ -15,6 +15,8 @@ */ package io.github.resilience4j.ratelimiter.autoconfigure; +import javax.annotation.PostConstruct; + import org.springframework.beans.factory.config.ConfigurableBeanFactory; import org.springframework.boot.actuate.autoconfigure.endpoint.EndpointAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.endpoint.condition.ConditionalOnEnabledEndpoint; @@ -25,12 +27,9 @@ import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; -import javax.annotation.PostConstruct; - import io.github.resilience4j.consumer.EventConsumerRegistry; import io.github.resilience4j.ratelimiter.RateLimiter; import io.github.resilience4j.ratelimiter.RateLimiterRegistry; -import io.github.resilience4j.ratelimiter.configure.RateLimiterConfiguration; import io.github.resilience4j.ratelimiter.event.RateLimiterEvent; import io.github.resilience4j.ratelimiter.monitoring.endpoint.RateLimiterEndpoint; import io.github.resilience4j.ratelimiter.monitoring.endpoint.RateLimiterEventsEndpoint; @@ -43,7 +42,7 @@ @Configuration @ConditionalOnClass(RateLimiter.class) @EnableConfigurationProperties(RateLimiterProperties.class) -@Import(RateLimiterConfiguration.class) +@Import(RateLimiterConfigurationOnMissingBean.class) @AutoConfigureBefore(EndpointAutoConfiguration.class) public class RateLimiterAutoConfiguration { private final RateLimiterProperties rateLimiterProperties; diff --git a/resilience4j-spring-boot2/src/main/java/io/github/resilience4j/ratelimiter/autoconfigure/RateLimiterConfigurationOnMissingBean.java b/resilience4j-spring-boot2/src/main/java/io/github/resilience4j/ratelimiter/autoconfigure/RateLimiterConfigurationOnMissingBean.java new file mode 100644 index 0000000000..20e3ef2e92 --- /dev/null +++ b/resilience4j-spring-boot2/src/main/java/io/github/resilience4j/ratelimiter/autoconfigure/RateLimiterConfigurationOnMissingBean.java @@ -0,0 +1,34 @@ +/* + * Copyright 2019 Mahmoud Romeh + * + * 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 + * + * http://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 io.github.resilience4j.ratelimiter.autoconfigure; + +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import io.github.resilience4j.consumer.EventConsumerRegistry; +import io.github.resilience4j.ratelimiter.event.RateLimiterEvent; +import io.github.resilience4j.springboot.common.ratelimiter.autoconfigure.AbstractRateLimiterConfigurationOnMissingBean; + +@Configuration +public class RateLimiterConfigurationOnMissingBean extends AbstractRateLimiterConfigurationOnMissingBean { + + @Bean + @ConditionalOnMissingBean(value = RateLimiterEvent.class, parameterizedContainer = EventConsumerRegistry.class) + public EventConsumerRegistry rateLimiterEventsConsumerRegistry() { + return rateLimiterConfiguration.rateLimiterEventsConsumerRegistry(); + } +} diff --git a/resilience4j-spring-boot2/src/main/java/io/github/resilience4j/ratelimiter/autoconfigure/RateLimiterMetricsAutoConfiguration.java b/resilience4j-spring-boot2/src/main/java/io/github/resilience4j/ratelimiter/autoconfigure/RateLimiterMetricsAutoConfiguration.java index abfbeae622..eb623dbcfe 100644 --- a/resilience4j-spring-boot2/src/main/java/io/github/resilience4j/ratelimiter/autoconfigure/RateLimiterMetricsAutoConfiguration.java +++ b/resilience4j-spring-boot2/src/main/java/io/github/resilience4j/ratelimiter/autoconfigure/RateLimiterMetricsAutoConfiguration.java @@ -22,6 +22,7 @@ import org.springframework.boot.actuate.autoconfigure.metrics.MetricsAutoConfiguration; import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -37,12 +38,14 @@ public class RateLimiterMetricsAutoConfiguration { @Bean + @ConditionalOnMissingBean @ConditionalOnProperty(value = "resilience4j.ratelimiter.metrics.use_legacy_binder", havingValue = "true") public RateLimiterMetrics registerLegacyRateLimiterMetrics(RateLimiterRegistry rateLimiterRegistry) { return RateLimiterMetrics.ofRateLimiterRegistry(rateLimiterRegistry); } @Bean + @ConditionalOnMissingBean @ConditionalOnProperty( value = "resilience4j.ratelimiter.metrics.use_legacy_binder", havingValue = "false", diff --git a/resilience4j-spring-boot2/src/main/java/io/github/resilience4j/retry/autoconfigure/RetryAutoConfiguration.java b/resilience4j-spring-boot2/src/main/java/io/github/resilience4j/retry/autoconfigure/RetryAutoConfiguration.java index 0561e1d2fa..d544e36bad 100644 --- a/resilience4j-spring-boot2/src/main/java/io/github/resilience4j/retry/autoconfigure/RetryAutoConfiguration.java +++ b/resilience4j-spring-boot2/src/main/java/io/github/resilience4j/retry/autoconfigure/RetryAutoConfiguration.java @@ -25,7 +25,6 @@ import io.github.resilience4j.consumer.EventConsumerRegistry; import io.github.resilience4j.retry.Retry; import io.github.resilience4j.retry.RetryRegistry; -import io.github.resilience4j.retry.configure.RetryConfiguration; import io.github.resilience4j.retry.event.RetryEvent; import io.github.resilience4j.retry.monitoring.endpoint.RetryEndpoint; import io.github.resilience4j.retry.monitoring.endpoint.RetryEventsEndpoint; @@ -38,7 +37,7 @@ @Configuration @ConditionalOnClass(Retry.class) @EnableConfigurationProperties(RetryProperties.class) -@Import(RetryConfiguration.class) +@Import(RetryConfigurationOnMissingBean.class) public class RetryAutoConfiguration { @Bean @ConditionalOnEnabledEndpoint diff --git a/resilience4j-spring-boot2/src/main/java/io/github/resilience4j/retry/autoconfigure/RetryConfigurationOnMissingBean.java b/resilience4j-spring-boot2/src/main/java/io/github/resilience4j/retry/autoconfigure/RetryConfigurationOnMissingBean.java new file mode 100644 index 0000000000..df3c059831 --- /dev/null +++ b/resilience4j-spring-boot2/src/main/java/io/github/resilience4j/retry/autoconfigure/RetryConfigurationOnMissingBean.java @@ -0,0 +1,46 @@ +/* + * Copyright 2019 Mahmoud Romeh + * + * 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 + * + * http://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 io.github.resilience4j.retry.autoconfigure; + +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import io.github.resilience4j.consumer.DefaultEventConsumerRegistry; +import io.github.resilience4j.consumer.EventConsumerRegistry; +import io.github.resilience4j.retry.event.RetryEvent; +import io.github.resilience4j.springboot.common.retry.autoconfigure.AbstractRetryConfigurationOnMissingBean; + +/** + * {@link Configuration + * Configuration} for resilience4j-retry. + */ +@Configuration +public class RetryConfigurationOnMissingBean extends AbstractRetryConfigurationOnMissingBean { + /** + * The EventConsumerRegistry is used to manage EventConsumer instances. + * The EventConsumerRegistry is used by the Retry events monitor to show the latest RetryEvent events + * for each Retry instance. + * + * @return a default EventConsumerRegistry {@link DefaultEventConsumerRegistry} + */ + @Bean + @ConditionalOnMissingBean(value = RetryEvent.class, parameterizedContainer = EventConsumerRegistry.class) + public EventConsumerRegistry retryEventConsumerRegistry() { + return retryConfiguration.retryEventConsumerRegistry(); + } + +} diff --git a/resilience4j-spring-boot2/src/main/java/io/github/resilience4j/retry/autoconfigure/RetryMetricsAutoConfiguration.java b/resilience4j-spring-boot2/src/main/java/io/github/resilience4j/retry/autoconfigure/RetryMetricsAutoConfiguration.java index c85a118a95..e25eff5d7a 100644 --- a/resilience4j-spring-boot2/src/main/java/io/github/resilience4j/retry/autoconfigure/RetryMetricsAutoConfiguration.java +++ b/resilience4j-spring-boot2/src/main/java/io/github/resilience4j/retry/autoconfigure/RetryMetricsAutoConfiguration.java @@ -15,16 +15,18 @@ */ package io.github.resilience4j.retry.autoconfigure; -import io.github.resilience4j.micrometer.RetryMetrics; -import io.github.resilience4j.micrometer.tagged.TaggedRetryMetrics; -import io.github.resilience4j.retry.RetryRegistry; import org.springframework.boot.actuate.autoconfigure.metrics.MetricsAutoConfiguration; import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import io.github.resilience4j.micrometer.RetryMetrics; +import io.github.resilience4j.micrometer.tagged.TaggedRetryMetrics; +import io.github.resilience4j.retry.RetryRegistry; + /** * {@link org.springframework.boot.autoconfigure.EnableAutoConfiguration * Auto-configuration} for resilience4j-metrics. @@ -36,16 +38,18 @@ public class RetryMetricsAutoConfiguration { @Bean + @ConditionalOnMissingBean @ConditionalOnProperty(value = "resilience4j.retry.metrics.use_legacy_binder", havingValue = "true") public RetryMetrics registerLegacyRetryMetrics(RetryRegistry retryRegistry) { return RetryMetrics.ofRetryRegistry(retryRegistry); } @Bean + @ConditionalOnMissingBean @ConditionalOnProperty( - value = "resilience4j.retry.metrics.use_legacy_binder", - havingValue = "false", - matchIfMissing = true + value = "resilience4j.retry.metrics.use_legacy_binder", + havingValue = "false", + matchIfMissing = true ) public TaggedRetryMetrics registerRetryMetrics(RetryRegistry retryRegistry) { return TaggedRetryMetrics.ofRetryRegistry(retryRegistry); diff --git a/resilience4j-spring-boot2/src/main/java/io/github/resilience4j/retry/autoconfigure/package-info.java b/resilience4j-spring-boot2/src/main/java/io/github/resilience4j/retry/autoconfigure/package-info.java new file mode 100644 index 0000000000..cf5e4c8dc8 --- /dev/null +++ b/resilience4j-spring-boot2/src/main/java/io/github/resilience4j/retry/autoconfigure/package-info.java @@ -0,0 +1,24 @@ +/* + * + * Copyright 2018: Clint Checketts + * + * 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 + * + * http://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. + * + * + */ +@NonNullApi +@NonNullFields +package io.github.resilience4j.retry.autoconfigure; + +import io.github.resilience4j.core.lang.NonNullApi; +import io.github.resilience4j.core.lang.NonNullFields; \ No newline at end of file diff --git a/resilience4j-spring-boot2/src/test/java/io/github/resilience4j/TestUtils.java b/resilience4j-spring-boot2/src/test/java/io/github/resilience4j/TestUtils.java new file mode 100644 index 0000000000..dcc63ca2fc --- /dev/null +++ b/resilience4j-spring-boot2/src/test/java/io/github/resilience4j/TestUtils.java @@ -0,0 +1,24 @@ +package io.github.resilience4j; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.lang.reflect.Method; + +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.context.annotation.Bean; + + +public class TestUtils { + + public static void assertAnnaations(Class originalClass, Class onMissingBeanClass) throws NoSuchMethodException { + for (Method methodBulkheadConfiguration : originalClass.getMethods()) { + if (methodBulkheadConfiguration.isAnnotationPresent(Bean.class)) { + final Method methodOnMissing = onMissingBeanClass + .getMethod(methodBulkheadConfiguration.getName(), methodBulkheadConfiguration.getParameterTypes()); + + assertThat(methodOnMissing.isAnnotationPresent(Bean.class)).isTrue(); + assertThat(methodOnMissing.isAnnotationPresent(ConditionalOnMissingBean.class)).isTrue(); + } + } + } +} diff --git a/resilience4j-spring-boot2/src/test/java/io/github/resilience4j/bulkhead/autoconfigure/BulkheadConfigurationOnMissingBeanTest.java b/resilience4j-spring-boot2/src/test/java/io/github/resilience4j/bulkhead/autoconfigure/BulkheadConfigurationOnMissingBeanTest.java new file mode 100644 index 0000000000..41b7fdafca --- /dev/null +++ b/resilience4j-spring-boot2/src/test/java/io/github/resilience4j/bulkhead/autoconfigure/BulkheadConfigurationOnMissingBeanTest.java @@ -0,0 +1,103 @@ +/* + * Copyright 2019 Mahmoud Romeh + * + * 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 + * + * http://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 io.github.resilience4j.bulkhead.autoconfigure; + +import static org.junit.Assert.assertEquals; + +import java.util.List; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +import io.github.resilience4j.TestUtils; +import io.github.resilience4j.bulkhead.BulkheadRegistry; +import io.github.resilience4j.bulkhead.configure.BulkheadAspect; +import io.github.resilience4j.bulkhead.configure.BulkheadAspectExt; +import io.github.resilience4j.bulkhead.configure.BulkheadConfiguration; +import io.github.resilience4j.bulkhead.event.BulkheadEvent; +import io.github.resilience4j.consumer.DefaultEventConsumerRegistry; +import io.github.resilience4j.consumer.EventConsumerRegistry; + +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration(classes = { + BulkheadConfigurationOnMissingBeanTest.ConfigWithOverrides.class, + BulkheadAutoConfiguration.class, + BulkheadConfigurationOnMissingBean.class +}) +@EnableConfigurationProperties(BulkheadProperties.class) +public class BulkheadConfigurationOnMissingBeanTest { + + @Autowired + private ConfigWithOverrides configWithOverrides; + + @Autowired + private BulkheadRegistry bulkheadRegistry; + + @Autowired + private BulkheadAspect bulkheadAspect; + + @Autowired + private EventConsumerRegistry bulkheadEventEventConsumerRegistry; + + @Test + public void testAllBeansFromBulkHeadHasOnMissingBean() throws NoSuchMethodException { + final Class originalClass = BulkheadConfiguration.class; + final Class onMissingBeanClass = BulkheadConfigurationOnMissingBean.class; + TestUtils.assertAnnaations(originalClass, onMissingBeanClass); + } + + @Test + public void testAllBulkHeadConfigurationBeansOverridden() { + assertEquals(bulkheadRegistry, configWithOverrides.bulkheadRegistry); + assertEquals(bulkheadAspect, configWithOverrides.bulkheadAspect); + assertEquals(bulkheadEventEventConsumerRegistry, configWithOverrides.bulkheadEventEventConsumerRegistry); + } + + @Configuration + public static class ConfigWithOverrides { + + private BulkheadRegistry bulkheadRegistry; + + private BulkheadAspect bulkheadAspect; + + private EventConsumerRegistry bulkheadEventEventConsumerRegistry; + + @Bean + public BulkheadRegistry bulkheadRegistry() { + bulkheadRegistry = BulkheadRegistry.ofDefaults(); + return bulkheadRegistry; + } + + @Bean + public BulkheadAspect bulkheadAspect(BulkheadRegistry bulkheadRegistry, + @Autowired(required = false) List bulkheadAspectExts) { + bulkheadAspect = new BulkheadAspect(new BulkheadProperties(), bulkheadRegistry, bulkheadAspectExts); + return bulkheadAspect; + } + + @Bean + public EventConsumerRegistry bulkheadEventConsumerRegistry() { + bulkheadEventEventConsumerRegistry = new DefaultEventConsumerRegistry<>(); + return bulkheadEventEventConsumerRegistry; + } + } +} \ No newline at end of file diff --git a/resilience4j-spring-boot2/src/test/java/io/github/resilience4j/circuitbreaker/CircuitBreakerAutoConfigurationRxJava2Test.java b/resilience4j-spring-boot2/src/test/java/io/github/resilience4j/circuitbreaker/CircuitBreakerAutoConfigurationRxJava2Test.java index 25f3ccf63e..0093da27d2 100644 --- a/resilience4j-spring-boot2/src/test/java/io/github/resilience4j/circuitbreaker/CircuitBreakerAutoConfigurationRxJava2Test.java +++ b/resilience4j-spring-boot2/src/test/java/io/github/resilience4j/circuitbreaker/CircuitBreakerAutoConfigurationRxJava2Test.java @@ -88,7 +88,7 @@ public void testCircuitBreakerAutoConfigurationReactiveRxJava2() throws IOExcept // expect circuitbreakers actuator endpoint contains both circuitbreakers ResponseEntity circuitBreakerList = restTemplate.getForEntity("/actuator/circuitbreakers", CircuitBreakerEndpointResponse.class); - assertThat(circuitBreakerList.getBody().getCircuitBreakers()).hasSize(2).containsExactly("backendA", "backendB"); + assertThat(circuitBreakerList.getBody().getCircuitBreakers()).hasSize(4).containsExactly("backendA", "backendB", "backendSharedA", "backendSharedB"); // expect circuitbreaker-event actuator endpoint recorded both events ResponseEntity circuitBreakerEventList = restTemplate.getForEntity("/actuator/circuitbreakerevents", CircuitBreakerEventsEndpointResponse.class); diff --git a/resilience4j-spring-boot2/src/test/java/io/github/resilience4j/circuitbreaker/CircuitBreakerAutoConfigurationTest.java b/resilience4j-spring-boot2/src/test/java/io/github/resilience4j/circuitbreaker/CircuitBreakerAutoConfigurationTest.java index bd151ac34f..7afd37e2b0 100644 --- a/resilience4j-spring-boot2/src/test/java/io/github/resilience4j/circuitbreaker/CircuitBreakerAutoConfigurationTest.java +++ b/resilience4j-spring-boot2/src/test/java/io/github/resilience4j/circuitbreaker/CircuitBreakerAutoConfigurationTest.java @@ -15,33 +15,37 @@ */ package io.github.resilience4j.circuitbreaker; -import io.github.resilience4j.circuitbreaker.autoconfigure.CircuitBreakerProperties; -import io.github.resilience4j.circuitbreaker.configure.CircuitBreakerAspect; -import io.github.resilience4j.circuitbreaker.monitoring.endpoint.CircuitBreakerEndpointResponse; -import io.github.resilience4j.circuitbreaker.monitoring.endpoint.CircuitBreakerEventsEndpointResponse; -import io.github.resilience4j.service.test.DummyService; -import io.github.resilience4j.service.test.ReactiveDummyService; -import io.github.resilience4j.service.test.TestApplication; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.Assert.assertEquals; + +import java.io.IOException; +import java.time.Duration; +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; + import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.web.client.TestRestTemplate; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; import org.springframework.http.ResponseEntity; import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; -import java.io.IOException; -import java.time.Duration; -import java.util.Map; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ExecutionException; - -import static org.assertj.core.api.Assertions.assertThat; +import io.github.resilience4j.circuitbreaker.autoconfigure.CircuitBreakerProperties; +import io.github.resilience4j.circuitbreaker.configure.CircuitBreakerAspect; +import io.github.resilience4j.circuitbreaker.monitoring.endpoint.CircuitBreakerEndpointResponse; +import io.github.resilience4j.circuitbreaker.monitoring.endpoint.CircuitBreakerEventsEndpointResponse; +import io.github.resilience4j.service.test.DummyService; +import io.github.resilience4j.service.test.ReactiveDummyService; +import io.github.resilience4j.service.test.TestApplication; @RunWith(SpringJUnit4ClassRunner.class) @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, - classes = TestApplication.class) + classes = {TestApplication.class, CircuitBreakerAutoConfigurationTest.AdditionalConfiguration.class}) public class CircuitBreakerAutoConfigurationTest { @Autowired @@ -62,6 +66,16 @@ public class CircuitBreakerAutoConfigurationTest { @Autowired private ReactiveDummyService reactiveDummyService; + @Configuration + public static class AdditionalConfiguration { + + // Shows that a circuit breaker can be created in code and still use the shared configuration. + @Bean + public CircuitBreaker otherCircuitBreaker(CircuitBreakerRegistry registry, CircuitBreakerProperties properties) { + return registry.circuitBreaker("backendSharedC", properties.createCircuitBreakerConfigFrom("default")); + } + } + /** * The test verifies that a CircuitBreaker instance is created and configured properly when the DummyService is invoked and * that the CircuitBreaker records successful and failed calls. @@ -93,11 +107,11 @@ public void testCircuitBreakerAutoConfiguration() throws IOException { assertThat(circuitBreaker.getCircuitBreakerConfig().getFailureRateThreshold()).isEqualTo(70f); assertThat(circuitBreaker.getCircuitBreakerConfig().getWaitDurationInOpenState()).isEqualByComparingTo(Duration.ofSeconds(5L)); - // expect circuitbreakers actuator endpoint contains both circuitbreakers + // expect circuitbreakers actuator endpoint contains all circuitbreakers ResponseEntity circuitBreakerList = restTemplate.getForEntity("/actuator/circuitbreakers", CircuitBreakerEndpointResponse.class); - assertThat(circuitBreakerList.getBody().getCircuitBreakers()).hasSize(2).containsExactly("backendA", "backendB"); + assertThat(circuitBreakerList.getBody().getCircuitBreakers()).hasSize(5).containsExactly("backendA", "backendB", "backendSharedA", "backendSharedB", "backendSharedC"); - // expect circuitbreaker-event actuator endpoint recorded both events + // expect circuitbreaker-event actuator endpoint recorded all events ResponseEntity circuitBreakerEventList = restTemplate.getForEntity("/actuator/circuitbreakerevents", CircuitBreakerEventsEndpointResponse.class); assertThat(circuitBreakerEventList.getBody().getCircuitBreakerEvents()).hasSize(2); @@ -109,6 +123,9 @@ public void testCircuitBreakerAutoConfiguration() throws IOException { assertThat(healthResponse.getBody().getDetails()).isNotNull(); assertThat(healthResponse.getBody().getDetails().get("backendACircuitBreaker")).isNotNull(); assertThat(healthResponse.getBody().getDetails().get("backendBCircuitBreaker")).isNull(); + assertThat(healthResponse.getBody().getDetails().get("backendSharedACircuitBreaker")).isNotNull(); + assertThat(healthResponse.getBody().getDetails().get("backendSharedBCircuitBreaker")).isNotNull(); + assertThat(healthResponse.getBody().getDetails().get("backendSharedCCircuitBreaker")).isNotNull(); assertThat(circuitBreaker.getCircuitBreakerConfig().getRecordFailurePredicate().test(new RecordedException())).isTrue(); assertThat(circuitBreaker.getCircuitBreakerConfig().getRecordFailurePredicate().test(new IgnoredException())).isFalse(); @@ -119,6 +136,23 @@ public void testCircuitBreakerAutoConfiguration() throws IOException { // expect aspect configured as defined in application.yml assertThat(circuitBreakerAspect.getOrder()).isEqualTo(400); + + // expect all shared configs share the same values and are from the application.yml file + CircuitBreaker sharedA = circuitBreakerRegistry.circuitBreaker("backendSharedA"); + CircuitBreaker sharedB = circuitBreakerRegistry.circuitBreaker("backendSharedB"); + CircuitBreaker sharedC = circuitBreakerRegistry.circuitBreaker("backendSharedC"); + assertThat(sharedA.getCircuitBreakerConfig().getRingBufferSizeInClosedState()).isEqualTo(6); + assertThat(sharedA.getCircuitBreakerConfig().getRingBufferSizeInHalfOpenState()).isEqualTo(10); + assertThat(sharedA.getCircuitBreakerConfig().getFailureRateThreshold()).isEqualTo(60f); + assertThat(sharedA.getCircuitBreakerConfig().getWaitDurationInOpenState()).isEqualByComparingTo(Duration.ofSeconds(10L)); + assertEquals(sharedA.getCircuitBreakerConfig().getRingBufferSizeInClosedState(), sharedB.getCircuitBreakerConfig().getRingBufferSizeInClosedState()); + assertEquals(sharedA.getCircuitBreakerConfig().getRingBufferSizeInHalfOpenState(), sharedB.getCircuitBreakerConfig().getRingBufferSizeInHalfOpenState()); + assertThat(sharedB.getCircuitBreakerConfig().getFailureRateThreshold()).isEqualTo(60f); + assertEquals(sharedA.getCircuitBreakerConfig().getWaitDurationInOpenState(), sharedB.getCircuitBreakerConfig().getWaitDurationInOpenState()); + assertEquals(sharedA.getCircuitBreakerConfig().getRingBufferSizeInClosedState(), sharedC.getCircuitBreakerConfig().getRingBufferSizeInClosedState()); + assertEquals(sharedA.getCircuitBreakerConfig().getRingBufferSizeInHalfOpenState(), sharedC.getCircuitBreakerConfig().getRingBufferSizeInHalfOpenState()); + assertThat(sharedC.getCircuitBreakerConfig().getFailureRateThreshold()).isEqualTo(60f); + assertEquals(sharedA.getCircuitBreakerConfig().getWaitDurationInOpenState(), sharedC.getCircuitBreakerConfig().getWaitDurationInOpenState()); } @@ -150,7 +184,7 @@ public void testCircuitBreakerAutoConfigurationAsync() throws IOException, Execu // expect circuitbreakers actuator endpoint contains both circuitbreakers ResponseEntity circuitBreakerList = restTemplate.getForEntity("/actuator/circuitbreakers", CircuitBreakerEndpointResponse.class); - assertThat(circuitBreakerList.getBody().getCircuitBreakers()).hasSize(2).containsExactly("backendA", "backendB"); + assertThat(circuitBreakerList.getBody().getCircuitBreakers()).hasSize(5).containsExactly("backendA", "backendB", "backendSharedA", "backendSharedB", "backendSharedC"); // expect circuitbreaker-event actuator endpoint recorded both events ResponseEntity circuitBreakerEventList = restTemplate.getForEntity("/actuator/circuitbreakerevents", CircuitBreakerEventsEndpointResponse.class); @@ -164,6 +198,9 @@ public void testCircuitBreakerAutoConfigurationAsync() throws IOException, Execu assertThat(healthResponse.getBody().getDetails()).isNotNull(); assertThat(healthResponse.getBody().getDetails().get("backendACircuitBreaker")).isNotNull(); assertThat(healthResponse.getBody().getDetails().get("backendBCircuitBreaker")).isNull(); + assertThat(healthResponse.getBody().getDetails().get("backendSharedACircuitBreaker")).isNotNull(); + assertThat(healthResponse.getBody().getDetails().get("backendSharedBCircuitBreaker")).isNotNull(); + assertThat(healthResponse.getBody().getDetails().get("backendSharedCCircuitBreaker")).isNotNull(); } @@ -196,9 +233,9 @@ public void testCircuitBreakerAutoConfigurationReactive() throws IOException { assertThat(circuitBreaker.getCircuitBreakerConfig().getFailureRateThreshold()).isEqualTo(50f); assertThat(circuitBreaker.getCircuitBreakerConfig().getWaitDurationInOpenState()).isEqualByComparingTo(Duration.ofSeconds(5L)); - // expect circuitbreakers actuator endpoint contains both circuitbreakers + // expect circuitbreakers actuator endpoint contains all circuitbreakers ResponseEntity circuitBreakerList = restTemplate.getForEntity("/actuator/circuitbreakers", CircuitBreakerEndpointResponse.class); - assertThat(circuitBreakerList.getBody().getCircuitBreakers()).hasSize(2).containsExactly("backendA", "backendB"); + assertThat(circuitBreakerList.getBody().getCircuitBreakers()).hasSize(5).containsExactly("backendA", "backendB", "backendSharedA", "backendSharedB", "backendSharedC"); // expect circuitbreaker-event actuator endpoint recorded both events ResponseEntity circuitBreakerEventList = restTemplate.getForEntity("/actuator/circuitbreakerevents", CircuitBreakerEventsEndpointResponse.class); diff --git a/resilience4j-spring-boot2/src/test/java/io/github/resilience4j/circuitbreaker/autoconfigure/CircuitBreakerConfigurationOnMissingBeanTest.java b/resilience4j-spring-boot2/src/test/java/io/github/resilience4j/circuitbreaker/autoconfigure/CircuitBreakerConfigurationOnMissingBeanTest.java new file mode 100644 index 0000000000..913c96074d --- /dev/null +++ b/resilience4j-spring-boot2/src/test/java/io/github/resilience4j/circuitbreaker/autoconfigure/CircuitBreakerConfigurationOnMissingBeanTest.java @@ -0,0 +1,88 @@ +package io.github.resilience4j.circuitbreaker.autoconfigure; + +import static org.junit.Assert.assertEquals; + +import java.util.List; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +import io.github.resilience4j.TestUtils; +import io.github.resilience4j.circuitbreaker.CircuitBreakerRegistry; +import io.github.resilience4j.circuitbreaker.configure.CircuitBreakerAspect; +import io.github.resilience4j.circuitbreaker.configure.CircuitBreakerAspectExt; +import io.github.resilience4j.circuitbreaker.configure.CircuitBreakerConfiguration; +import io.github.resilience4j.circuitbreaker.event.CircuitBreakerEvent; +import io.github.resilience4j.consumer.DefaultEventConsumerRegistry; +import io.github.resilience4j.consumer.EventConsumerRegistry; + +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration(classes = { + CircuitBreakerConfigurationOnMissingBeanTest.ConfigWithOverrides.class, + CircuitBreakerAutoConfiguration.class, + CircuitBreakerConfigurationOnMissingBean.class +}) +@EnableConfigurationProperties(CircuitBreakerProperties.class) +public class CircuitBreakerConfigurationOnMissingBeanTest { + + @Autowired + private ConfigWithOverrides configWithOverrides; + + @Autowired + private CircuitBreakerRegistry circuitBreakerRegistry; + + @Autowired + private CircuitBreakerAspect circuitBreakerAspect; + + @Autowired + private EventConsumerRegistry circuitEventConsumerBreakerRegistry; + + @Test + public void testAllBeansFromCircuitBreakerConfigurationHasOnMissingBean() throws NoSuchMethodException { + final Class originalClass = CircuitBreakerConfiguration.class; + final Class onMissingBeanClass = CircuitBreakerConfigurationOnMissingBean.class; + TestUtils.assertAnnaations(originalClass, onMissingBeanClass); + } + + @Test + public void testAllCircuitBreakerConfigurationBeansOverridden() { + assertEquals(circuitBreakerRegistry, configWithOverrides.circuitBreakerRegistry); + assertEquals(circuitBreakerAspect, configWithOverrides.circuitBreakerAspect); + assertEquals(circuitEventConsumerBreakerRegistry, configWithOverrides.circuitEventConsumerBreakerRegistry); + } + + @Configuration + public static class ConfigWithOverrides { + + public CircuitBreakerRegistry circuitBreakerRegistry; + + public CircuitBreakerAspect circuitBreakerAspect; + + public EventConsumerRegistry circuitEventConsumerBreakerRegistry; + + @Bean + public CircuitBreakerRegistry circuitBreakerRegistry() { + circuitBreakerRegistry = CircuitBreakerRegistry.ofDefaults(); + return circuitBreakerRegistry; + } + + @Bean + public CircuitBreakerAspect circuitBreakerAspect(CircuitBreakerRegistry circuitBreakerRegistry, + @Autowired(required = false) List circuitBreakerAspectExtList) { + circuitBreakerAspect = new CircuitBreakerAspect(new CircuitBreakerProperties(), circuitBreakerRegistry, circuitBreakerAspectExtList); + return circuitBreakerAspect; + } + + @Bean + public EventConsumerRegistry eventConsumerRegistry() { + circuitEventConsumerBreakerRegistry = new DefaultEventConsumerRegistry<>(); + return circuitEventConsumerBreakerRegistry; + } + } +} \ No newline at end of file diff --git a/resilience4j-spring-boot2/src/test/java/io/github/resilience4j/ratelimiter/autoconfigure/RateLimiterConfigurationOnMissingBeanTest.java b/resilience4j-spring-boot2/src/test/java/io/github/resilience4j/ratelimiter/autoconfigure/RateLimiterConfigurationOnMissingBeanTest.java new file mode 100644 index 0000000000..f0c2667229 --- /dev/null +++ b/resilience4j-spring-boot2/src/test/java/io/github/resilience4j/ratelimiter/autoconfigure/RateLimiterConfigurationOnMissingBeanTest.java @@ -0,0 +1,90 @@ +package io.github.resilience4j.ratelimiter.autoconfigure; + +import static org.junit.Assert.assertEquals; + +import java.util.List; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +import io.github.resilience4j.TestUtils; +import io.github.resilience4j.consumer.DefaultEventConsumerRegistry; +import io.github.resilience4j.consumer.EventConsumerRegistry; +import io.github.resilience4j.ratelimiter.RateLimiterConfig; +import io.github.resilience4j.ratelimiter.RateLimiterRegistry; +import io.github.resilience4j.ratelimiter.configure.RateLimiterAspect; +import io.github.resilience4j.ratelimiter.configure.RateLimiterAspectExt; +import io.github.resilience4j.ratelimiter.configure.RateLimiterConfiguration; +import io.github.resilience4j.ratelimiter.configure.RateLimiterConfigurationProperties; +import io.github.resilience4j.ratelimiter.event.RateLimiterEvent; + +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration(classes = { + RateLimiterConfigurationOnMissingBeanTest.ConfigWithOverrides.class, + RateLimiterAutoConfiguration.class, + RateLimiterConfigurationOnMissingBean.class +}) +@EnableConfigurationProperties(RateLimiterProperties.class) +public class RateLimiterConfigurationOnMissingBeanTest { + + @Autowired + public ConfigWithOverrides configWithOverrides; + + @Autowired + private RateLimiterRegistry rateLimiterRegistry; + + @Autowired + private RateLimiterAspect rateLimiterAspect; + + @Autowired + private EventConsumerRegistry rateLimiterEventsConsumerRegistry; + + @Test + public void testAllBeansFromCircuitBreakerConfigurationHasOnMissingBean() throws NoSuchMethodException { + final Class originalClass = RateLimiterConfiguration.class; + final Class onMissingBeanClass = RateLimiterConfigurationOnMissingBean.class; + TestUtils.assertAnnaations(originalClass, onMissingBeanClass); + } + + @Test + public void testAllCircuitBreakerConfigurationBeansOverridden() { + assertEquals(rateLimiterRegistry, configWithOverrides.rateLimiterRegistry); + assertEquals(rateLimiterAspect, configWithOverrides.rateLimiterAspect); + assertEquals(rateLimiterEventsConsumerRegistry, configWithOverrides.rateLimiterEventsConsumerRegistry); + } + + @Configuration + public static class ConfigWithOverrides { + + public RateLimiterRegistry rateLimiterRegistry; + + public RateLimiterAspect rateLimiterAspect; + + public EventConsumerRegistry rateLimiterEventsConsumerRegistry; + + @Bean + public RateLimiterRegistry rateLimiterRegistry() { + rateLimiterRegistry = RateLimiterRegistry.of(RateLimiterConfig.ofDefaults()); + return rateLimiterRegistry; + } + + @Bean + public RateLimiterAspect rateLimiterAspect(RateLimiterRegistry rateLimiterRegistry, @Autowired(required = false) List rateLimiterAspectExtList) { + rateLimiterAspect = new RateLimiterAspect(rateLimiterRegistry, new RateLimiterConfigurationProperties(), rateLimiterAspectExtList); + return rateLimiterAspect; + } + + @Bean + public EventConsumerRegistry rateLimiterEventsConsumerRegistry() { + rateLimiterEventsConsumerRegistry = new DefaultEventConsumerRegistry<>(); + return rateLimiterEventsConsumerRegistry; + } + + } +} \ No newline at end of file diff --git a/resilience4j-spring-boot2/src/test/java/io/github/resilience4j/retry/autoconfigure/RetryConfigurationOnMissingBeanTest.java b/resilience4j-spring-boot2/src/test/java/io/github/resilience4j/retry/autoconfigure/RetryConfigurationOnMissingBeanTest.java new file mode 100644 index 0000000000..418752c7f7 --- /dev/null +++ b/resilience4j-spring-boot2/src/test/java/io/github/resilience4j/retry/autoconfigure/RetryConfigurationOnMissingBeanTest.java @@ -0,0 +1,103 @@ +/* + * Copyright 2019 Mahmoud Romeh + * + * 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 + * + * http://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 io.github.resilience4j.retry.autoconfigure; + +import static org.junit.Assert.assertEquals; + +import java.util.List; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +import io.github.resilience4j.TestUtils; +import io.github.resilience4j.consumer.DefaultEventConsumerRegistry; +import io.github.resilience4j.consumer.EventConsumerRegistry; +import io.github.resilience4j.retry.RetryRegistry; +import io.github.resilience4j.retry.configure.RetryAspect; +import io.github.resilience4j.retry.configure.RetryAspectExt; +import io.github.resilience4j.retry.configure.RetryConfiguration; +import io.github.resilience4j.retry.event.RetryEvent; + +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration(classes = { + RetryConfigurationOnMissingBeanTest.ConfigWithOverrides.class, + RetryAutoConfiguration.class, + RetryConfigurationOnMissingBean.class +}) +@EnableConfigurationProperties(RetryProperties.class) +public class RetryConfigurationOnMissingBeanTest { + + @Autowired + private ConfigWithOverrides configWithOverrides; + + @Autowired + private RetryRegistry retryRegistry; + + @Autowired + private RetryAspect retryAspect; + + @Autowired + private EventConsumerRegistry retryEventConsumerRegistry; + + @Test + public void testAllBeansFromRetryHasOnMissingBean() throws NoSuchMethodException { + final Class originalClass = RetryConfiguration.class; + final Class onMissingBeanClass = RetryConfigurationOnMissingBean.class; + TestUtils.assertAnnaations(originalClass, onMissingBeanClass); + } + + @Test + public void testAllRetryConfigurationBeansOverridden() { + assertEquals(retryAspect, configWithOverrides.retryAspect); + assertEquals(retryEventConsumerRegistry, configWithOverrides.retryEventConsumerRegistry); + assertEquals(retryRegistry, configWithOverrides.retryRegistry); + } + + @Configuration + public static class ConfigWithOverrides { + + private RetryRegistry retryRegistry; + + private RetryAspect retryAspect; + + private EventConsumerRegistry retryEventConsumerRegistry; + + @Bean + public RetryRegistry retryRegistry() { + this.retryRegistry = RetryRegistry.ofDefaults(); + return retryRegistry; + } + + @Bean + public RetryAspect retryAspect(RetryRegistry retryRegistry, + @Autowired(required = false) List retryAspectExts) { + this.retryAspect = new RetryAspect(new RetryProperties(), retryRegistry, retryAspectExts); + return retryAspect; + } + + @Bean + public EventConsumerRegistry retryEventConsumerRegistry() { + this.retryEventConsumerRegistry = new DefaultEventConsumerRegistry<>(); + return retryEventConsumerRegistry; + } + } +} \ No newline at end of file diff --git a/resilience4j-spring-boot2/src/test/resources/application.yaml b/resilience4j-spring-boot2/src/test/resources/application.yaml index 88c5aa8c3d..2da9318035 100644 --- a/resilience4j-spring-boot2/src/test/resources/application.yaml +++ b/resilience4j-spring-boot2/src/test/resources/application.yaml @@ -28,6 +28,14 @@ resilience4j.retry: resilience4j.circuitbreaker: circuitBreakerAspectOrder: 400 + configs: + default: + ringBufferSizeInClosedState: 100 + ringBufferSizeInHalfOpenState: 10 + waitDurationInOpenState: 10000 + failureRateThreshold: 60 + eventConsumerBufferSize: 10 + registerHealthIndicator: true backends: backendA: ringBufferSizeInClosedState: 6 @@ -47,6 +55,11 @@ resilience4j.circuitbreaker: failureRateThreshold: 50 eventConsumerBufferSize: 10 registerHealthIndicator: false + backendSharedA: + baseConfig: default + ringBufferSizeInClosedState: 6 + backendSharedB: + baseConfig: default resilience4j.ratelimiter: rateLimiterAspectOrder: 401 diff --git a/resilience4j-spring/build.gradle b/resilience4j-spring/build.gradle index 2e96546141..9dd3ee76aa 100644 --- a/resilience4j-spring/build.gradle +++ b/resilience4j-spring/build.gradle @@ -7,8 +7,8 @@ dependencies { compile project(':resilience4j-circuitbreaker') compileOnly project(':resilience4j-reactor') compileOnly project(':resilience4j-rxjava2') - compileOnly (libraries.rxjava2) - compileOnly (libraries.reactor) + compileOnly(libraries.rxjava2) + compileOnly(libraries.reactor) compile project(':resilience4j-ratelimiter') compile project(':resilience4j-retry') compile project(':resilience4j-bulkhead') @@ -16,5 +16,12 @@ dependencies { compileOnly project(':resilience4j-metrics') testCompile project(':resilience4j-prometheus') testCompile project(':resilience4j-metrics') + testCompile(libraries.aspectj) + testCompile(libraries.rxjava2) + testCompile(libraries.reactor) + testCompile project(':resilience4j-reactor') + testCompile project(':resilience4j-rxjava2') + testCompile(libraries.spring_context) + testCompile(libraries.spring_test) } -ext.moduleName='io.github.resilience4j.spring' \ No newline at end of file +ext.moduleName = 'io.github.resilience4j.spring' \ No newline at end of file diff --git a/resilience4j-spring/src/main/java/io/github/resilience4j/bulkhead/configure/RxJava2BulkheadAspectExt.java b/resilience4j-spring/src/main/java/io/github/resilience4j/bulkhead/configure/RxJava2BulkheadAspectExt.java index 99146024f3..96fb54cc4e 100644 --- a/resilience4j-spring/src/main/java/io/github/resilience4j/bulkhead/configure/RxJava2BulkheadAspectExt.java +++ b/resilience4j-spring/src/main/java/io/github/resilience4j/bulkhead/configure/RxJava2BulkheadAspectExt.java @@ -25,7 +25,6 @@ import io.github.resilience4j.bulkhead.Bulkhead; import io.github.resilience4j.bulkhead.operator.BulkheadOperator; -import io.github.resilience4j.circuitbreaker.configure.CircuitBreakerAspect; import io.reactivex.Completable; import io.reactivex.CompletableSource; import io.reactivex.Flowable; @@ -42,7 +41,7 @@ */ public class RxJava2BulkheadAspectExt implements BulkheadAspectExt { - private static final Logger logger = LoggerFactory.getLogger(CircuitBreakerAspect.class); + private static final Logger logger = LoggerFactory.getLogger(RxJava2BulkheadAspectExt.class); private final Set rxSupportedTypes = newHashSet(ObservableSource.class, SingleSource.class, CompletableSource.class, MaybeSource.class, Flowable.class); /** diff --git a/resilience4j-spring/src/main/java/io/github/resilience4j/circuitbreaker/configure/CircuitBreakerConfiguration.java b/resilience4j-spring/src/main/java/io/github/resilience4j/circuitbreaker/configure/CircuitBreakerConfiguration.java index ef5ea24733..309cf220c2 100644 --- a/resilience4j-spring/src/main/java/io/github/resilience4j/circuitbreaker/configure/CircuitBreakerConfiguration.java +++ b/resilience4j-spring/src/main/java/io/github/resilience4j/circuitbreaker/configure/CircuitBreakerConfiguration.java @@ -25,6 +25,7 @@ import io.github.resilience4j.circuitbreaker.CircuitBreaker; import io.github.resilience4j.circuitbreaker.CircuitBreakerConfig; import io.github.resilience4j.circuitbreaker.CircuitBreakerRegistry; +import io.github.resilience4j.circuitbreaker.configure.CircuitBreakerConfigurationProperties.BackendProperties; import io.github.resilience4j.circuitbreaker.event.CircuitBreakerEvent; import io.github.resilience4j.consumer.DefaultEventConsumerRegistry; import io.github.resilience4j.consumer.EventConsumerRegistry; @@ -38,23 +39,23 @@ @Configuration public class CircuitBreakerConfiguration { + private final CircuitBreakerConfigurationProperties circuitBreakerProperties; + + public CircuitBreakerConfiguration(CircuitBreakerConfigurationProperties circuitBreakerProperties) { + this.circuitBreakerProperties = circuitBreakerProperties; + } + @Bean - public CircuitBreakerRegistry circuitBreakerRegistry(CircuitBreakerConfigurationProperties circuitBreakerProperties, - EventConsumerRegistry eventConsumerRegistry) { + public CircuitBreakerRegistry circuitBreakerRegistry(EventConsumerRegistry eventConsumerRegistry) { CircuitBreakerRegistry circuitBreakerRegistry = CircuitBreakerRegistry.ofDefaults(); - circuitBreakerProperties.getBackends().forEach( - (name, properties) -> { - CircuitBreakerConfig circuitBreakerConfig = circuitBreakerProperties.createCircuitBreakerConfig(name); - CircuitBreaker circuitBreaker = circuitBreakerRegistry.circuitBreaker(name, circuitBreakerConfig); - circuitBreaker.getEventPublisher().onEvent(eventConsumerRegistry.createEventConsumer(name, properties.getEventConsumerBufferSize())); - } - ); + registerPostCreationEventConsumer(circuitBreakerRegistry, eventConsumerRegistry); + initializeBackends(circuitBreakerRegistry); return circuitBreakerRegistry; } @Bean - public CircuitBreakerAspect circuitBreakerAspect(CircuitBreakerConfigurationProperties circuitBreakerProperties, - CircuitBreakerRegistry circuitBreakerRegistry, @Autowired(required = false) List circuitBreakerAspectExtList) { + public CircuitBreakerAspect circuitBreakerAspect(CircuitBreakerRegistry circuitBreakerRegistry, + @Autowired(required = false) List circuitBreakerAspectExtList) { return new CircuitBreakerAspect(circuitBreakerProperties, circuitBreakerRegistry, circuitBreakerAspectExtList); } @@ -82,4 +83,52 @@ public ReactorCircuitBreakerAspectExt reactorCircuitBreakerAspect() { public EventConsumerRegistry eventConsumerRegistry() { return new DefaultEventConsumerRegistry<>(); } + + /** + * Initializes the backends configured in the properties. + * + * @param circuitBreakerRegistry The circuit breaker registry. + */ + public void initializeBackends(CircuitBreakerRegistry circuitBreakerRegistry) { + + circuitBreakerProperties.getBackends().forEach( + (name, properties) -> { + CircuitBreakerConfig circuitBreakerConfig = circuitBreakerProperties.createCircuitBreakerConfig(name); + circuitBreakerRegistry.circuitBreaker(name, circuitBreakerConfig); + } + ); + + } + + /** + * Registers the post creation consumer function that registers the consumer events to the circuit breakers. + * + * @param circuitBreakerRegistry The circuit breaker registry. + * @param eventConsumerRegistry The event consumer registry. + */ + public void registerPostCreationEventConsumer(CircuitBreakerRegistry circuitBreakerRegistry, + EventConsumerRegistry eventConsumerRegistry) { + final EventConsumerRegister eventConsumerRegister = new EventConsumerRegister(eventConsumerRegistry); + circuitBreakerRegistry.registerPostCreationConsumer(eventConsumerRegister::registerEventConsumer); + } + + /** + * Holds onto the event consumer registry for the post creation consumer function. + */ + private final class EventConsumerRegister { + + private final EventConsumerRegistry eventConsumerRegistry; + + public EventConsumerRegister(EventConsumerRegistry eventConsumerRegistry) { + this.eventConsumerRegistry = eventConsumerRegistry; + } + + private void registerEventConsumer(CircuitBreaker circuitBreaker) { + BackendProperties backendProperties = circuitBreakerProperties.findCircuitBreakerBackend(circuitBreaker, circuitBreaker.getCircuitBreakerConfig()); + + if (backendProperties != null) { + circuitBreaker.getEventPublisher().onEvent(eventConsumerRegistry.createEventConsumer(circuitBreaker.getName(), backendProperties.getEventConsumerBufferSize())); + } + } + } } diff --git a/resilience4j-spring/src/main/java/io/github/resilience4j/circuitbreaker/configure/CircuitBreakerConfigurationProperties.java b/resilience4j-spring/src/main/java/io/github/resilience4j/circuitbreaker/configure/CircuitBreakerConfigurationProperties.java index 18f02a7f47..f8d046180c 100644 --- a/resilience4j-spring/src/main/java/io/github/resilience4j/circuitbreaker/configure/CircuitBreakerConfigurationProperties.java +++ b/resilience4j-spring/src/main/java/io/github/resilience4j/circuitbreaker/configure/CircuitBreakerConfigurationProperties.java @@ -15,279 +15,372 @@ * limitations under the License. */ -import io.github.resilience4j.circuitbreaker.CircuitBreakerConfig; -import io.github.resilience4j.circuitbreaker.CircuitBreakerConfig.Builder; -import io.github.resilience4j.core.lang.Nullable; -import org.hibernate.validator.constraints.time.DurationMin; -import org.springframework.beans.BeanUtils; - -import javax.validation.constraints.Max; -import javax.validation.constraints.Min; -import javax.validation.constraints.NotNull; import java.time.Duration; import java.util.HashMap; import java.util.Map; import java.util.function.Predicate; -public class CircuitBreakerConfigurationProperties { - // This property gives you control over CircuitBreaker aspect application order. - // By default CircuitBreaker will be executed BEFORE RateLimiter. - // By adjusting RateLimiterProperties.rateLimiterAspectOrder and CircuitBreakerProperties.circuitBreakerAspectOrder - // you explicitly define aspects CircuitBreaker and RateLimiter execution sequence. - private int circuitBreakerAspectOrder = Integer.MAX_VALUE - 1; - private Map backends = new HashMap<>(); +import javax.validation.constraints.Max; +import javax.validation.constraints.Min; +import javax.validation.constraints.NotNull; + +import org.hibernate.validator.constraints.time.DurationMin; +import org.springframework.beans.BeanUtils; +import org.springframework.context.annotation.Configuration; +import org.springframework.util.StringUtils; - public int getCircuitBreakerAspectOrder() { - return circuitBreakerAspectOrder; - } +import io.github.resilience4j.circuitbreaker.CircuitBreaker; +import io.github.resilience4j.circuitbreaker.CircuitBreakerConfig; +import io.github.resilience4j.circuitbreaker.CircuitBreakerConfig.Builder; +import io.github.resilience4j.core.lang.Nullable; +import io.github.resilience4j.utils.CommonUtils; - public void setCircuitBreakerAspectOrder(int circuitBreakerAspectOrder) { - this.circuitBreakerAspectOrder = circuitBreakerAspectOrder; - } +@Configuration +public class CircuitBreakerConfigurationProperties { + // This property gives you control over CircuitBreaker aspect application order. + // By default CircuitBreaker will be executed BEFORE RateLimiter. + // By adjusting RateLimiterProperties.rateLimiterAspectOrder and CircuitBreakerProperties.circuitBreakerAspectOrder + // you explicitly define aspects CircuitBreaker and RateLimiter execution sequence. + private static final String DEFAULT_CONFIG_KEY = "default"; + private int circuitBreakerAspectOrder = Integer.MAX_VALUE - 1; + private Map backends = new HashMap<>(); + private Map configs = new HashMap<>(); + + public int getCircuitBreakerAspectOrder() { + return circuitBreakerAspectOrder; + } + + public void setCircuitBreakerAspectOrder(int circuitBreakerAspectOrder) { + this.circuitBreakerAspectOrder = circuitBreakerAspectOrder; + } + + @Nullable + private BackendProperties getBackendProperties(String backend) { + BackendProperties backendProperties = backends.get(backend); + if (backendProperties != null && !StringUtils.isEmpty(backendProperties.getBaseConfig())) { + return CommonUtils.mergeProperties(backendProperties, getConfigProperties(backendProperties.getBaseConfig())); + + } + return backendProperties; + } + + private BackendProperties getConfigProperties(String sharedConfig) { + return configs.getOrDefault(sharedConfig, configs.computeIfAbsent(DEFAULT_CONFIG_KEY, key -> CommonUtils.getDefaultProperties())); + } + + public CircuitBreakerConfig createCircuitBreakerConfig(String backend) { + return createCircuitBreakerConfig(getBackendProperties(backend)); + } + + public CircuitBreakerConfig createCircuitBreakerConfigFrom(String baseConfigName) { + BackendProperties backendProperties = getConfigProperties(baseConfigName); + if (!StringUtils.isEmpty(backendProperties.getBaseConfig())) { + return buildCircuitBreakerConfig(backendProperties).configurationName(baseConfigName).build(); + } + return createCircuitBreakerConfig(getConfigProperties(baseConfigName)); + } + + private CircuitBreakerConfig createCircuitBreakerConfig(@Nullable BackendProperties backendProperties) { + return buildCircuitBreakerConfig(backendProperties).build(); + } + + public Builder buildCircuitBreakerConfig(@Nullable BackendProperties properties) { + if (properties == null) { + return new Builder(); + } + + Builder builder = CircuitBreakerConfig.custom(); + + if (properties.getWaitDurationInOpenState() != null) { + builder.waitDurationInOpenState(properties.getWaitDurationInOpenState()); + } + + if (properties.getFailureRateThreshold() != null) { + builder.failureRateThreshold(properties.getFailureRateThreshold()); + } - private BackendProperties getBackendProperties(String backend) { - return backends.get(backend); - } - - public CircuitBreakerConfig createCircuitBreakerConfig(String backend) { - return createCircuitBreakerConfig(getBackendProperties(backend)); - } - - private CircuitBreakerConfig createCircuitBreakerConfig(BackendProperties backendProperties) { - return buildCircuitBreakerConfig(backendProperties).build(); - } - - public Builder buildCircuitBreakerConfig(@Nullable BackendProperties properties) { - if (properties == null) { - return new Builder(); - } - - Builder builder = CircuitBreakerConfig.custom(); - - if (properties.getWaitDurationInOpenState() != null) { - builder.waitDurationInOpenState(properties.getWaitDurationInOpenState()); - } - - if (properties.getFailureRateThreshold() != null) { - builder.failureRateThreshold(properties.getFailureRateThreshold()); - } - - if (properties.getRingBufferSizeInClosedState() != null) { - builder.ringBufferSizeInClosedState(properties.getRingBufferSizeInClosedState()); - } - - if (properties.getRingBufferSizeInHalfOpenState() != null) { - builder.ringBufferSizeInHalfOpenState(properties.getRingBufferSizeInHalfOpenState()); - } - - if (properties.recordFailurePredicate != null) { - buildRecordFailurePredicate(properties, builder); - } - - if (properties.recordExceptions != null) { - builder.recordExceptions(properties.recordExceptions); - } - - if (properties.ignoreExceptions != null) { - builder.ignoreExceptions(properties.ignoreExceptions); - } - - if (properties.automaticTransitionFromOpenToHalfOpenEnabled) { - builder.enableAutomaticTransitionFromOpenToHalfOpen(); - } - return builder; - } - - protected void buildRecordFailurePredicate(BackendProperties properties, Builder builder) { - builder.recordFailure(BeanUtils.instantiateClass(properties.getRecordFailurePredicate())); - } - - public Map getBackends() { - return backends; - } - - /** - * Class storing property values for configuring {@link io.github.resilience4j.circuitbreaker.CircuitBreaker} instances. - */ - public static class BackendProperties { - - @DurationMin(seconds = 1) - @Nullable - private Duration waitDurationInOpenState; - - @Min(1) - @Max(100) - @Nullable - private Integer failureRateThreshold; - - @Min(1) - @Nullable - private Integer ringBufferSizeInClosedState; - - @Min(1) - @Nullable - private Integer ringBufferSizeInHalfOpenState; - - @NotNull - private Boolean automaticTransitionFromOpenToHalfOpenEnabled = false; - - @Min(1) - private Integer eventConsumerBufferSize = 100; - - @NotNull - private Boolean registerHealthIndicator = true; - - @Nullable - private Class> recordFailurePredicate; - - @Nullable - private Class[] recordExceptions; - - @Nullable - private Class[] ignoreExceptions; - /** - * Sets the wait duration in seconds the CircuitBreaker should stay open, before it switches to half closed. - * - * @param waitInterval the wait duration - */ - @Deprecated - public void setWaitInterval(Integer waitInterval) { - this.waitDurationInOpenState = Duration.ofMillis(waitInterval); - } - - /** - * Returns the failure rate threshold for the circuit breaker as percentage. - * - * @return the failure rate threshold - */ - @Nullable - public Integer getFailureRateThreshold() { - return failureRateThreshold; - } - - /** - * Sets the failure rate threshold for the circuit breaker as percentage. - * - * @param failureRateThreshold the failure rate threshold - */ - public void setFailureRateThreshold(Integer failureRateThreshold) { - this.failureRateThreshold = failureRateThreshold; - } - - /** - * Returns the wait duration the CircuitBreaker will stay open, before it switches to half closed. - * - * @return the wait duration - */ - @Nullable - public Duration getWaitDurationInOpenState() { - return waitDurationInOpenState; - } - - /** - * Sets the wait duration the CircuitBreaker should stay open, before it switches to half closed. - * - * @param waitDurationInOpenState the wait duration - */ - public void setWaitDurationInOpenState(Duration waitDurationInOpenState) { - this.waitDurationInOpenState = waitDurationInOpenState; - } - - /** - * Returns the ring buffer size for the circuit breaker while in closed state. - * - * @return the ring buffer size - */ - @Nullable - public Integer getRingBufferSizeInClosedState() { - return ringBufferSizeInClosedState; - } - - /** - * Sets the ring buffer size for the circuit breaker while in closed state. - * - * @param ringBufferSizeInClosedState the ring buffer size - */ - public void setRingBufferSizeInClosedState(Integer ringBufferSizeInClosedState) { - this.ringBufferSizeInClosedState = ringBufferSizeInClosedState; - } - - /** - * Returns the ring buffer size for the circuit breaker while in half open state. - * - * @return the ring buffer size - */ - @Nullable - public Integer getRingBufferSizeInHalfOpenState() { - return ringBufferSizeInHalfOpenState; - } - - /** - * Sets the ring buffer size for the circuit breaker while in half open state. - * - * @param ringBufferSizeInHalfOpenState the ring buffer size - */ - public void setRingBufferSizeInHalfOpenState(Integer ringBufferSizeInHalfOpenState) { - this.ringBufferSizeInHalfOpenState = ringBufferSizeInHalfOpenState; - } - - /** - * Returns if we should automaticly transition to half open after the timer has run out. - * - * @return automaticTransitionFromOpenToHalfOpenEnabled if we should automaticly go to half open or not - */ - public Boolean getAutomaticTransitionFromOpenToHalfOpenEnabled() { - return this.automaticTransitionFromOpenToHalfOpenEnabled; - } - - /** - * Sets if we should automaticly transition to half open after the timer has run out. - */ - public void setAutomaticTransitionFromOpenToHalfOpenEnabled(Boolean automaticTransitionFromOpenToHalfOpenEnabled) { - this.automaticTransitionFromOpenToHalfOpenEnabled = automaticTransitionFromOpenToHalfOpenEnabled; - } - - public Integer getEventConsumerBufferSize() { - return eventConsumerBufferSize; - } - - public void setEventConsumerBufferSize(Integer eventConsumerBufferSize) { - this.eventConsumerBufferSize = eventConsumerBufferSize; - } - - public Boolean getRegisterHealthIndicator() { - return registerHealthIndicator; - } - - public void setRegisterHealthIndicator(Boolean registerHealthIndicator) { - this.registerHealthIndicator = registerHealthIndicator; - } - - @Nullable - public Class> getRecordFailurePredicate() { - return recordFailurePredicate; - } - - public void setRecordFailurePredicate(Class> recordFailurePredicate) { - this.recordFailurePredicate = recordFailurePredicate; - } - - @Nullable - public Class[] getRecordExceptions() { - return recordExceptions; - } - - public void setRecordExceptions(Class[] recordExceptions) { - this.recordExceptions = recordExceptions; - } - - @Nullable - public Class[] getIgnoreExceptions() { - return ignoreExceptions; - } - - public void setIgnoreExceptions(Class[] ignoreExceptions) { - this.ignoreExceptions = ignoreExceptions; - } } + if (properties.getRingBufferSizeInClosedState() != null) { + builder.ringBufferSizeInClosedState(properties.getRingBufferSizeInClosedState()); + } + + if (properties.getRingBufferSizeInHalfOpenState() != null) { + builder.ringBufferSizeInHalfOpenState(properties.getRingBufferSizeInHalfOpenState()); + } + + if (properties.recordFailurePredicate != null) { + buildRecordFailurePredicate(properties, builder); + } + + if (properties.recordExceptions != null) { + builder.recordExceptions(properties.recordExceptions); + } + + if (properties.ignoreExceptions != null) { + builder.ignoreExceptions(properties.ignoreExceptions); + } + + if (properties.automaticTransitionFromOpenToHalfOpenEnabled) { + builder.enableAutomaticTransitionFromOpenToHalfOpen(); + } + + if (properties.baseConfig != null) { + builder.configurationName(properties.baseConfig); + } + + return builder; + } + + protected void buildRecordFailurePredicate(BackendProperties properties, Builder builder) { + builder.recordFailure(BeanUtils.instantiateClass(properties.getRecordFailurePredicate())); + } + + public Map getBackends() { + return backends; + } + + public Map getConfigs() { + return configs; + } + + @Nullable + public BackendProperties findCircuitBreakerBackend(CircuitBreaker circuitBreaker, CircuitBreakerConfig circuitBreakerConfig) { + BackendProperties backendProperties = backends.getOrDefault(circuitBreaker.getName(), null); + + if (circuitBreakerConfig.getConfigurationName() != null + && configs.containsKey(circuitBreakerConfig.getConfigurationName())) { + backendProperties = configs.get(circuitBreakerConfig.getConfigurationName()); + } + + return backendProperties; + } + + /** + * Class storing property values for configuring {@link io.github.resilience4j.circuitbreaker.CircuitBreaker} instances. + */ + public static class BackendProperties { + + @DurationMin(seconds = 1) + @Nullable + private Duration waitDurationInOpenState; + + @Min(1) + @Max(100) + @Nullable + private Integer failureRateThreshold; + + @Min(1) + @Nullable + private Integer ringBufferSizeInClosedState; + + @Min(1) + @Nullable + private Integer ringBufferSizeInHalfOpenState; + + @NotNull + private Boolean automaticTransitionFromOpenToHalfOpenEnabled = false; + + @Min(1) + private Integer eventConsumerBufferSize = 100; + + @NotNull + private Boolean registerHealthIndicator = true; + + @Nullable + private Class> recordFailurePredicate; + + @Nullable + private Class[] recordExceptions; + + @Nullable + private Class[] ignoreExceptions; + + @Nullable + private String baseConfig; + + + /** + * @deprecated + * @since (0.0.14) use instead {@link #setWaitDurationInOpenState(Duration)} ()} + * Sets the wait duration in seconds the CircuitBreaker should stay open, before it switches to half closed. + * + * @param waitInterval the wait duration + */ + @Deprecated + public void setWaitInterval(Integer waitInterval) { + this.waitDurationInOpenState = Duration.ofMillis(waitInterval); + } + + /** + * @deprecated + * @since (0.0.14) use instead {@link #getWaitDurationInOpenState()} + * + * get the wait duration in seconds the CircuitBreaker should stay open, before it switches to half closed. + * + * @return waitInterval the wait duration + */ + @Deprecated + public Duration getWaitInterval() { + return getWaitDurationInOpenState(); + } + + /** + * Returns the failure rate threshold for the circuit breaker as percentage. + * + * @return the failure rate threshold + */ + @Nullable + public Integer getFailureRateThreshold() { + return failureRateThreshold; + } + + /** + * Sets the failure rate threshold for the circuit breaker as percentage. + * + * @param failureRateThreshold the failure rate threshold + */ + public void setFailureRateThreshold(Integer failureRateThreshold) { + this.failureRateThreshold = failureRateThreshold; + } + + /** + * Returns the wait duration the CircuitBreaker will stay open, before it switches to half closed. + * + * @return the wait duration + */ + @Nullable + public Duration getWaitDurationInOpenState() { + return waitDurationInOpenState; + } + + /** + * Sets the wait duration the CircuitBreaker should stay open, before it switches to half closed. + * + * @param waitDurationInOpenState the wait duration + */ + public void setWaitDurationInOpenState(Duration waitDurationInOpenState) { + this.waitDurationInOpenState = waitDurationInOpenState; + } + + /** + * Returns the ring buffer size for the circuit breaker while in closed state. + * + * @return the ring buffer size + */ + @Nullable + public Integer getRingBufferSizeInClosedState() { + return ringBufferSizeInClosedState; + } + + /** + * Sets the ring buffer size for the circuit breaker while in closed state. + * + * @param ringBufferSizeInClosedState the ring buffer size + */ + public void setRingBufferSizeInClosedState(Integer ringBufferSizeInClosedState) { + this.ringBufferSizeInClosedState = ringBufferSizeInClosedState; + } + + /** + * Returns the ring buffer size for the circuit breaker while in half open state. + * + * @return the ring buffer size + */ + @Nullable + public Integer getRingBufferSizeInHalfOpenState() { + return ringBufferSizeInHalfOpenState; + } + + /** + * Sets the ring buffer size for the circuit breaker while in half open state. + * + * @param ringBufferSizeInHalfOpenState the ring buffer size + */ + public void setRingBufferSizeInHalfOpenState(Integer ringBufferSizeInHalfOpenState) { + this.ringBufferSizeInHalfOpenState = ringBufferSizeInHalfOpenState; + } + + /** + * Returns if we should automaticly transition to half open after the timer has run out. + * + * @return automaticTransitionFromOpenToHalfOpenEnabled if we should automaticly go to half open or not + */ + public Boolean getAutomaticTransitionFromOpenToHalfOpenEnabled() { + return this.automaticTransitionFromOpenToHalfOpenEnabled; + } + + /** + * Sets if we should automatically transition to half open after the timer has run out. + * + * @param automaticTransitionFromOpenToHalfOpenEnabled The flag for automatic transition to half open after the timer has run out. + */ + public void setAutomaticTransitionFromOpenToHalfOpenEnabled(Boolean automaticTransitionFromOpenToHalfOpenEnabled) { + this.automaticTransitionFromOpenToHalfOpenEnabled = automaticTransitionFromOpenToHalfOpenEnabled; + } + + public Integer getEventConsumerBufferSize() { + return eventConsumerBufferSize; + } + + public void setEventConsumerBufferSize(Integer eventConsumerBufferSize) { + this.eventConsumerBufferSize = eventConsumerBufferSize; + } + + public Boolean getRegisterHealthIndicator() { + return registerHealthIndicator; + } + + public void setRegisterHealthIndicator(Boolean registerHealthIndicator) { + this.registerHealthIndicator = registerHealthIndicator; + } + + @Nullable + public Class> getRecordFailurePredicate() { + return recordFailurePredicate; + } + + public void setRecordFailurePredicate(Class> recordFailurePredicate) { + this.recordFailurePredicate = recordFailurePredicate; + } + + @Nullable + public Class[] getRecordExceptions() { + return recordExceptions; + } + + public void setRecordExceptions(Class[] recordExceptions) { + this.recordExceptions = recordExceptions; + } + + @Nullable + public Class[] getIgnoreExceptions() { + return ignoreExceptions; + } + + public void setIgnoreExceptions(Class[] ignoreExceptions) { + this.ignoreExceptions = ignoreExceptions; + } + + /** + * Gets the shared configuration name. If this is set, the configuration builder will use the the shared + * configuration backend over this one. + * + * @return The shared configuration name. + */ + @Nullable + public String getBaseConfig() { + return baseConfig; + } + + /** + * Sets the shared configuration name. If this is set, the configuration builder will use the the shared + * configuration backend over this one. + * + * @param baseConfig The shared configuration name. + */ + public void setBaseConfig(String baseConfig) { + this.baseConfig = baseConfig; + } + + } } diff --git a/resilience4j-spring/src/main/java/io/github/resilience4j/circuitbreaker/configure/RxJava2CircuitBreakerAspectExt.java b/resilience4j-spring/src/main/java/io/github/resilience4j/circuitbreaker/configure/RxJava2CircuitBreakerAspectExt.java index ba83308934..760b2f7d6a 100644 --- a/resilience4j-spring/src/main/java/io/github/resilience4j/circuitbreaker/configure/RxJava2CircuitBreakerAspectExt.java +++ b/resilience4j-spring/src/main/java/io/github/resilience4j/circuitbreaker/configure/RxJava2CircuitBreakerAspectExt.java @@ -41,7 +41,7 @@ */ public class RxJava2CircuitBreakerAspectExt implements CircuitBreakerAspectExt { - private static final Logger logger = LoggerFactory.getLogger(CircuitBreakerAspect.class); + private static final Logger logger = LoggerFactory.getLogger(RxJava2CircuitBreakerAspectExt.class); private final Set rxSupportedTypes = newHashSet(ObservableSource.class, SingleSource.class, CompletableSource.class, MaybeSource.class, Flowable.class); /** diff --git a/resilience4j-spring/src/main/java/io/github/resilience4j/ratelimiter/configure/RateLimiterConfiguration.java b/resilience4j-spring/src/main/java/io/github/resilience4j/ratelimiter/configure/RateLimiterConfiguration.java index 57834ff200..3d86d4fe33 100644 --- a/resilience4j-spring/src/main/java/io/github/resilience4j/ratelimiter/configure/RateLimiterConfiguration.java +++ b/resilience4j-spring/src/main/java/io/github/resilience4j/ratelimiter/configure/RateLimiterConfiguration.java @@ -22,7 +22,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.config.ConfigurableBeanFactory; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Conditional; import org.springframework.context.annotation.Configuration; @@ -48,8 +47,7 @@ public class RateLimiterConfiguration { @Bean public RateLimiterRegistry rateLimiterRegistry(RateLimiterConfigurationProperties rateLimiterProperties, - EventConsumerRegistry rateLimiterEventsConsumerRegistry, - ConfigurableBeanFactory beanFactory) { + EventConsumerRegistry rateLimiterEventsConsumerRegistry) { RateLimiterRegistry rateLimiterRegistry = RateLimiterRegistry.of(RateLimiterConfig.ofDefaults()); rateLimiterProperties.getLimiters().forEach( (name, properties) -> { @@ -69,8 +67,8 @@ public RateLimiterAspect rateLimiterAspect(RateLimiterConfigurationProperties ra @Bean @Conditional(value = {RxJava2OnClasspathCondition.class}) - public RxJava2RateLimterAspectExt rxJava2RateLimterAspectExt() { - return new RxJava2RateLimterAspectExt(); + public RxJava2RateLimiterAspectExt rxJava2RateLimterAspectExt() { + return new RxJava2RateLimiterAspectExt(); } @Bean @@ -83,6 +81,8 @@ public ReactorRateLimiterAspectExt reactorRateLimiterAspectExt() { * The EventConsumerRegistry is used to manage EventConsumer instances. * The EventConsumerRegistry is used by the RateLimiterHealthIndicator to show the latest RateLimiterEvents events * for each RateLimiter instance. + * + * @return The EventConsumerRegistry of RateLimiterEvent bean. */ @Bean public EventConsumerRegistry rateLimiterEventsConsumerRegistry() { diff --git a/resilience4j-spring/src/main/java/io/github/resilience4j/ratelimiter/configure/RxJava2RateLimterAspectExt.java b/resilience4j-spring/src/main/java/io/github/resilience4j/ratelimiter/configure/RxJava2RateLimiterAspectExt.java similarity index 93% rename from resilience4j-spring/src/main/java/io/github/resilience4j/ratelimiter/configure/RxJava2RateLimterAspectExt.java rename to resilience4j-spring/src/main/java/io/github/resilience4j/ratelimiter/configure/RxJava2RateLimiterAspectExt.java index 9806f79d33..3be0b62816 100644 --- a/resilience4j-spring/src/main/java/io/github/resilience4j/ratelimiter/configure/RxJava2RateLimterAspectExt.java +++ b/resilience4j-spring/src/main/java/io/github/resilience4j/ratelimiter/configure/RxJava2RateLimiterAspectExt.java @@ -23,7 +23,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import io.github.resilience4j.circuitbreaker.configure.CircuitBreakerAspect; import io.github.resilience4j.ratelimiter.RateLimiter; import io.github.resilience4j.ratelimiter.operator.RateLimiterOperator; import io.reactivex.Completable; @@ -40,9 +39,9 @@ * the Rx RateLimiter logic support for the spring AOP * conditional on the presence of Rx classes on the spring class loader */ -public class RxJava2RateLimterAspectExt implements RateLimiterAspectExt { +public class RxJava2RateLimiterAspectExt implements RateLimiterAspectExt { - private static final Logger logger = LoggerFactory.getLogger(CircuitBreakerAspect.class); + private static final Logger logger = LoggerFactory.getLogger(RxJava2RateLimiterAspectExt.class); private final Set rxSupportedTypes = newHashSet(ObservableSource.class, SingleSource.class, CompletableSource.class, MaybeSource.class, Flowable.class); /** diff --git a/resilience4j-spring/src/main/java/io/github/resilience4j/retry/configure/RetryConfigurationProperties.java b/resilience4j-spring/src/main/java/io/github/resilience4j/retry/configure/RetryConfigurationProperties.java index 4f6abc9f28..c73ddfeab8 100644 --- a/resilience4j-spring/src/main/java/io/github/resilience4j/retry/configure/RetryConfigurationProperties.java +++ b/resilience4j-spring/src/main/java/io/github/resilience4j/retry/configure/RetryConfigurationProperties.java @@ -89,6 +89,7 @@ private BackendProperties getBackendProperties(String backend) { * @param properties the configured spring backend properties * @return retry config builder instance */ + @SuppressWarnings("unchecked") public RetryConfig.Builder buildRetryConfig(BackendProperties properties) { if (properties == null) { return new RetryConfig.Builder(); diff --git a/resilience4j-spring/src/main/java/io/github/resilience4j/retry/configure/RxJava2RetryAspectExt.java b/resilience4j-spring/src/main/java/io/github/resilience4j/retry/configure/RxJava2RetryAspectExt.java index aa9ff4b5ec..8b0219fb86 100644 --- a/resilience4j-spring/src/main/java/io/github/resilience4j/retry/configure/RxJava2RetryAspectExt.java +++ b/resilience4j-spring/src/main/java/io/github/resilience4j/retry/configure/RxJava2RetryAspectExt.java @@ -23,7 +23,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import io.github.resilience4j.circuitbreaker.configure.CircuitBreakerAspect; import io.github.resilience4j.retry.Retry; import io.github.resilience4j.retry.transformer.RetryTransformer; import io.reactivex.Completable; @@ -42,7 +41,7 @@ */ public class RxJava2RetryAspectExt implements RetryAspectExt { - private static final Logger logger = LoggerFactory.getLogger(CircuitBreakerAspect.class); + private static final Logger logger = LoggerFactory.getLogger(RxJava2RetryAspectExt.class); private final Set rxSupportedTypes = newHashSet(ObservableSource.class, SingleSource.class, CompletableSource.class, MaybeSource.class, Flowable.class); /** diff --git a/resilience4j-spring/src/main/java/io/github/resilience4j/utils/AnnotationExtractor.java b/resilience4j-spring/src/main/java/io/github/resilience4j/utils/AnnotationExtractor.java index 97ba67a968..53a09cb489 100644 --- a/resilience4j-spring/src/main/java/io/github/resilience4j/utils/AnnotationExtractor.java +++ b/resilience4j-spring/src/main/java/io/github/resilience4j/utils/AnnotationExtractor.java @@ -17,6 +17,7 @@ private AnnotationExtractor() { * * @param targetClass target class * @param annotationClass annotation class + * @param The annotation type. * @return annotation */ @Nullable diff --git a/resilience4j-spring/src/main/java/io/github/resilience4j/utils/CommonUtils.java b/resilience4j-spring/src/main/java/io/github/resilience4j/utils/CommonUtils.java new file mode 100644 index 0000000000..fd6977a8f5 --- /dev/null +++ b/resilience4j-spring/src/main/java/io/github/resilience4j/utils/CommonUtils.java @@ -0,0 +1,74 @@ +/* + * Copyright 2019 Mahmoud Romeh + * + * 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 + * + * http://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 io.github.resilience4j.utils; + +import java.beans.FeatureDescriptor; +import java.util.stream.Stream; + +import org.springframework.beans.BeanUtils; +import org.springframework.beans.BeanWrapper; +import org.springframework.beans.BeanWrapperImpl; + +import io.github.resilience4j.circuitbreaker.CircuitBreakerConfig; +import io.github.resilience4j.circuitbreaker.configure.CircuitBreakerConfigurationProperties; + +/** + * common utils for spring configuration + */ +public class CommonUtils { + + + private CommonUtils(){} + + /** + * @param source source entity + * @param target target entity + * @param type of entity + * @return merged entity with its properties + */ + public static T mergeProperties(T source, T target) { + BeanUtils.copyProperties(source, target, getNullPropertyNames(source)); + return target; + } + + /** + * @return default backend properties for the circuit breaker + */ + public static CircuitBreakerConfigurationProperties.BackendProperties getDefaultProperties() { + CircuitBreakerConfig circuitBreakerConfig = CircuitBreakerConfig.ofDefaults(); + CircuitBreakerConfigurationProperties.BackendProperties backendProperties = new CircuitBreakerConfigurationProperties.BackendProperties(); + backendProperties.setFailureRateThreshold(Math.round(circuitBreakerConfig.getFailureRateThreshold())); + backendProperties.setAutomaticTransitionFromOpenToHalfOpenEnabled(circuitBreakerConfig.isAutomaticTransitionFromOpenToHalfOpenEnabled()); + backendProperties.setEventConsumerBufferSize(100); + backendProperties.setRegisterHealthIndicator(true); + backendProperties.setRingBufferSizeInClosedState(circuitBreakerConfig.getRingBufferSizeInClosedState()); + backendProperties.setRingBufferSizeInHalfOpenState(circuitBreakerConfig.getRingBufferSizeInHalfOpenState()); + backendProperties.setWaitDurationInOpenState(circuitBreakerConfig.getWaitDurationInOpenState()); + return backendProperties; + } + + /** + * @param source source entity + * @return array of null properties + */ + private static String[] getNullPropertyNames(Object source) { + final BeanWrapper wrappedSource = new BeanWrapperImpl(source); + return Stream.of(wrappedSource.getPropertyDescriptors()) + .map(FeatureDescriptor::getName) + .filter(propertyName -> wrappedSource.getPropertyValue(propertyName) == null) + .toArray(String[]::new); + } +} diff --git a/resilience4j-spring/src/test/java/io/github/resilience4j/bulkhead/configure/ReactorBulkheadAspectExtTest.java b/resilience4j-spring/src/test/java/io/github/resilience4j/bulkhead/configure/ReactorBulkheadAspectExtTest.java new file mode 100644 index 0000000000..103f41a990 --- /dev/null +++ b/resilience4j-spring/src/test/java/io/github/resilience4j/bulkhead/configure/ReactorBulkheadAspectExtTest.java @@ -0,0 +1,63 @@ +/* + * Copyright 2019 Mahmoud Romeh + * + * 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 + * + * http://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 io.github.resilience4j.bulkhead.configure; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.when; + +import org.aspectj.lang.ProceedingJoinPoint; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; + +import io.github.resilience4j.bulkhead.Bulkhead; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +/** + * aspect unit test + */ +@RunWith(MockitoJUnitRunner.class) +public class ReactorBulkheadAspectExtTest { + + @Mock + ProceedingJoinPoint proceedingJoinPoint; + + @InjectMocks + ReactorBulkheadAspectExt reactorBulkheadAspectExt; + + + @Test + public void testCheckTypes() { + assertThat(reactorBulkheadAspectExt.canHandleReturnType(Mono.class)).isTrue(); + assertThat(reactorBulkheadAspectExt.canHandleReturnType(Flux.class)).isTrue(); + } + + @Test + public void testReactorTypes() throws Throwable { + Bulkhead bulkhead = Bulkhead.ofDefaults("test"); + + when(proceedingJoinPoint.proceed()).thenReturn(Mono.just("Test")); + assertThat(reactorBulkheadAspectExt.handle(proceedingJoinPoint, bulkhead, "testMethod")).isNotNull(); + + when(proceedingJoinPoint.proceed()).thenReturn(Flux.just("Test")); + assertThat(reactorBulkheadAspectExt.handle(proceedingJoinPoint, bulkhead, "testMethod")).isNotNull(); + } + + +} \ No newline at end of file diff --git a/resilience4j-spring/src/test/java/io/github/resilience4j/bulkhead/configure/RxJava2BulkheadAspectExtTest.java b/resilience4j-spring/src/test/java/io/github/resilience4j/bulkhead/configure/RxJava2BulkheadAspectExtTest.java new file mode 100644 index 0000000000..7e975efd73 --- /dev/null +++ b/resilience4j-spring/src/test/java/io/github/resilience4j/bulkhead/configure/RxJava2BulkheadAspectExtTest.java @@ -0,0 +1,63 @@ +/* + * Copyright 2019 Mahmoud Romeh + * + * 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 + * + * http://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 io.github.resilience4j.bulkhead.configure; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.when; + +import org.aspectj.lang.ProceedingJoinPoint; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; + +import io.github.resilience4j.bulkhead.Bulkhead; +import io.reactivex.Flowable; +import io.reactivex.Single; + +/** + * aspect unit test + */ +@RunWith(MockitoJUnitRunner.class) +public class RxJava2BulkheadAspectExtTest { + + @Mock + ProceedingJoinPoint proceedingJoinPoint; + + @InjectMocks + RxJava2BulkheadAspectExt rxJava2BulkheadAspectExt; + + + @Test + public void testCheckTypes() { + assertThat(rxJava2BulkheadAspectExt.canHandleReturnType(Flowable.class)).isTrue(); + assertThat(rxJava2BulkheadAspectExt.canHandleReturnType(Single.class)).isTrue(); + } + + @Test + public void testReactorTypes() throws Throwable { + Bulkhead bulkhead = Bulkhead.ofDefaults("test"); + + when(proceedingJoinPoint.proceed()).thenReturn(Single.just("Test")); + assertThat(rxJava2BulkheadAspectExt.handle(proceedingJoinPoint, bulkhead, "testMethod")).isNotNull(); + + when(proceedingJoinPoint.proceed()).thenReturn(Flowable.just("Test")); + assertThat(rxJava2BulkheadAspectExt.handle(proceedingJoinPoint, bulkhead, "testMethod")).isNotNull(); + } + + +} \ No newline at end of file diff --git a/resilience4j-spring/src/test/java/io/github/resilience4j/circuitbreaker/configure/CircuitBreakerConfigurationSpringTest.java b/resilience4j-spring/src/test/java/io/github/resilience4j/circuitbreaker/configure/CircuitBreakerConfigurationSpringTest.java new file mode 100644 index 0000000000..8eebc20063 --- /dev/null +++ b/resilience4j-spring/src/test/java/io/github/resilience4j/circuitbreaker/configure/CircuitBreakerConfigurationSpringTest.java @@ -0,0 +1,113 @@ +/* + * Copyright 2019 Mahmoud Romeh + * + * 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 + * + * http://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 io.github.resilience4j.circuitbreaker.configure; + +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import java.util.List; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +import io.github.resilience4j.circuitbreaker.CircuitBreaker; +import io.github.resilience4j.circuitbreaker.CircuitBreakerConfig; +import io.github.resilience4j.circuitbreaker.CircuitBreakerRegistry; +import io.github.resilience4j.circuitbreaker.event.CircuitBreakerEvent; +import io.github.resilience4j.consumer.DefaultEventConsumerRegistry; +import io.github.resilience4j.consumer.EventConsumerRegistry; + +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration(classes = { + CircuitBreakerConfigurationSpringTest.ConfigWithOverrides.class +}) +public class CircuitBreakerConfigurationSpringTest { + + @Autowired + private ConfigWithOverrides configWithOverrides; + + + @Test + public void testAllCircuitBreakerConfigurationBeansOverridden() { + assertNotNull(configWithOverrides.circuitBreakerRegistry); + assertNotNull(configWithOverrides.circuitBreakerAspect); + assertNotNull(configWithOverrides.circuitEventConsumerBreakerRegistry); + assertNotNull(configWithOverrides.circuitBreakerConfigurationProperties); + assertTrue(configWithOverrides.circuitBreakerConfigurationProperties.getConfigs().size() == 1); + final CircuitBreakerConfigurationProperties.BackendProperties circuitBreakerBackend = configWithOverrides.circuitBreakerConfigurationProperties + .findCircuitBreakerBackend(CircuitBreaker.ofDefaults("testBackEndForShared"), CircuitBreakerConfig.custom().configurationName("sharedBackend").build()); + assertTrue(circuitBreakerBackend.getBaseConfig().equals("sharedConfig")); + assertTrue(circuitBreakerBackend.getFailureRateThreshold() == 3); + + } + + @Configuration + @ComponentScan("io.github.resilience4j.circuitbreaker") + public static class ConfigWithOverrides { + + private CircuitBreakerRegistry circuitBreakerRegistry; + + private CircuitBreakerAspect circuitBreakerAspect; + + private EventConsumerRegistry circuitEventConsumerBreakerRegistry; + + private CircuitBreakerConfigurationProperties circuitBreakerConfigurationProperties; + + @Bean + public CircuitBreakerRegistry circuitBreakerRegistry() { + circuitBreakerRegistry = CircuitBreakerRegistry.ofDefaults(); + return circuitBreakerRegistry; + } + + @Bean + public CircuitBreakerAspect circuitBreakerAspect(CircuitBreakerRegistry circuitBreakerRegistry, + @Autowired(required = false) List circuitBreakerAspectExtList) { + circuitBreakerAspect = new CircuitBreakerAspect(circuitBreakerConfigurationProperties(), circuitBreakerRegistry, circuitBreakerAspectExtList); + return circuitBreakerAspect; + } + + @Bean + public EventConsumerRegistry eventConsumerRegistry() { + circuitEventConsumerBreakerRegistry = new DefaultEventConsumerRegistry<>(); + return circuitEventConsumerBreakerRegistry; + } + + @Bean + public CircuitBreakerConfigurationProperties circuitBreakerConfigurationProperties() { + circuitBreakerConfigurationProperties = new CircuitBreakerConfigurationPropertiesTest(); + return circuitBreakerConfigurationProperties; + } + + private class CircuitBreakerConfigurationPropertiesTest extends CircuitBreakerConfigurationProperties { + + CircuitBreakerConfigurationPropertiesTest() { + BackendProperties backendProperties = new BackendProperties(); + backendProperties.setBaseConfig("sharedConfig"); + backendProperties.setFailureRateThreshold(3); + getConfigs().put("sharedBackend", backendProperties); + } + + } + } + + +} \ No newline at end of file diff --git a/resilience4j-spring/src/test/java/io/github/resilience4j/circuitbreaker/configure/CircuitBreakerConfigurationTest.java b/resilience4j-spring/src/test/java/io/github/resilience4j/circuitbreaker/configure/CircuitBreakerConfigurationTest.java new file mode 100644 index 0000000000..18d39b3790 --- /dev/null +++ b/resilience4j-spring/src/test/java/io/github/resilience4j/circuitbreaker/configure/CircuitBreakerConfigurationTest.java @@ -0,0 +1,52 @@ +package io.github.resilience4j.circuitbreaker.configure; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.when; + +import java.util.Collections; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; + +import io.github.resilience4j.circuitbreaker.CircuitBreakerConfig; +import io.github.resilience4j.circuitbreaker.CircuitBreakerRegistry; +import io.github.resilience4j.consumer.DefaultEventConsumerRegistry; + +/** + * test custom init of circuit breaker registry + */ +@RunWith(MockitoJUnitRunner.class) +public class CircuitBreakerConfigurationTest { + + @Mock + private CircuitBreakerConfigurationProperties circuitBreakerConfigurationProperties; + + + @Test + public void testCircuitBreakerRegistryConfig() { + CircuitBreakerConfigurationProperties.BackendProperties backendProperties = new CircuitBreakerConfigurationProperties.BackendProperties(); + backendProperties.setFailureRateThreshold(3); + when(circuitBreakerConfigurationProperties.getBackends()).thenReturn(Collections.singletonMap("testBackend", backendProperties)); + when(circuitBreakerConfigurationProperties.createCircuitBreakerConfig(anyString())).thenReturn(CircuitBreakerConfig.ofDefaults()); + + CircuitBreakerConfiguration circuitBreakerConfiguration = new CircuitBreakerConfiguration(circuitBreakerConfigurationProperties); + CircuitBreakerRegistry circuitBreakerRegistry = circuitBreakerConfiguration.circuitBreakerRegistry(new DefaultEventConsumerRegistry<>()); + assertThat(circuitBreakerRegistry.getAllCircuitBreakers().size()).isEqualTo(1); + assertThat(circuitBreakerRegistry.circuitBreaker("testBackend")).isNotNull(); + + } + + @Test + public void testCircuitBreakerSharedConfig() { + CircuitBreakerConfigurationProperties properties = new CircuitBreakerConfigurationProperties(); + assertThat(properties.createCircuitBreakerConfig("backend")).isNotNull(); + assertThat(properties.createCircuitBreakerConfigFrom("sharedConfig")).isNotNull(); + assertThat(properties.getBackends().size()).isEqualTo(0); + assertThat(properties.getConfigs().size()).isEqualTo(1); + + } + +} \ No newline at end of file diff --git a/resilience4j-spring/src/test/java/io/github/resilience4j/circuitbreaker/configure/ReactorCircuitBreakerAspectExtTest.java b/resilience4j-spring/src/test/java/io/github/resilience4j/circuitbreaker/configure/ReactorCircuitBreakerAspectExtTest.java new file mode 100644 index 0000000000..ea8c5d1266 --- /dev/null +++ b/resilience4j-spring/src/test/java/io/github/resilience4j/circuitbreaker/configure/ReactorCircuitBreakerAspectExtTest.java @@ -0,0 +1,63 @@ +/* + * Copyright 2019 Mahmoud Romeh + * + * 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 + * + * http://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 io.github.resilience4j.circuitbreaker.configure; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.when; + +import org.aspectj.lang.ProceedingJoinPoint; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; + +import io.github.resilience4j.circuitbreaker.CircuitBreaker; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +/** + * aspect unit test + */ +@RunWith(MockitoJUnitRunner.class) +public class ReactorCircuitBreakerAspectExtTest { + + @Mock + ProceedingJoinPoint proceedingJoinPoint; + + @InjectMocks + ReactorCircuitBreakerAspectExt reactorCircuitBreakerAspectExt; + + + @Test + public void testCheckTypes() { + assertThat(reactorCircuitBreakerAspectExt.canHandleReturnType(Mono.class)).isTrue(); + assertThat(reactorCircuitBreakerAspectExt.canHandleReturnType(Flux.class)).isTrue(); + } + + @Test + public void testReactorTypes() throws Throwable { + CircuitBreaker circuitBreaker = CircuitBreaker.ofDefaults("test"); + + when(proceedingJoinPoint.proceed()).thenReturn(Mono.just("Test")); + assertThat(reactorCircuitBreakerAspectExt.handle(proceedingJoinPoint, circuitBreaker, "testMethod")).isNotNull(); + + when(proceedingJoinPoint.proceed()).thenReturn(Flux.just("Test")); + assertThat(reactorCircuitBreakerAspectExt.handle(proceedingJoinPoint, circuitBreaker, "testMethod")).isNotNull(); + } + + +} \ No newline at end of file diff --git a/resilience4j-spring/src/test/java/io/github/resilience4j/circuitbreaker/configure/RxJava2CircuitBreakerAspectExtTest.java b/resilience4j-spring/src/test/java/io/github/resilience4j/circuitbreaker/configure/RxJava2CircuitBreakerAspectExtTest.java new file mode 100644 index 0000000000..804a2c025c --- /dev/null +++ b/resilience4j-spring/src/test/java/io/github/resilience4j/circuitbreaker/configure/RxJava2CircuitBreakerAspectExtTest.java @@ -0,0 +1,60 @@ +/* + * Copyright 2019 Mahmoud Romeh + * + * 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 + * + * http://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 io.github.resilience4j.circuitbreaker.configure; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.when; + +import org.aspectj.lang.ProceedingJoinPoint; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; + +import io.github.resilience4j.circuitbreaker.CircuitBreaker; +import io.reactivex.Flowable; +import io.reactivex.Single; + +/** + * aspect unit test + */ +@RunWith(MockitoJUnitRunner.class) +public class RxJava2CircuitBreakerAspectExtTest { + @Mock + ProceedingJoinPoint proceedingJoinPoint; + + @InjectMocks + RxJava2CircuitBreakerAspectExt rxJava2CircuitBreakerAspectExt; + + + @Test + public void testCheckTypes() { + assertThat(rxJava2CircuitBreakerAspectExt.canHandleReturnType(Flowable.class)).isTrue(); + assertThat(rxJava2CircuitBreakerAspectExt.canHandleReturnType(Single.class)).isTrue(); + } + + @Test + public void testReactorTypes() throws Throwable { + CircuitBreaker circuitBreaker = CircuitBreaker.ofDefaults("test"); + + when(proceedingJoinPoint.proceed()).thenReturn(Single.just("Test")); + assertThat(rxJava2CircuitBreakerAspectExt.handle(proceedingJoinPoint, circuitBreaker, "testMethod")).isNotNull(); + + when(proceedingJoinPoint.proceed()).thenReturn(Flowable.just("Test")); + assertThat(rxJava2CircuitBreakerAspectExt.handle(proceedingJoinPoint, circuitBreaker, "testMethod")).isNotNull(); + } +} \ No newline at end of file diff --git a/resilience4j-spring/src/test/java/io/github/resilience4j/ratelimiter/configure/ReactorRateLimiterAspectExtTest.java b/resilience4j-spring/src/test/java/io/github/resilience4j/ratelimiter/configure/ReactorRateLimiterAspectExtTest.java new file mode 100644 index 0000000000..636cb98a2c --- /dev/null +++ b/resilience4j-spring/src/test/java/io/github/resilience4j/ratelimiter/configure/ReactorRateLimiterAspectExtTest.java @@ -0,0 +1,63 @@ +/* + * Copyright 2019 Mahmoud Romeh + * + * 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 + * + * http://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 io.github.resilience4j.ratelimiter.configure; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.when; + +import org.aspectj.lang.ProceedingJoinPoint; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; + +import io.github.resilience4j.ratelimiter.RateLimiter; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +/** + * aspect unit test + */ +@RunWith(MockitoJUnitRunner.class) +public class ReactorRateLimiterAspectExtTest { + + @Mock + ProceedingJoinPoint proceedingJoinPoint; + + @InjectMocks + ReactorRateLimiterAspectExt reactorRateLimiterAspectExt; + + + @Test + public void testCheckTypes() { + assertThat(reactorRateLimiterAspectExt.canHandleReturnType(Mono.class)).isTrue(); + assertThat(reactorRateLimiterAspectExt.canHandleReturnType(Flux.class)).isTrue(); + } + + @Test + public void testReactorTypes() throws Throwable { + RateLimiter rateLimiter = RateLimiter.ofDefaults("test"); + + when(proceedingJoinPoint.proceed()).thenReturn(Mono.just("Test")); + assertThat(reactorRateLimiterAspectExt.handle(proceedingJoinPoint, rateLimiter, "testMethod")).isNotNull(); + + when(proceedingJoinPoint.proceed()).thenReturn(Flux.just("Test")); + assertThat(reactorRateLimiterAspectExt.handle(proceedingJoinPoint, rateLimiter, "testMethod")).isNotNull(); + } + + +} \ No newline at end of file diff --git a/resilience4j-spring/src/test/java/io/github/resilience4j/ratelimiter/configure/RxJava2RateLimiterAspectExtTest.java b/resilience4j-spring/src/test/java/io/github/resilience4j/ratelimiter/configure/RxJava2RateLimiterAspectExtTest.java new file mode 100644 index 0000000000..373ca3adbe --- /dev/null +++ b/resilience4j-spring/src/test/java/io/github/resilience4j/ratelimiter/configure/RxJava2RateLimiterAspectExtTest.java @@ -0,0 +1,74 @@ +/* + * Copyright 2019 Mahmoud Romeh + * + * 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 + * + * http://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 io.github.resilience4j.ratelimiter.configure; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.when; + +import org.aspectj.lang.ProceedingJoinPoint; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; + +import io.github.resilience4j.ratelimiter.RateLimiter; +import io.reactivex.Completable; +import io.reactivex.Flowable; +import io.reactivex.Maybe; +import io.reactivex.Observable; +import io.reactivex.Single; + +/** + * aspect unit test + */ +@RunWith(MockitoJUnitRunner.class) +public class RxJava2RateLimiterAspectExtTest { + @Mock + ProceedingJoinPoint proceedingJoinPoint; + + @InjectMocks + RxJava2RateLimiterAspectExt rxJava2RateLimiterAspectExt; + + + @Test + public void testCheckTypes() { + assertThat(rxJava2RateLimiterAspectExt.canHandleReturnType(Flowable.class)).isTrue(); + assertThat(rxJava2RateLimiterAspectExt.canHandleReturnType(Single.class)).isTrue(); + } + + @Test + public void testReactorTypes() throws Throwable { + RateLimiter rateLimiter = RateLimiter.ofDefaults("test"); + + when(proceedingJoinPoint.proceed()).thenReturn(Single.just("Test")); + assertThat(rxJava2RateLimiterAspectExt.handle(proceedingJoinPoint, rateLimiter, "testMethod")).isNotNull(); + + when(proceedingJoinPoint.proceed()).thenReturn(Flowable.just("Test")); + assertThat(rxJava2RateLimiterAspectExt.handle(proceedingJoinPoint, rateLimiter, "testMethod")).isNotNull(); + + when(proceedingJoinPoint.proceed()).thenReturn(Completable.complete()); + assertThat(rxJava2RateLimiterAspectExt.handle(proceedingJoinPoint, rateLimiter, "testMethod")).isNotNull(); + + when(proceedingJoinPoint.proceed()).thenReturn(Maybe.just("Test")); + assertThat(rxJava2RateLimiterAspectExt.handle(proceedingJoinPoint, rateLimiter, "testMethod")).isNotNull(); + + when(proceedingJoinPoint.proceed()).thenReturn(Observable.just("Test")); + assertThat(rxJava2RateLimiterAspectExt.handle(proceedingJoinPoint, rateLimiter, "testMethod")).isNotNull(); + + + } +} \ No newline at end of file diff --git a/resilience4j-spring/src/test/java/io/github/resilience4j/retry/configure/ReactorRetryAspectExtTest.java b/resilience4j-spring/src/test/java/io/github/resilience4j/retry/configure/ReactorRetryAspectExtTest.java new file mode 100644 index 0000000000..cebba92e34 --- /dev/null +++ b/resilience4j-spring/src/test/java/io/github/resilience4j/retry/configure/ReactorRetryAspectExtTest.java @@ -0,0 +1,63 @@ +/* + * Copyright 2019 Mahmoud Romeh + * + * 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 + * + * http://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 io.github.resilience4j.retry.configure; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.when; + +import org.aspectj.lang.ProceedingJoinPoint; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; + +import io.github.resilience4j.retry.Retry; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +/** + * aspect unit test + */ +@RunWith(MockitoJUnitRunner.class) +public class ReactorRetryAspectExtTest { + + @Mock + ProceedingJoinPoint proceedingJoinPoint; + + @InjectMocks + ReactorRetryAspectExt reactorRetryAspectExt; + + + @Test + public void testCheckTypes() { + assertThat(reactorRetryAspectExt.canHandleReturnType(Mono.class)).isTrue(); + assertThat(reactorRetryAspectExt.canHandleReturnType(Flux.class)).isTrue(); + } + + @Test + public void testReactorTypes() throws Throwable { + Retry retry = Retry.ofDefaults("test"); + + when(proceedingJoinPoint.proceed()).thenReturn(Mono.just("Test")); + assertThat(reactorRetryAspectExt.handle(proceedingJoinPoint, retry, "testMethod")).isNotNull(); + + when(proceedingJoinPoint.proceed()).thenReturn(Flux.just("Test")); + assertThat(reactorRetryAspectExt.handle(proceedingJoinPoint, retry, "testMethod")).isNotNull(); + } + + +} \ No newline at end of file diff --git a/resilience4j-spring/src/test/java/io/github/resilience4j/retry/configure/RxJava2RetryAspectExtTest.java b/resilience4j-spring/src/test/java/io/github/resilience4j/retry/configure/RxJava2RetryAspectExtTest.java new file mode 100644 index 0000000000..908cd4eb36 --- /dev/null +++ b/resilience4j-spring/src/test/java/io/github/resilience4j/retry/configure/RxJava2RetryAspectExtTest.java @@ -0,0 +1,60 @@ +/* + * Copyright 2019 Mahmoud Romeh + * + * 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 + * + * http://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 io.github.resilience4j.retry.configure; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.when; + +import org.aspectj.lang.ProceedingJoinPoint; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; + +import io.github.resilience4j.retry.Retry; +import io.reactivex.Flowable; +import io.reactivex.Single; + +/** + * aspect unit test + */ +@RunWith(MockitoJUnitRunner.class) +public class RxJava2RetryAspectExtTest { + @Mock + ProceedingJoinPoint proceedingJoinPoint; + + @InjectMocks + RxJava2RetryAspectExt rxJava2RetryAspectExt; + + + @Test + public void testCheckTypes() { + assertThat(rxJava2RetryAspectExt.canHandleReturnType(Flowable.class)).isTrue(); + assertThat(rxJava2RetryAspectExt.canHandleReturnType(Single.class)).isTrue(); + } + + @Test + public void testReactorTypes() throws Throwable { + Retry retry = Retry.ofDefaults("test"); + + when(proceedingJoinPoint.proceed()).thenReturn(Single.just("Test")); + assertThat(rxJava2RetryAspectExt.handle(proceedingJoinPoint, retry, "testMethod")).isNotNull(); + + when(proceedingJoinPoint.proceed()).thenReturn(Flowable.just("Test")); + assertThat(rxJava2RetryAspectExt.handle(proceedingJoinPoint, retry, "testMethod")).isNotNull(); + } +} \ No newline at end of file diff --git a/resilience4j-spring/src/test/java/io/github/resilience4j/utils/CommonUtilsTest.java b/resilience4j-spring/src/test/java/io/github/resilience4j/utils/CommonUtilsTest.java new file mode 100644 index 0000000000..ba545aa40d --- /dev/null +++ b/resilience4j-spring/src/test/java/io/github/resilience4j/utils/CommonUtilsTest.java @@ -0,0 +1,61 @@ +package io.github.resilience4j.utils; + + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; + +import org.junit.Test; + +/** + * unit tes for the util class + */ +public class CommonUtilsTest { + + + @Test + public void testMergeProperties() { + Pojo pojo1 = new Pojo("Test1", "Test2"); + Pojo pojo2 = new Pojo("Test3", "Test4"); + final Pojo pojo = CommonUtils.mergeProperties(pojo1, pojo2); + assertThat(pojo.property1).isEqualTo("Test1"); + assertThat(pojo.property2).isEqualTo("Test2"); + } + + @Test + public void testGetNullProperties() { + Pojo pojo1 = new Pojo("Test1", null); + Pojo pojo2 = new Pojo("Test3", "Test4"); + final Pojo pojo = CommonUtils.mergeProperties(pojo1, pojo2); + assertThat(pojo.property1).isEqualTo("Test1"); + assertThat(pojo.property2).isEqualTo("Test4"); + } + + + class Pojo { + + private String property1; + private String property2; + + Pojo(String property1, String property2) { + this.property1 = property1; + this.property2 = property2; + } + + public void setProperty1(String property1) { + this.property1 = property1; + } + + public void setProperty2(String property2) { + this.property2 = property2; + } + + public String getProperty1() { + return property1; + } + + public String getProperty2() { + return property2; + } + + } + +} \ No newline at end of file diff --git a/settings.gradle b/settings.gradle index 57606a004d..6e6f1120d6 100644 --- a/settings.gradle +++ b/settings.gradle @@ -24,4 +24,6 @@ include 'resilience4j-timelimiter' include 'resilience4j-rxjava2' include 'resilience4j-reactor' include 'resilience4j-micrometer' -include 'resilience4j-bom' \ No newline at end of file +include 'resilience4j-bom' +include 'resilience4j-spring-boot-common' +