-
Notifications
You must be signed in to change notification settings - Fork 717
Add interface clients support for circuit breaker #1532
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
OlgaMaciaszek
merged 33 commits into
main
from
add-interface-clients-support-for-circuit-breaker
Jul 24, 2025
Merged
Changes from 31 commits
Commits
Show all changes
33 commits
Select commit
Hold shift + click to select a range
053deb5
Init draft.
OlgaMaciaszek 8606c70
Merge remote-tracking branch 'origin/main' into add-interface-clients…
OlgaMaciaszek 6dfd121
Handle missing fallbacks. Fix configuration.
OlgaMaciaszek 5c1e91f
Create proxy to invoke fallback method.
OlgaMaciaszek 12352c3
Handle proxy target instantiation exceptions.
OlgaMaciaszek 1d9b8a6
Add decorator.
OlgaMaciaszek 034aae7
Switch to using decorator.
OlgaMaciaszek 397ff05
Refactor CircuitBreakerRestClientAdapterDecorator.
OlgaMaciaszek de9b5ce
Refactor CircuitBreakerRestClientAdapterDecorator and CircuitBreakerR…
OlgaMaciaszek 4c3dac6
Handle empty or null fallbackClassName. Add tests.
OlgaMaciaszek a77fa3e
Repackage. Add decorator tests.
OlgaMaciaszek 19c065b
Merge remote-tracking branch 'origin/main' into add-interface-clients…
OlgaMaciaszek d449a70
Add more tests. Refactor.
OlgaMaciaszek fc81ce7
Draft reactive implementation.
OlgaMaciaszek 34d34fb
Add tests for reactive implementation.
OlgaMaciaszek 200dc59
Fix checkstyle. Adjust to changes in FW. Match fallback method by ret…
OlgaMaciaszek bd3dbaa
Fix configuration.
OlgaMaciaszek 6dc3bb2
Fix reactive implementation and add more tests.
OlgaMaciaszek 3223d8c
Refactor.
OlgaMaciaszek f7d0645
Add javadocs.
OlgaMaciaszek 6a290b6
Add property flag to autoconfiguration.
OlgaMaciaszek d8d6636
Reword javadoc.
OlgaMaciaszek 15dfe57
Add docs.
OlgaMaciaszek ac02fef
Allow using multiple fallback classes.
OlgaMaciaszek 1fe805b
Fix the logic for resolving fallback from multiple fallback classes.
OlgaMaciaszek 9dd9b1b
Add javadoc and regenerate configprops.
OlgaMaciaszek dcc8d8a
Allow defining default fallback for all groups. Add more tests.
OlgaMaciaszek a736cdd
Use String map keys. Handle default settings for all groups. Refactor…
OlgaMaciaszek ca3ab9f
Init draft.
OlgaMaciaszek f0e2a29
Switch to using only annotations.
OlgaMaciaszek 3ff7bf0
Rename annotation to `@HttpServiceFallback`. Update documentation.
OlgaMaciaszek 383c130
Rename `@HttpServiceFallback` attributes.
OlgaMaciaszek 6c0c3c5
Update docs.
OlgaMaciaszek File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
148 changes: 148 additions & 0 deletions
148
...ringframework/cloud/client/circuitbreaker/httpservice/CircuitBreakerAdapterDecorator.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,148 @@ | ||
| /* | ||
| * Copyright 2013-2025 the original author or authors. | ||
| * | ||
| * Licensed under the Apache License, Version 2.0 (the "License"); | ||
| * you may not use this file except in compliance with the License. | ||
| * You may obtain a copy of the License at | ||
| * | ||
| * https://www.apache.org/licenses/LICENSE-2.0 | ||
| * | ||
| * Unless required by applicable law or agreed to in writing, software | ||
| * distributed under the License is distributed on an "AS IS" BASIS, | ||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| * See the License for the specific language governing permissions and | ||
| * limitations under the License. | ||
| */ | ||
|
|
||
| package org.springframework.cloud.client.circuitbreaker.httpservice; | ||
|
|
||
| import java.util.Map; | ||
| import java.util.function.Function; | ||
|
|
||
| import org.apache.commons.logging.Log; | ||
| import org.apache.commons.logging.LogFactory; | ||
| import org.jspecify.annotations.Nullable; | ||
|
|
||
| import org.springframework.cloud.client.circuitbreaker.CircuitBreaker; | ||
| import org.springframework.core.ParameterizedTypeReference; | ||
| import org.springframework.http.HttpHeaders; | ||
| import org.springframework.http.ResponseEntity; | ||
| import org.springframework.web.service.invoker.HttpExchangeAdapter; | ||
| import org.springframework.web.service.invoker.HttpExchangeAdapterDecorator; | ||
| import org.springframework.web.service.invoker.HttpRequestValues; | ||
|
|
||
| import static org.springframework.cloud.client.circuitbreaker.httpservice.CircuitBreakerConfigurerUtils.createProxies; | ||
| import static org.springframework.cloud.client.circuitbreaker.httpservice.CircuitBreakerConfigurerUtils.getFallback; | ||
|
|
||
| /** | ||
| * Blocking implementation of {@link HttpExchangeAdapterDecorator} that wraps | ||
| * {@code @HttpExchange} | ||
| * <p> | ||
| * In the event of a CircuitBreaker fallback, this class uses the user-provided fallback | ||
| * class to create a proxy. The fallback method is selected by matching either: | ||
| * <ul> | ||
| * <li>A method with the same name and argument types as the original method, or</li> | ||
| * <li>A method with the same name and the original arguments preceded by a | ||
| * {@link Throwable}, allowing the user to access the cause of failure within the | ||
| * fallback.</li> | ||
| * </ul> | ||
| * Once a matching method is found, it is invoked to provide the fallback behavior. Both | ||
| * the fallback class and the fallback methods must be public. | ||
| * </p> | ||
| * | ||
| * @author Olga Maciaszek-Sharma | ||
| * @since 5.0.0 | ||
| * @see HttpServiceFallback | ||
| */ | ||
| public class CircuitBreakerAdapterDecorator extends HttpExchangeAdapterDecorator { | ||
|
|
||
| private static final Log LOG = LogFactory.getLog(CircuitBreakerAdapterDecorator.class); | ||
|
|
||
| private final CircuitBreaker circuitBreaker; | ||
|
|
||
| private final Map<String, Class<?>> fallbackClasses; | ||
|
|
||
| private volatile Map<String, Object> fallbackProxies; | ||
|
|
||
| public CircuitBreakerAdapterDecorator(HttpExchangeAdapter delegate, CircuitBreaker circuitBreaker, | ||
| Map<String, Class<?>> fallbackClasses) { | ||
| super(delegate); | ||
| this.circuitBreaker = circuitBreaker; | ||
| this.fallbackClasses = fallbackClasses; | ||
| } | ||
|
|
||
| @Override | ||
| public void exchange(HttpRequestValues requestValues) { | ||
| circuitBreaker.run(() -> { | ||
| super.exchange(requestValues); | ||
| return null; | ||
| }, createFallbackHandler(requestValues)); | ||
| } | ||
|
|
||
| @Override | ||
| public HttpHeaders exchangeForHeaders(HttpRequestValues values) { | ||
| Object result = circuitBreaker.run(() -> super.exchangeForHeaders(values), createFallbackHandler(values)); | ||
| return castIfPossible(result); | ||
| } | ||
|
|
||
| @Override | ||
| public <T> @Nullable T exchangeForBody(HttpRequestValues values, ParameterizedTypeReference<T> bodyType) { | ||
| Object result = circuitBreaker.run(() -> super.exchangeForBody(values, bodyType), | ||
| createFallbackHandler(values)); | ||
| return castIfPossible(result); | ||
| } | ||
|
|
||
| @Override | ||
| public ResponseEntity<Void> exchangeForBodilessEntity(HttpRequestValues values) { | ||
| Object result = circuitBreaker.run(() -> super.exchangeForBodilessEntity(values), | ||
| createFallbackHandler(values)); | ||
| return castIfPossible(result); | ||
| } | ||
|
|
||
| @Override | ||
| public <T> ResponseEntity<T> exchangeForEntity(HttpRequestValues values, ParameterizedTypeReference<T> bodyType) { | ||
| Object result = circuitBreaker.run(() -> super.exchangeForEntity(values, bodyType), | ||
| createFallbackHandler(values)); | ||
| return castIfPossible(result); | ||
| } | ||
|
|
||
| // Visible for tests | ||
| CircuitBreaker getCircuitBreaker() { | ||
| return circuitBreaker; | ||
| } | ||
|
|
||
| // Visible for tests | ||
| Map<String, Class<?>> getFallbackClasses() { | ||
| return fallbackClasses; | ||
| } | ||
|
|
||
| @SuppressWarnings("unchecked") | ||
| private <T> T castIfPossible(Object result) { | ||
| try { | ||
| return (T) result; | ||
| } | ||
| catch (ClassCastException exception) { | ||
| if (LOG.isErrorEnabled()) { | ||
| LOG.error("Failed to cast object of type " + result.getClass() + " to expected type."); | ||
| } | ||
| throw exception; | ||
| } | ||
| } | ||
|
|
||
| // Visible for tests | ||
| Function<Throwable, Object> createFallbackHandler(HttpRequestValues requestValues) { | ||
| return throwable -> getFallback(requestValues, throwable, getFallbackProxies(), fallbackClasses); | ||
| } | ||
|
|
||
| private Map<String, Object> getFallbackProxies() { | ||
| if (fallbackProxies == null) { | ||
| synchronized (this) { | ||
| if (fallbackProxies == null) { | ||
| fallbackProxies = createProxies(fallbackClasses); | ||
| } | ||
| } | ||
| } | ||
| return fallbackProxies; | ||
| } | ||
|
|
||
| } |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.