Commit b5d49b30 authored by Stephane Nicoll's avatar Stephane Nicoll

Expose additional admin features

Improve SpringApplicationAdminMXBean to expose additional information:

* Whether the application uses an embedded container
* The properties exposed by the `Environment`

This allows to know if the application is web-based and the HTTP port
on which it is running.

Closes gh-3067
parent c177a774
...@@ -30,10 +30,18 @@ import org.junit.Rule; ...@@ -30,10 +30,18 @@ import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
import org.junit.rules.ExpectedException; import org.junit.rules.ExpectedException;
import org.springframework.boot.autoconfigure.jmx.JmxAutoConfiguration; import org.springframework.boot.autoconfigure.jmx.JmxAutoConfiguration;
import org.springframework.boot.autoconfigure.web.DispatcherServletAutoConfiguration;
import org.springframework.boot.autoconfigure.web.EmbeddedServletContainerAutoConfiguration;
import org.springframework.boot.autoconfigure.web.ServerPropertiesAutoConfiguration;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.context.embedded.EmbeddedWebApplicationContext;
import org.springframework.boot.test.EnvironmentTestUtils; import org.springframework.boot.test.EnvironmentTestUtils;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail; import static org.junit.Assert.fail;
/** /**
...@@ -52,7 +60,7 @@ public class SpringApplicationAdminJmxAutoConfigurationTests { ...@@ -52,7 +60,7 @@ public class SpringApplicationAdminJmxAutoConfigurationTests {
@Rule @Rule
public final ExpectedException thrown = ExpectedException.none(); public final ExpectedException thrown = ExpectedException.none();
private AnnotationConfigApplicationContext context; private ConfigurableApplicationContext context;
private MBeanServer mBeanServer; private MBeanServer mBeanServer;
...@@ -104,6 +112,22 @@ public class SpringApplicationAdminJmxAutoConfigurationTests { ...@@ -104,6 +112,22 @@ public class SpringApplicationAdminJmxAutoConfigurationTests {
} }
} }
@SuppressWarnings("unchecked")
@Test
public void registerWithSimpleWebApp() throws Exception {
this.context = new SpringApplicationBuilder()
.sources(
EmbeddedServletContainerAutoConfiguration.class,
ServerPropertiesAutoConfiguration.class, DispatcherServletAutoConfiguration.class,
JmxAutoConfiguration.class, SpringApplicationAdminJmxAutoConfiguration.class)
.run("--" + ENABLE_ADMIN_PROP, "--server.port=0");
assertTrue(this.context instanceof EmbeddedWebApplicationContext);
assertEquals(true, this.mBeanServer.getAttribute(createDefaultObjectName(), "EmbeddedWebApplication"));
int expected = ((EmbeddedWebApplicationContext) this.context).getEmbeddedServletContainer().getPort();
String actual = getProperty(createDefaultObjectName(), "local.server.port");
assertEquals(String.valueOf(expected), actual);
}
private ObjectName createDefaultObjectName() { private ObjectName createDefaultObjectName() {
return createObjectName(DEFAULT_JMX_NAME); return createObjectName(DEFAULT_JMX_NAME);
} }
...@@ -117,6 +141,11 @@ public class SpringApplicationAdminJmxAutoConfigurationTests { ...@@ -117,6 +141,11 @@ public class SpringApplicationAdminJmxAutoConfigurationTests {
} }
} }
private String getProperty(ObjectName objectName, String key) throws Exception {
return (String) this.mBeanServer.invoke(objectName, "getProperty",
new Object[]{key}, new String[]{String.class.getName()});
}
private void load(String... environment) { private void load(String... environment) {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(); AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
EnvironmentTestUtils.addEnvironment(applicationContext, environment); EnvironmentTestUtils.addEnvironment(applicationContext, environment);
......
...@@ -228,6 +228,9 @@ It is possible to enable admin-related features for the application by specifyin ...@@ -228,6 +228,9 @@ It is possible to enable admin-related features for the application by specifyin
on the platform `MBeanServer`. You could use this feature to administer your Spring Boot on the platform `MBeanServer`. You could use this feature to administer your Spring Boot
application remotely. This could also be useful for any service wrapper implementation. application remotely. This could also be useful for any service wrapper implementation.
TIP: If you want to know on which HTTP port the application is running, get the property
with key `local.server.port`.
NOTE: Take care when enabling this feature as the MBean exposes a method to shutdown the NOTE: Take care when enabling this feature as the MBean exposes a method to shutdown the
application. application.
......
...@@ -32,6 +32,23 @@ public interface SpringApplicationAdminMXBean { ...@@ -32,6 +32,23 @@ public interface SpringApplicationAdminMXBean {
*/ */
boolean isReady(); boolean isReady();
/**
* Specify if the application runs in an embedded web container. Can return
* {@code null} if that information is not yet available. It is preferable to
* wait for the application to be {@link #isReady() ready}.
* @return {@code true} if the application runs in an embedded web container
* @see #isReady()
*/
boolean isEmbeddedWebApplication();
/**
* Return the value of the specified key from the application
* {@link org.springframework.core.env.Environment Environment}.
* @param key the property key
* @return the property value or {@code null} if it does not exist
*/
String getProperty(String key);
/** /**
* Shutdown the application. * Shutdown the application.
* @see org.springframework.context.ConfigurableApplicationContext#close() * @see org.springframework.context.ConfigurableApplicationContext#close()
......
...@@ -27,11 +27,15 @@ import org.apache.commons.logging.LogFactory; ...@@ -27,11 +27,15 @@ import org.apache.commons.logging.LogFactory;
import org.springframework.beans.BeansException; import org.springframework.beans.BeansException;
import org.springframework.beans.factory.DisposableBean; import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.InitializingBean;
import org.springframework.boot.context.embedded.EmbeddedWebApplicationContext;
import org.springframework.boot.context.event.ApplicationReadyEvent; import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware; import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ApplicationListener; import org.springframework.context.ApplicationListener;
import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.EnvironmentAware;
import org.springframework.core.env.Environment;
import org.springframework.core.env.StandardEnvironment;
import org.springframework.util.Assert; import org.springframework.util.Assert;
/** /**
...@@ -42,12 +46,14 @@ import org.springframework.util.Assert; ...@@ -42,12 +46,14 @@ import org.springframework.util.Assert;
* @since 1.3.0 * @since 1.3.0
*/ */
public class SpringApplicationAdminMXBeanRegistrar implements ApplicationContextAware, public class SpringApplicationAdminMXBeanRegistrar implements ApplicationContextAware,
InitializingBean, DisposableBean, ApplicationListener<ApplicationReadyEvent> { EnvironmentAware, InitializingBean, DisposableBean, ApplicationListener<ApplicationReadyEvent> {
private static final Log logger = LogFactory.getLog(SpringApplicationAdmin.class); private static final Log logger = LogFactory.getLog(SpringApplicationAdmin.class);
private ConfigurableApplicationContext applicationContext; private ConfigurableApplicationContext applicationContext;
private Environment environment = new StandardEnvironment();
private final ObjectName objectName; private final ObjectName objectName;
private boolean ready = false; private boolean ready = false;
...@@ -65,6 +71,11 @@ public class SpringApplicationAdminMXBeanRegistrar implements ApplicationContext ...@@ -65,6 +71,11 @@ public class SpringApplicationAdminMXBeanRegistrar implements ApplicationContext
this.applicationContext = (ConfigurableApplicationContext) applicationContext; this.applicationContext = (ConfigurableApplicationContext) applicationContext;
} }
@Override
public void setEnvironment(Environment environment) {
this.environment = environment;
}
@Override @Override
public void onApplicationEvent(ApplicationReadyEvent event) { public void onApplicationEvent(ApplicationReadyEvent event) {
this.ready = true; this.ready = true;
...@@ -92,6 +103,17 @@ public class SpringApplicationAdminMXBeanRegistrar implements ApplicationContext ...@@ -92,6 +103,17 @@ public class SpringApplicationAdminMXBeanRegistrar implements ApplicationContext
return SpringApplicationAdminMXBeanRegistrar.this.ready; return SpringApplicationAdminMXBeanRegistrar.this.ready;
} }
@Override
public boolean isEmbeddedWebApplication() {
return (applicationContext != null
&& applicationContext instanceof EmbeddedWebApplicationContext);
}
@Override
public String getProperty(String key) {
return environment.getProperty(key);
}
@Override @Override
public void shutdown() { public void shutdown() {
logger.info("Application shutdown requested."); logger.info("Application shutdown requested.");
......
...@@ -29,15 +29,15 @@ import org.junit.Rule; ...@@ -29,15 +29,15 @@ import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
import org.junit.rules.ExpectedException; import org.junit.rules.ExpectedException;
import org.springframework.boot.SpringApplication; import org.springframework.boot.SpringApplication;
import org.springframework.boot.admin.SpringApplicationAdminMXBeanRegistrar;
import org.springframework.context.ApplicationListener; import org.springframework.context.ApplicationListener;
import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.context.event.ContextRefreshedEvent; import org.springframework.context.event.ContextRefreshedEvent;
import static org.junit.Assert.assertFalse; import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertTrue; import static org.hamcrest.CoreMatchers.nullValue;
import static org.junit.Assert.assertThat;
/** /**
* Tests for {@link SpringApplicationAdminMXBeanRegistrar}. * Tests for {@link SpringApplicationAdminMXBeanRegistrar}.
...@@ -76,8 +76,7 @@ public class SpringApplicationAdminMXBeanRegistrarTests { ...@@ -76,8 +76,7 @@ public class SpringApplicationAdminMXBeanRegistrarTests {
@Override @Override
public void onApplicationEvent(ContextRefreshedEvent event) { public void onApplicationEvent(ContextRefreshedEvent event) {
try { try {
assertFalse("Application should not be ready yet", assertThat(isApplicationReady(objectName), is(false));
isCurrentApplicationReady(objectName));
} }
catch (Exception ex) { catch (Exception ex) {
throw new IllegalStateException( throw new IllegalStateException(
...@@ -86,8 +85,19 @@ public class SpringApplicationAdminMXBeanRegistrarTests { ...@@ -86,8 +85,19 @@ public class SpringApplicationAdminMXBeanRegistrarTests {
} }
}); });
this.context = application.run(); this.context = application.run();
assertTrue("application should be ready now", assertThat(isApplicationReady(objectName), is(true));
isCurrentApplicationReady(objectName)); }
@Test
public void environmentIsExposed() {
final ObjectName objectName = createObjectName(OBJECT_NAME);
SpringApplication application = new SpringApplication(Config.class);
application.setWebEnvironment(false);
this.context = application.run("--foo.bar=blam");
assertThat(isApplicationReady(objectName), is(true));
assertThat(isApplicationEmbeddedWebApplication(objectName), is(false));
assertThat(getProperty(objectName, "foo.bar"), is("blam"));
assertThat(getProperty(objectName, "does.not.exist.test"), is(nullValue()));
} }
@Test @Test
...@@ -96,16 +106,36 @@ public class SpringApplicationAdminMXBeanRegistrarTests { ...@@ -96,16 +106,36 @@ public class SpringApplicationAdminMXBeanRegistrarTests {
SpringApplication application = new SpringApplication(Config.class); SpringApplication application = new SpringApplication(Config.class);
application.setWebEnvironment(false); application.setWebEnvironment(false);
this.context = application.run(); this.context = application.run();
assertTrue("application should be running", this.context.isRunning()); assertThat(this.context.isRunning(), is(true));
invokeShutdown(objectName); invokeShutdown(objectName);
assertFalse("application should not be running", this.context.isRunning()); assertThat(this.context.isRunning(), is(false));
this.thrown.expect(InstanceNotFoundException.class); // JMX cleanup this.thrown.expect(InstanceNotFoundException.class); // JMX cleanup
this.mBeanServer.getObjectInstance(objectName); this.mBeanServer.getObjectInstance(objectName);
} }
private Boolean isCurrentApplicationReady(ObjectName objectName) { private Boolean isApplicationReady(ObjectName objectName) {
return getAttribute(objectName, Boolean.class, "Ready");
}
private Boolean isApplicationEmbeddedWebApplication(ObjectName objectName) {
return getAttribute(objectName, Boolean.class, "EmbeddedWebApplication");
}
private String getProperty(ObjectName objectName, String key) {
try {
return (String) this.mBeanServer.invoke(objectName, "getProperty",
new Object[] {key}, new String[] {String.class.getName()});
}
catch (Exception ex) {
throw new IllegalStateException(ex.getMessage(), ex);
}
}
private <T> T getAttribute(ObjectName objectName, Class<T> type, String attribute) {
try { try {
return (Boolean) this.mBeanServer.getAttribute(objectName, "Ready"); Object value = this.mBeanServer.getAttribute(objectName, attribute);
assertThat((value == null || type.isInstance(value)), is(true));
return type.cast(value);
} }
catch (Exception ex) { catch (Exception ex) {
throw new IllegalStateException(ex.getMessage(), ex); throw new IllegalStateException(ex.getMessage(), ex);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment