Reflect context hierarchy in beans endpoint’s response structure

Closes gh-10156
This commit is contained in:
Andy Wilkinson 2017-09-05 10:38:08 +01:00
parent ab54801143
commit a6b30a3aab
3 changed files with 58 additions and 79 deletions

View File

@ -16,8 +16,6 @@
package org.springframework.boot.actuate.endpoint;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@ -32,7 +30,7 @@ import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.util.StringUtils;
/**
* {@link Endpoint} to expose details of an application's bean, grouped by application
* {@link Endpoint} to expose details of an application's beans, grouped by application
* context.
*
* @author Dave Syer
@ -55,50 +53,53 @@ public class BeansEndpoint {
}
@ReadOperation
public Map<String, Object> beans() {
List<ApplicationContextDescriptor> contexts = new ArrayList<>();
ConfigurableApplicationContext current = this.context;
while (current != null) {
contexts.add(ApplicationContextDescriptor.describing(current));
current = getConfigurableParent(current);
}
return Collections.singletonMap("contexts", contexts);
public ApplicationContextDescriptor beans() {
return ApplicationContextDescriptor.describing(this.context);
}
private ConfigurableApplicationContext getConfigurableParent(
ConfigurableApplicationContext context) {
ApplicationContext parent = context.getParent();
if (parent instanceof ConfigurableApplicationContext) {
return (ConfigurableApplicationContext) parent;
/**
* Response produced by the {@link BeansEndpoint}, primarily intended for
* serialization to JSON.
*/
public static class BeansEndpointResponse {
private List<ApplicationContextDescriptor> contexts;
public BeansEndpointResponse(List<ApplicationContextDescriptor> contexts) {
this.contexts = contexts;
}
return null;
public List<ApplicationContextDescriptor> getContexts() {
return this.contexts;
}
}
/**
* A description of an application context, primarily intended for serialization to
* JSON.
*/
static final class ApplicationContextDescriptor {
public static final class ApplicationContextDescriptor {
private final String id;
private final String parentId;
private final Map<String, BeanDescriptor> beans;
private ApplicationContextDescriptor(String id, String parentId,
Map<String, BeanDescriptor> beans) {
private final ApplicationContextDescriptor parent;
private ApplicationContextDescriptor(String id, Map<String, BeanDescriptor> beans,
ApplicationContextDescriptor parent) {
this.id = id;
this.parentId = parentId;
this.beans = beans;
this.parent = parent;
}
public String getId() {
return this.id;
}
public String getParentId() {
return this.parentId;
public ApplicationContextDescriptor getParent() {
return this.parent;
}
public Map<String, BeanDescriptor> getBeans() {
@ -107,10 +108,12 @@ public class BeansEndpoint {
private static ApplicationContextDescriptor describing(
ConfigurableApplicationContext context) {
ApplicationContext parent = context.getParent();
if (context == null) {
return null;
}
return new ApplicationContextDescriptor(context.getId(),
parent == null ? null : parent.getId(),
describeBeans(context.getBeanFactory()));
describeBeans(context.getBeanFactory()),
describing(getConfigurableParent(context)));
}
private static Map<String, BeanDescriptor> describeBeans(
@ -138,13 +141,22 @@ public class BeansEndpoint {
&& (!bd.isLazyInit() || bf.containsSingleton(beanName)));
}
private static ConfigurableApplicationContext getConfigurableParent(
ConfigurableApplicationContext context) {
ApplicationContext parent = context.getParent();
if (parent instanceof ConfigurableApplicationContext) {
return (ConfigurableApplicationContext) parent;
}
return null;
}
}
/**
* A description of a bean in an application context, primarily intended for
* serialization to JSON.
*/
static final class BeanDescriptor {
public static final class BeanDescriptor {
private final String[] aliases;

View File

@ -43,27 +43,22 @@ import static org.assertj.core.api.Assertions.assertThat;
*/
public class BeansEndpointTests {
@SuppressWarnings("unchecked")
@Test
public void beansAreFound() {
ApplicationContextRunner contextRunner = new ApplicationContextRunner()
.withUserConfiguration(EndpointConfiguration.class);
contextRunner.run((context) -> {
Map<String, Object> result = context.getBean(BeansEndpoint.class).beans();
List<ApplicationContextDescriptor> contexts = (List<ApplicationContextDescriptor>) result
.get("contexts");
assertThat(contexts).hasSize(1);
ApplicationContextDescriptor contextDescriptor = contexts.get(0);
assertThat(contextDescriptor.getParentId()).isNull();
assertThat(contextDescriptor.getId()).isEqualTo(context.getId());
Map<String, BeanDescriptor> beans = contextDescriptor.getBeans();
ApplicationContextDescriptor result = context.getBean(BeansEndpoint.class)
.beans();
assertThat(result.getParent()).isNull();
assertThat(result.getId()).isEqualTo(context.getId());
Map<String, BeanDescriptor> beans = result.getBeans();
assertThat(beans.size())
.isLessThanOrEqualTo(context.getBeanDefinitionCount());
assertThat(contexts.get(0).getBeans()).containsKey("endpoint");
assertThat(beans).containsKey("endpoint");
});
}
@SuppressWarnings("unchecked")
@Test
public void infrastructureBeansAreOmitted() {
ApplicationContextRunner contextRunner = new ApplicationContextRunner()
@ -75,32 +70,28 @@ public class BeansEndpointTests {
.filter((name) -> BeanDefinition.ROLE_INFRASTRUCTURE == factory
.getBeanDefinition(name).getRole())
.collect(Collectors.toList());
Map<String, Object> result = context.getBean(BeansEndpoint.class).beans();
List<ApplicationContextDescriptor> contexts = (List<ApplicationContextDescriptor>) result
.get("contexts");
Map<String, BeanDescriptor> beans = contexts.get(0).getBeans();
ApplicationContextDescriptor result = context.getBean(BeansEndpoint.class)
.beans();
Map<String, BeanDescriptor> beans = result.getBeans();
for (String infrastructureBean : infrastructureBeans) {
assertThat(beans).doesNotContainKey(infrastructureBean);
}
});
}
@SuppressWarnings("unchecked")
@Test
public void lazyBeansAreOmitted() {
ApplicationContextRunner contextRunner = new ApplicationContextRunner()
.withUserConfiguration(EndpointConfiguration.class,
LazyBeanConfiguration.class);
contextRunner.run((context) -> {
Map<String, Object> result = context.getBean(BeansEndpoint.class).beans();
List<ApplicationContextDescriptor> contexts = (List<ApplicationContextDescriptor>) result
.get("contexts");
ApplicationContextDescriptor result = context.getBean(BeansEndpoint.class)
.beans();
assertThat(context).hasBean("lazyBean");
assertThat(contexts.get(0).getBeans()).doesNotContainKey("lazyBean");
assertThat(result.getBeans()).doesNotContainKey("lazyBean");
});
}
@SuppressWarnings("unchecked")
@Test
public void beansInParentContextAreFound() {
ApplicationContextRunner parentRunner = new ApplicationContextRunner()
@ -110,31 +101,9 @@ public class BeansEndpointTests {
.withUserConfiguration(EndpointConfiguration.class).withParent(parent)
.run(child -> {
BeansEndpoint endpoint = child.getBean(BeansEndpoint.class);
Map<String, Object> result = endpoint.beans();
List<ApplicationContextDescriptor> contexts = (List<ApplicationContextDescriptor>) result
.get("contexts");
assertThat(contexts).hasSize(2);
assertThat(contexts.get(1).getBeans()).containsKey("bean");
assertThat(contexts.get(0).getBeans()).containsKey("endpoint");
});
});
}
@SuppressWarnings("unchecked")
@Test
public void beansInChildContextAreNotFound() {
ApplicationContextRunner parentRunner = new ApplicationContextRunner()
.withUserConfiguration(EndpointConfiguration.class);
parentRunner.run((parent) -> {
new ApplicationContextRunner().withUserConfiguration(BeanConfiguration.class)
.withParent(parent).run(child -> {
BeansEndpoint endpoint = child.getBean(BeansEndpoint.class);
Map<String, Object> result = endpoint.beans();
List<ApplicationContextDescriptor> contexts = (List<ApplicationContextDescriptor>) result
.get("contexts");
assertThat(contexts).hasSize(1);
assertThat(contexts.get(0).getBeans()).containsKey("endpoint");
assertThat(contexts.get(0).getBeans()).doesNotContainKey("bean");
ApplicationContextDescriptor result = endpoint.beans();
assertThat(result.getParent().getBeans()).containsKey("bean");
assertThat(result.getBeans()).containsKey("endpoint");
});
});
}

View File

@ -217,10 +217,8 @@ public class SampleActuatorApplicationTests {
.withBasicAuth("user", getPassword())
.getForEntity("/application/beans", Map.class);
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.OK);
assertThat(entity.getBody()).hasSize(1);
Map<String, Object> body = (Map<String, Object>) ((List<?>) entity.getBody()
.get("contexts")).get(0);
assertThat(((String) body.get("id"))).startsWith("application");
assertThat(entity.getBody()).containsOnlyKeys("beans", "parent", "id");
assertThat(((String) entity.getBody().get("id"))).startsWith("application");
}
@SuppressWarnings("unchecked")