From 11edff7576085964191f4fea7593dd47eebeec76 Mon Sep 17 00:00:00 2001 From: Stephane Nicoll Date: Fri, 1 Sep 2017 16:45:52 +0200 Subject: [PATCH] Discover endpoints in parent context Closes gh-10144 --- .../AnnotationEndpointDiscoverer.java | 34 +++++++++++++-- .../AnnotationEndpointDiscovererTests.java | 43 +++++++++++++++---- 2 files changed, 64 insertions(+), 13 deletions(-) diff --git a/spring-boot/src/main/java/org/springframework/boot/endpoint/AnnotationEndpointDiscoverer.java b/spring-boot/src/main/java/org/springframework/boot/endpoint/AnnotationEndpointDiscoverer.java index 4aa125ab976..8d68ee1e74f 100644 --- a/spring-boot/src/main/java/org/springframework/boot/endpoint/AnnotationEndpointDiscoverer.java +++ b/spring-boot/src/main/java/org/springframework/boot/endpoint/AnnotationEndpointDiscoverer.java @@ -19,6 +19,7 @@ package org.springframework.boot.endpoint; import java.lang.annotation.Annotation; import java.lang.reflect.Method; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashMap; @@ -28,6 +29,8 @@ import java.util.Map; import java.util.function.Consumer; import java.util.function.Function; +import org.springframework.beans.factory.HierarchicalBeanFactory; +import org.springframework.beans.factory.ListableBeanFactory; import org.springframework.context.ApplicationContext; import org.springframework.core.MethodIntrospector; import org.springframework.core.annotation.AnnotatedElementUtils; @@ -35,6 +38,7 @@ import org.springframework.core.annotation.AnnotationAttributes; import org.springframework.util.Assert; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.ObjectUtils; +import org.springframework.util.StringUtils; /** * A base {@link EndpointDiscoverer} implementation that discovers {@link Endpoint} beans @@ -88,8 +92,8 @@ public abstract class AnnotationEndpointDiscoverer } private Map, EndpointInfo> discoverEndpoints(EndpointExposure exposure) { - String[] beanNames = this.applicationContext - .getBeanNamesForAnnotation(Endpoint.class); + String[] beanNames = beanNamesForAnnotationIncludingAncestors( + this.applicationContext, Endpoint.class); Map, EndpointInfo> endpoints = new LinkedHashMap<>(); Map> endpointsById = new LinkedHashMap<>(); for (String beanName : beanNames) { @@ -121,8 +125,8 @@ public abstract class AnnotationEndpointDiscoverer if (extensionType == null) { return Collections.emptyMap(); } - String[] beanNames = this.applicationContext - .getBeanNamesForAnnotation(extensionType); + String[] beanNames = beanNamesForAnnotationIncludingAncestors( + this.applicationContext, extensionType); Map, EndpointExtensionInfo> extensions = new HashMap<>(); for (String beanName : beanNames) { Class beanType = this.applicationContext.getType(beanName); @@ -150,6 +154,28 @@ public abstract class AnnotationEndpointDiscoverer } + private static String[] beanNamesForAnnotationIncludingAncestors( + ListableBeanFactory lbf, Class annotationType) { + Assert.notNull(lbf, "ListableBeanFactory must not be null"); + String[] result = lbf.getBeanNamesForAnnotation(annotationType); + if (lbf instanceof HierarchicalBeanFactory) { + HierarchicalBeanFactory hbf = (HierarchicalBeanFactory) lbf; + if (hbf.getParentBeanFactory() instanceof ListableBeanFactory) { + String[] parentResult = beanNamesForAnnotationIncludingAncestors( + (ListableBeanFactory) hbf.getParentBeanFactory(), annotationType); + List resultList = new ArrayList<>(); + resultList.addAll(Arrays.asList(result)); + for (String beanName : parentResult) { + if (!resultList.contains(beanName) && !hbf.containsLocalBean(beanName)) { + resultList.add(beanName); + } + } + result = StringUtils.toStringArray(resultList); + } + } + return result; + } + private EndpointInfo getEndpointInfo(Map, EndpointInfo> endpoints, Class beanType, Class endpointClass) { EndpointInfo endpoint = endpoints.get(endpointClass); diff --git a/spring-boot/src/test/java/org/springframework/boot/endpoint/AnnotationEndpointDiscovererTests.java b/spring-boot/src/test/java/org/springframework/boot/endpoint/AnnotationEndpointDiscovererTests.java index 2199dea9ee0..8e90a05dd36 100644 --- a/spring-boot/src/test/java/org/springframework/boot/endpoint/AnnotationEndpointDiscovererTests.java +++ b/spring-boot/src/test/java/org/springframework/boot/endpoint/AnnotationEndpointDiscovererTests.java @@ -58,7 +58,18 @@ public class AnnotationEndpointDiscovererTests { @Test public void endpointIsDiscovered() { - load(TestEndpointConfiguration.class, (context) -> { + load(TestEndpointConfiguration.class, hasTestEndpoint()); + } + + @Test + public void endpointIsInParentContextIsDiscovered() { + AnnotationConfigApplicationContext parent = + new AnnotationConfigApplicationContext(TestEndpointConfiguration.class); + loadWithParent(parent, EmptyConfiguration.class, hasTestEndpoint()); + } + + private Consumer hasTestEndpoint() { + return (context) -> { Map> endpoints = mapEndpoints( new TestAnnotationEndpointDiscoverer(context).discoverEndpoints()); assertThat(endpoints).containsOnlyKeys("test"); @@ -73,7 +84,7 @@ public class AnnotationEndpointDiscovererTests { String.class), ReflectionUtils.findMethod(TestEndpoint.class, "deleteOne", String.class)); - }); + }; } @Test @@ -114,7 +125,7 @@ public class AnnotationEndpointDiscovererTests { Map> endpoints = mapEndpoints( new TestAnnotationEndpointDiscoverer(context, (endpointId) -> new CachingConfiguration(0)) - .discoverEndpoints()); + .discoverEndpoints()); assertThat(endpoints).containsOnlyKeys("test"); Map operations = mapOperations( endpoints.get("test")); @@ -128,7 +139,7 @@ public class AnnotationEndpointDiscovererTests { public void endpointMainReadOperationIsNotCachedWithNonMatchingId() { Function cachingConfigurationFactory = ( endpointId) -> (endpointId.equals("foo") ? new CachingConfiguration(500) - : new CachingConfiguration(0)); + : new CachingConfiguration(0)); load(TestEndpointConfiguration.class, (context) -> { Map> endpoints = mapEndpoints( new TestAnnotationEndpointDiscoverer(context, @@ -146,7 +157,7 @@ public class AnnotationEndpointDiscovererTests { public void endpointMainReadOperationIsCachedWithMatchingId() { Function cachingConfigurationFactory = ( endpointId) -> (endpointId.equals("test") ? new CachingConfiguration(500) - : new CachingConfiguration(0)); + : new CachingConfiguration(0)); load(TestEndpointConfiguration.class, (context) -> { Map> endpoints = mapEndpoints( new TestAnnotationEndpointDiscoverer(context, @@ -163,10 +174,10 @@ public class AnnotationEndpointDiscovererTests { .isEqualTo(500); assertThat(operations.get(ReflectionUtils.findMethod(TestEndpoint.class, "getOne", String.class)).getInvoker()) - .isNotInstanceOf(CachingOperationInvoker.class); + .isNotInstanceOf(CachingOperationInvoker.class); assertThat(operations.get(ReflectionUtils.findMethod(TestEndpoint.class, "update", String.class, String.class)).getInvoker()) - .isNotInstanceOf(CachingOperationInvoker.class); + .isNotInstanceOf(CachingOperationInvoker.class); }); } @@ -201,8 +212,22 @@ public class AnnotationEndpointDiscovererTests { private void load(Class configuration, Consumer consumer) { - AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext( - configuration); + doLoad(null, configuration, consumer); + } + + private void loadWithParent(ApplicationContext parent, Class configuration, + Consumer consumer) { + doLoad(parent, configuration, consumer); + } + + private void doLoad(ApplicationContext parent, Class configuration, + Consumer consumer) { + AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); + if (parent != null) { + context.setParent(parent); + } + context.register(configuration); + context.refresh(); try { consumer.accept(context); }