Discover endpoints in parent context
Closes gh-10144
This commit is contained in:
@@ -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<T extends Operation, K>
|
||||
}
|
||||
|
||||
private Map<Class<?>, EndpointInfo<T>> discoverEndpoints(EndpointExposure exposure) {
|
||||
String[] beanNames = this.applicationContext
|
||||
.getBeanNamesForAnnotation(Endpoint.class);
|
||||
String[] beanNames = beanNamesForAnnotationIncludingAncestors(
|
||||
this.applicationContext, Endpoint.class);
|
||||
Map<Class<?>, EndpointInfo<T>> endpoints = new LinkedHashMap<>();
|
||||
Map<String, EndpointInfo<T>> endpointsById = new LinkedHashMap<>();
|
||||
for (String beanName : beanNames) {
|
||||
@@ -121,8 +125,8 @@ public abstract class AnnotationEndpointDiscoverer<T extends Operation, K>
|
||||
if (extensionType == null) {
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
String[] beanNames = this.applicationContext
|
||||
.getBeanNamesForAnnotation(extensionType);
|
||||
String[] beanNames = beanNamesForAnnotationIncludingAncestors(
|
||||
this.applicationContext, extensionType);
|
||||
Map<Class<?>, EndpointExtensionInfo<T>> extensions = new HashMap<>();
|
||||
for (String beanName : beanNames) {
|
||||
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,
|
||||
Class<?> beanType, Class<?> endpointClass) {
|
||||
EndpointInfo<T> endpoint = endpoints.get(endpointClass);
|
||||
|
||||
@@ -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<AnnotationConfigApplicationContext> hasTestEndpoint() {
|
||||
return (context) -> {
|
||||
Map<String, EndpointInfo<TestEndpointOperation>> 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<String, EndpointInfo<TestEndpointOperation>> endpoints = mapEndpoints(
|
||||
new TestAnnotationEndpointDiscoverer(context,
|
||||
(endpointId) -> new CachingConfiguration(0))
|
||||
.discoverEndpoints());
|
||||
.discoverEndpoints());
|
||||
assertThat(endpoints).containsOnlyKeys("test");
|
||||
Map<Method, TestEndpointOperation> operations = mapOperations(
|
||||
endpoints.get("test"));
|
||||
@@ -128,7 +139,7 @@ public class AnnotationEndpointDiscovererTests {
|
||||
public void endpointMainReadOperationIsNotCachedWithNonMatchingId() {
|
||||
Function<String, CachingConfiguration> cachingConfigurationFactory = (
|
||||
endpointId) -> (endpointId.equals("foo") ? new CachingConfiguration(500)
|
||||
: new CachingConfiguration(0));
|
||||
: new CachingConfiguration(0));
|
||||
load(TestEndpointConfiguration.class, (context) -> {
|
||||
Map<String, EndpointInfo<TestEndpointOperation>> endpoints = mapEndpoints(
|
||||
new TestAnnotationEndpointDiscoverer(context,
|
||||
@@ -146,7 +157,7 @@ public class AnnotationEndpointDiscovererTests {
|
||||
public void endpointMainReadOperationIsCachedWithMatchingId() {
|
||||
Function<String, CachingConfiguration> cachingConfigurationFactory = (
|
||||
endpointId) -> (endpointId.equals("test") ? new CachingConfiguration(500)
|
||||
: new CachingConfiguration(0));
|
||||
: new CachingConfiguration(0));
|
||||
load(TestEndpointConfiguration.class, (context) -> {
|
||||
Map<String, EndpointInfo<TestEndpointOperation>> 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<AnnotationConfigApplicationContext> consumer) {
|
||||
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(
|
||||
configuration);
|
||||
doLoad(null, configuration, consumer);
|
||||
}
|
||||
|
||||
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 {
|
||||
consumer.accept(context);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user