Discover endpoints in parent context

Closes gh-10144
pull/10152/head
Stephane Nicoll 7 years ago
parent a274c78fa0
commit 11edff7576

@ -19,6 +19,7 @@ package org.springframework.boot.endpoint;
import java.lang.annotation.Annotation; import java.lang.annotation.Annotation;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
@ -28,6 +29,8 @@ import java.util.Map;
import java.util.function.Consumer; import java.util.function.Consumer;
import java.util.function.Function; 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.context.ApplicationContext;
import org.springframework.core.MethodIntrospector; import org.springframework.core.MethodIntrospector;
import org.springframework.core.annotation.AnnotatedElementUtils; 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.Assert;
import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.ObjectUtils; import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
/** /**
* A base {@link EndpointDiscoverer} implementation that discovers {@link Endpoint} beans * A base {@link EndpointDiscoverer} implementation that discovers {@link Endpoint} beans
@ -88,8 +92,8 @@ public abstract class AnnotationEndpointDiscoverer<T extends Operation, K>
} }
private Map<Class<?>, EndpointInfo<T>> discoverEndpoints(EndpointExposure exposure) { private Map<Class<?>, EndpointInfo<T>> discoverEndpoints(EndpointExposure exposure) {
String[] beanNames = this.applicationContext String[] beanNames = beanNamesForAnnotationIncludingAncestors(
.getBeanNamesForAnnotation(Endpoint.class); this.applicationContext, Endpoint.class);
Map<Class<?>, EndpointInfo<T>> endpoints = new LinkedHashMap<>(); Map<Class<?>, EndpointInfo<T>> endpoints = new LinkedHashMap<>();
Map<String, EndpointInfo<T>> endpointsById = new LinkedHashMap<>(); Map<String, EndpointInfo<T>> endpointsById = new LinkedHashMap<>();
for (String beanName : beanNames) { for (String beanName : beanNames) {
@ -121,8 +125,8 @@ public abstract class AnnotationEndpointDiscoverer<T extends Operation, K>
if (extensionType == null) { if (extensionType == null) {
return Collections.emptyMap(); return Collections.emptyMap();
} }
String[] beanNames = this.applicationContext String[] beanNames = beanNamesForAnnotationIncludingAncestors(
.getBeanNamesForAnnotation(extensionType); this.applicationContext, extensionType);
Map<Class<?>, EndpointExtensionInfo<T>> extensions = new HashMap<>(); Map<Class<?>, EndpointExtensionInfo<T>> extensions = new HashMap<>();
for (String beanName : beanNames) { for (String beanName : beanNames) {
Class<?> beanType = this.applicationContext.getType(beanName); Class<?> beanType = this.applicationContext.getType(beanName);
@ -150,6 +154,28 @@ public abstract class AnnotationEndpointDiscoverer<T extends Operation, K>
} }
private static String[] beanNamesForAnnotationIncludingAncestors(
ListableBeanFactory lbf, Class<? extends Annotation> 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<String> 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<T> getEndpointInfo(Map<Class<?>, EndpointInfo<T>> endpoints, private EndpointInfo<T> getEndpointInfo(Map<Class<?>, EndpointInfo<T>> endpoints,
Class<?> beanType, Class<?> endpointClass) { Class<?> beanType, Class<?> endpointClass) {
EndpointInfo<T> endpoint = endpoints.get(endpointClass); EndpointInfo<T> endpoint = endpoints.get(endpointClass);

@ -58,7 +58,18 @@ public class AnnotationEndpointDiscovererTests {
@Test @Test
public void endpointIsDiscovered() { 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<AnnotationConfigApplicationContext> hasTestEndpoint() {
return (context) -> {
Map<String, EndpointInfo<TestEndpointOperation>> endpoints = mapEndpoints( Map<String, EndpointInfo<TestEndpointOperation>> endpoints = mapEndpoints(
new TestAnnotationEndpointDiscoverer(context).discoverEndpoints()); new TestAnnotationEndpointDiscoverer(context).discoverEndpoints());
assertThat(endpoints).containsOnlyKeys("test"); assertThat(endpoints).containsOnlyKeys("test");
@ -73,7 +84,7 @@ public class AnnotationEndpointDiscovererTests {
String.class), String.class),
ReflectionUtils.findMethod(TestEndpoint.class, "deleteOne", ReflectionUtils.findMethod(TestEndpoint.class, "deleteOne",
String.class)); String.class));
}); };
} }
@Test @Test
@ -114,7 +125,7 @@ public class AnnotationEndpointDiscovererTests {
Map<String, EndpointInfo<TestEndpointOperation>> endpoints = mapEndpoints( Map<String, EndpointInfo<TestEndpointOperation>> endpoints = mapEndpoints(
new TestAnnotationEndpointDiscoverer(context, new TestAnnotationEndpointDiscoverer(context,
(endpointId) -> new CachingConfiguration(0)) (endpointId) -> new CachingConfiguration(0))
.discoverEndpoints()); .discoverEndpoints());
assertThat(endpoints).containsOnlyKeys("test"); assertThat(endpoints).containsOnlyKeys("test");
Map<Method, TestEndpointOperation> operations = mapOperations( Map<Method, TestEndpointOperation> operations = mapOperations(
endpoints.get("test")); endpoints.get("test"));
@ -128,7 +139,7 @@ public class AnnotationEndpointDiscovererTests {
public void endpointMainReadOperationIsNotCachedWithNonMatchingId() { public void endpointMainReadOperationIsNotCachedWithNonMatchingId() {
Function<String, CachingConfiguration> cachingConfigurationFactory = ( Function<String, CachingConfiguration> cachingConfigurationFactory = (
endpointId) -> (endpointId.equals("foo") ? new CachingConfiguration(500) endpointId) -> (endpointId.equals("foo") ? new CachingConfiguration(500)
: new CachingConfiguration(0)); : new CachingConfiguration(0));
load(TestEndpointConfiguration.class, (context) -> { load(TestEndpointConfiguration.class, (context) -> {
Map<String, EndpointInfo<TestEndpointOperation>> endpoints = mapEndpoints( Map<String, EndpointInfo<TestEndpointOperation>> endpoints = mapEndpoints(
new TestAnnotationEndpointDiscoverer(context, new TestAnnotationEndpointDiscoverer(context,
@ -146,7 +157,7 @@ public class AnnotationEndpointDiscovererTests {
public void endpointMainReadOperationIsCachedWithMatchingId() { public void endpointMainReadOperationIsCachedWithMatchingId() {
Function<String, CachingConfiguration> cachingConfigurationFactory = ( Function<String, CachingConfiguration> cachingConfigurationFactory = (
endpointId) -> (endpointId.equals("test") ? new CachingConfiguration(500) endpointId) -> (endpointId.equals("test") ? new CachingConfiguration(500)
: new CachingConfiguration(0)); : new CachingConfiguration(0));
load(TestEndpointConfiguration.class, (context) -> { load(TestEndpointConfiguration.class, (context) -> {
Map<String, EndpointInfo<TestEndpointOperation>> endpoints = mapEndpoints( Map<String, EndpointInfo<TestEndpointOperation>> endpoints = mapEndpoints(
new TestAnnotationEndpointDiscoverer(context, new TestAnnotationEndpointDiscoverer(context,
@ -163,10 +174,10 @@ public class AnnotationEndpointDiscovererTests {
.isEqualTo(500); .isEqualTo(500);
assertThat(operations.get(ReflectionUtils.findMethod(TestEndpoint.class, assertThat(operations.get(ReflectionUtils.findMethod(TestEndpoint.class,
"getOne", String.class)).getInvoker()) "getOne", String.class)).getInvoker())
.isNotInstanceOf(CachingOperationInvoker.class); .isNotInstanceOf(CachingOperationInvoker.class);
assertThat(operations.get(ReflectionUtils.findMethod(TestEndpoint.class, assertThat(operations.get(ReflectionUtils.findMethod(TestEndpoint.class,
"update", String.class, String.class)).getInvoker()) "update", String.class, String.class)).getInvoker())
.isNotInstanceOf(CachingOperationInvoker.class); .isNotInstanceOf(CachingOperationInvoker.class);
}); });
} }
@ -201,8 +212,22 @@ public class AnnotationEndpointDiscovererTests {
private void load(Class<?> configuration, private void load(Class<?> configuration,
Consumer<AnnotationConfigApplicationContext> consumer) { Consumer<AnnotationConfigApplicationContext> consumer) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext( doLoad(null, configuration, consumer);
configuration); }
private void loadWithParent(ApplicationContext parent, Class<?> configuration,
Consumer<AnnotationConfigApplicationContext> consumer) {
doLoad(parent, configuration, consumer);
}
private void doLoad(ApplicationContext parent, Class<?> configuration,
Consumer<AnnotationConfigApplicationContext> consumer) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
if (parent != null) {
context.setParent(parent);
}
context.register(configuration);
context.refresh();
try { try {
consumer.accept(context); consumer.accept(context);
} }

Loading…
Cancel
Save