diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/annotation/EndpointDiscoverer.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/annotation/EndpointDiscoverer.java index 5bfde9aa9a..596de3d2a8 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/annotation/EndpointDiscoverer.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/annotation/EndpointDiscoverer.java @@ -30,6 +30,7 @@ import java.util.function.Function; import java.util.function.Supplier; import java.util.stream.Collectors; +import org.springframework.aop.scope.ScopedProxyUtils; import org.springframework.beans.BeanUtils; import org.springframework.beans.factory.BeanFactoryUtils; import org.springframework.boot.actuate.endpoint.EndpointFilter; @@ -131,12 +132,15 @@ public abstract class EndpointDiscoverer, O exten String[] beanNames = BeanFactoryUtils.beanNamesForAnnotationIncludingAncestors( this.applicationContext, Endpoint.class); for (String beanName : beanNames) { - EndpointBean endpointBean = createEndpointBean(beanName); - EndpointBean previous = byId.putIfAbsent(endpointBean.getId(), endpointBean); - Assert.state(previous == null, - () -> "Found two endpoints with the id '" + endpointBean.getId() - + "': '" + endpointBean.getBeanName() + "' and '" - + previous.getBeanName() + "'"); + if (!ScopedProxyUtils.isScopedTarget(beanName)) { + EndpointBean endpointBean = createEndpointBean(beanName); + EndpointBean previous = byId.putIfAbsent(endpointBean.getId(), + endpointBean); + Assert.state(previous == null, + () -> "Found two endpoints with the id '" + endpointBean.getId() + + "': '" + endpointBean.getBeanName() + "' and '" + + previous.getBeanName() + "'"); + } } return byId.values(); } diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/annotation/EndpointDiscovererTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/annotation/EndpointDiscovererTests.java index a465f180bc..0da86eca0c 100644 --- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/annotation/EndpointDiscovererTests.java +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/annotation/EndpointDiscovererTests.java @@ -148,6 +148,18 @@ public class EndpointDiscovererTests { "Found two endpoints with the id 'test': ")); } + @Test + public void getEndpointsWhenEndpointsArePrefixedWithScopedTargetShouldRegisterOnlyOneEndpoint() { + load(ScopedTargetEndpointConfiguration.class, (context) -> { + TestEndpoint expectedEndpoint = context + .getBean(ScopedTargetEndpointConfiguration.class).testEndpoint(); + Collection endpoints = new TestEndpointDiscoverer( + context).getEndpoints(); + assertThat(endpoints).flatExtracting(TestExposableEndpoint::getEndpointBean) + .containsOnly(expectedEndpoint); + }); + } + @Test public void getEndpointsWhenTtlSetToZeroShouldNotCacheInvokeCalls() { load(TestEndpointConfiguration.class, (context) -> { @@ -393,6 +405,21 @@ public class EndpointDiscovererTests { } + @Configuration + static class ScopedTargetEndpointConfiguration { + + @Bean + public TestEndpoint testEndpoint() { + return new TestEndpoint(); + } + + @Bean(name = "scopedTarget.testEndpoint") + public TestEndpoint scopedTargetTestEndpoint() { + return new TestEndpoint(); + } + + } + @Import({ TestEndpoint.class, SpecializedTestEndpoint.class, SpecializedExtension.class }) static class SpecializedEndpointsConfiguration {