mirror of
https://github.com/spring-projects/spring-boot.git
synced 2024-08-29 03:06:45 +08:00
Expose context hierarchy in beans endpoint
Previously, the beans endpoint would only expose the context that contained the endpoint. This commit updates the endpoint so that the context that contains the endpoint and all of its ancestors are exposed. In a context hierarhcy, the relation ship is child -> parent and there is no way to navigate from a parent to a child. As a result, any contexts that are descendants of the context containing the endpoint are not exposed. Closes gh-5188
This commit is contained in:
parent
dbee44a6b5
commit
a72365e1a2
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012-2016 the original author or authors.
|
||||
* Copyright 2012-2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@ -16,7 +16,9 @@
|
||||
|
||||
package org.springframework.boot.actuate.endpoint;
|
||||
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
@ -24,22 +26,25 @@ import org.springframework.boot.json.JsonParser;
|
||||
import org.springframework.boot.json.JsonParserFactory;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationContextAware;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.context.support.LiveBeansView;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Exposes JSON view of Spring beans. If the {@link Environment} contains a key setting
|
||||
* the {@link LiveBeansView#MBEAN_DOMAIN_PROPERTY_NAME} then all application contexts in
|
||||
* the JVM will be shown (and the corresponding MBeans will be registered per the standard
|
||||
* behavior of LiveBeansView). Otherwise only the current application context.
|
||||
* behavior of LiveBeansView). Otherwise only the current application context hierarchy.
|
||||
*
|
||||
* @author Dave Syer
|
||||
* @author Andy Wilkinson
|
||||
*/
|
||||
@ConfigurationProperties(prefix = "endpoints.beans")
|
||||
public class BeansEndpoint extends AbstractEndpoint<List<Object>>
|
||||
implements ApplicationContextAware {
|
||||
|
||||
private final LiveBeansView liveBeansView = new LiveBeansView();
|
||||
private final HierarchyAwareLiveBeansView liveBeansView = new HierarchyAwareLiveBeansView();
|
||||
|
||||
private final JsonParser parser = JsonParserFactory.getJsonParser();
|
||||
|
||||
@ -51,7 +56,7 @@ public class BeansEndpoint extends AbstractEndpoint<List<Object>>
|
||||
public void setApplicationContext(ApplicationContext context) throws BeansException {
|
||||
if (context.getEnvironment()
|
||||
.getProperty(LiveBeansView.MBEAN_DOMAIN_PROPERTY_NAME) == null) {
|
||||
this.liveBeansView.setApplicationContext(context);
|
||||
this.liveBeansView.setLeafContext(context);
|
||||
}
|
||||
}
|
||||
|
||||
@ -60,4 +65,37 @@ public class BeansEndpoint extends AbstractEndpoint<List<Object>>
|
||||
return this.parser.parseList(this.liveBeansView.getSnapshotAsJson());
|
||||
}
|
||||
|
||||
private static class HierarchyAwareLiveBeansView extends LiveBeansView {
|
||||
|
||||
private ConfigurableApplicationContext leafContext;
|
||||
|
||||
private void setLeafContext(ApplicationContext leafContext) {
|
||||
this.leafContext = asConfigurableContext(leafContext);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getSnapshotAsJson() {
|
||||
return generateJson(getContextHierarchy());
|
||||
}
|
||||
|
||||
private ConfigurableApplicationContext asConfigurableContext(
|
||||
ApplicationContext applicationContext) {
|
||||
Assert.isTrue(applicationContext instanceof ConfigurableApplicationContext,
|
||||
"'" + applicationContext
|
||||
+ "' does not implement ConfigurableApplicationContext");
|
||||
return (ConfigurableApplicationContext) applicationContext;
|
||||
}
|
||||
|
||||
private Set<ConfigurableApplicationContext> getContextHierarchy() {
|
||||
Set<ConfigurableApplicationContext> contexts = new LinkedHashSet<ConfigurableApplicationContext>();
|
||||
ApplicationContext context = this.leafContext;
|
||||
while (context != null) {
|
||||
contexts.add(asConfigurableContext(context));
|
||||
context = context.getParent();
|
||||
}
|
||||
return contexts;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,137 @@
|
||||
/*
|
||||
* Copyright 2012-2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.actuate.endpoint;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.assertj.core.api.Condition;
|
||||
import org.junit.After;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@link BeansEndpoint} with an application context hierarchy.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
*/
|
||||
public class BeansEndpointContextHierarchyTests {
|
||||
|
||||
private AnnotationConfigApplicationContext parent;
|
||||
|
||||
private AnnotationConfigApplicationContext child;
|
||||
|
||||
@After
|
||||
public void closeContexts() {
|
||||
if (this.child != null) {
|
||||
this.child.close();
|
||||
}
|
||||
if (this.parent != null) {
|
||||
this.parent.close();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void beansInParentContextAreFound() {
|
||||
load(BeanConfiguration.class, EndpointConfiguration.class);
|
||||
BeansEndpoint endpoint = this.child.getBean(BeansEndpoint.class);
|
||||
List<Object> contexts = endpoint.invoke();
|
||||
assertThat(contexts).hasSize(2);
|
||||
assertThat(contexts.get(1)).has(beanNamed("bean"));
|
||||
assertThat(contexts.get(0)).has(beanNamed("endpoint"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void beansInChildContextAreNotFound() {
|
||||
load(EndpointConfiguration.class, BeanConfiguration.class);
|
||||
BeansEndpoint endpoint = this.child.getBean(BeansEndpoint.class);
|
||||
List<Object> contexts = endpoint.invoke();
|
||||
assertThat(contexts).hasSize(1);
|
||||
assertThat(contexts.get(0)).has(beanNamed("endpoint"));
|
||||
assertThat(contexts.get(0)).doesNotHave(beanNamed("bean"));
|
||||
}
|
||||
|
||||
private void load(Class<?> parent, Class<?> child) {
|
||||
this.parent = new AnnotationConfigApplicationContext(parent);
|
||||
this.child = new AnnotationConfigApplicationContext();
|
||||
this.child.setParent(this.parent);
|
||||
this.child.register(child);
|
||||
this.child.refresh();
|
||||
}
|
||||
|
||||
private ContextHasBeanCondition beanNamed(String beanName) {
|
||||
return new ContextHasBeanCondition(beanName);
|
||||
}
|
||||
|
||||
private static final class ContextHasBeanCondition extends Condition<Object> {
|
||||
|
||||
private final String beanName;
|
||||
|
||||
private ContextHasBeanCondition(String beanName) {
|
||||
this.beanName = beanName;
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public boolean matches(Object context) {
|
||||
if (!(context instanceof Map)) {
|
||||
return false;
|
||||
}
|
||||
List<Object> beans = (List<Object>) ((Map<String, Object>) context)
|
||||
.get("beans");
|
||||
if (beans == null) {
|
||||
return false;
|
||||
}
|
||||
for (Object bean : beans) {
|
||||
if (!(bean instanceof Map)) {
|
||||
return false;
|
||||
}
|
||||
if (this.beanName.equals(((Map<String, Object>) bean).get("bean"))) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
static class EndpointConfiguration {
|
||||
|
||||
@Bean
|
||||
public BeansEndpoint endpoint() {
|
||||
return new BeansEndpoint();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
static class BeanConfiguration {
|
||||
|
||||
@Bean
|
||||
public String bean() {
|
||||
return "bean";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue
Block a user