diff --git a/spring-cloud-commons/src/main/java/org/springframework/cloud/client/circuitbreaker/httpservice/CircuitBreakerRequestValueProcessor.java b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/circuitbreaker/httpservice/CircuitBreakerRequestValueProcessor.java index dd0039da2..d93d226f6 100644 --- a/spring-cloud-commons/src/main/java/org/springframework/cloud/client/circuitbreaker/httpservice/CircuitBreakerRequestValueProcessor.java +++ b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/circuitbreaker/httpservice/CircuitBreakerRequestValueProcessor.java @@ -72,7 +72,7 @@ public void process(Method method, MethodParameter[] parameters, @Nullable Objec builder.addAttribute(PARAMETER_TYPES_ATTRIBUTE_NAME, method.getParameterTypes()); builder.addAttribute(ARGUMENTS_ATTRIBUTE_NAME, arguments); builder.addAttribute(RETURN_TYPE_ATTRIBUTE_NAME, method.getReturnType()); - builder.addAttribute(DECLARING_CLASS_ATTRIBUTE_NAME, method.getDeclaringClass().getCanonicalName()); + builder.addAttribute(DECLARING_CLASS_ATTRIBUTE_NAME, method.getDeclaringClass().getName()); } } diff --git a/spring-cloud-commons/src/test/java/org/springframework/cloud/client/circuitbreaker/httpservice/CircuitBreakerAdapterDecoratorTests.java b/spring-cloud-commons/src/test/java/org/springframework/cloud/client/circuitbreaker/httpservice/CircuitBreakerAdapterDecoratorTests.java index 4a92b3c11..cdb82a510 100644 --- a/spring-cloud-commons/src/test/java/org/springframework/cloud/client/circuitbreaker/httpservice/CircuitBreakerAdapterDecoratorTests.java +++ b/spring-cloud-commons/src/test/java/org/springframework/cloud/client/circuitbreaker/httpservice/CircuitBreakerAdapterDecoratorTests.java @@ -16,6 +16,7 @@ package org.springframework.cloud.client.circuitbreaker.httpservice; +import java.lang.reflect.Method; import java.util.HashMap; import java.util.Map; import java.util.function.Function; @@ -24,6 +25,8 @@ import org.springframework.cloud.client.circuitbreaker.CircuitBreaker; import org.springframework.cloud.client.circuitbreaker.NoFallbackAvailableException; +import org.springframework.core.MethodParameter; +import org.springframework.http.HttpMethod; import org.springframework.web.service.invoker.HttpExchangeAdapter; import org.springframework.web.service.invoker.HttpRequestValues; @@ -128,4 +131,34 @@ void shouldThrowExceptionWhenNoFallbackAvailable() { .isThrownBy(() -> fallbackHandler.apply(new RuntimeException("test"))); } + // Fallback classes are registered per service under Class#getName() (binary name), + // so the declaring-class attribute written by the processor must use the same form. + // For a nested @HttpExchange interface getName() ("Outer$Inner") differs from + // getCanonicalName() ("Outer.Inner"), which used to leave the per-class fallback + // unresolved and fall through to NoFallbackAvailableException. + @Test + void shouldResolvePerClassFallbackForNestedServiceInterface() throws NoSuchMethodException { + Method method = NestedTestService.class.getMethod("test", String.class, Integer.class); + HttpRequestValues.Builder builder = HttpRequestValues.builder(); + new CircuitBreakerRequestValueProcessor().process(method, new MethodParameter[0], + new Object[] { "testDescription", 5 }, builder); + builder.setHttpMethod(HttpMethod.GET); + builder.setUriTemplate("/test"); + Map attributes = builder.build().getAttributes(); + CircuitBreakerAdapterDecorator nestedDecorator = new CircuitBreakerAdapterDecorator(adapter, circuitBreaker, + Map.of(NestedTestService.class.getName(), Fallbacks.class)); + when(httpRequestValues.getAttributes()).thenReturn(attributes); + Function fallbackHandler = nestedDecorator.createFallbackHandler(httpRequestValues); + + Object fallback = fallbackHandler.apply(new RuntimeException("test")); + + assertThat(fallback).isEqualTo("testDescription: 5"); + } + + interface NestedTestService { + + String test(String description, Integer value); + + } + }