diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/admin/SpringApplicationAdminJmxAutoConfigurationTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/admin/SpringApplicationAdminJmxAutoConfigurationTests.java index 2b56ef4410d..abeb308b24c 100644 --- a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/admin/SpringApplicationAdminJmxAutoConfigurationTests.java +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/admin/SpringApplicationAdminJmxAutoConfigurationTests.java @@ -30,10 +30,18 @@ import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; 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.context.ConfigurableApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; /** @@ -52,7 +60,7 @@ public class SpringApplicationAdminJmxAutoConfigurationTests { @Rule public final ExpectedException thrown = ExpectedException.none(); - private AnnotationConfigApplicationContext context; + private ConfigurableApplicationContext context; private MBeanServer mBeanServer; @@ -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() { return createObjectName(DEFAULT_JMX_NAME); } @@ -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) { AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(); EnvironmentTestUtils.addEnvironment(applicationContext, environment); diff --git a/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc b/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc index a0dadf697cc..cdb04b88a37 100644 --- a/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc +++ b/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc @@ -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 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 application. diff --git a/spring-boot/src/main/java/org/springframework/boot/admin/SpringApplicationAdminMXBean.java b/spring-boot/src/main/java/org/springframework/boot/admin/SpringApplicationAdminMXBean.java index 61099470260..6b10bf6085c 100644 --- a/spring-boot/src/main/java/org/springframework/boot/admin/SpringApplicationAdminMXBean.java +++ b/spring-boot/src/main/java/org/springframework/boot/admin/SpringApplicationAdminMXBean.java @@ -32,6 +32,23 @@ public interface SpringApplicationAdminMXBean { */ 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. * @see org.springframework.context.ConfigurableApplicationContext#close() diff --git a/spring-boot/src/main/java/org/springframework/boot/admin/SpringApplicationAdminMXBeanRegistrar.java b/spring-boot/src/main/java/org/springframework/boot/admin/SpringApplicationAdminMXBeanRegistrar.java index 602c45b4d64..544343e98c7 100644 --- a/spring-boot/src/main/java/org/springframework/boot/admin/SpringApplicationAdminMXBeanRegistrar.java +++ b/spring-boot/src/main/java/org/springframework/boot/admin/SpringApplicationAdminMXBeanRegistrar.java @@ -27,11 +27,15 @@ import org.apache.commons.logging.LogFactory; import org.springframework.beans.BeansException; import org.springframework.beans.factory.DisposableBean; import org.springframework.beans.factory.InitializingBean; +import org.springframework.boot.context.embedded.EmbeddedWebApplicationContext; import org.springframework.boot.context.event.ApplicationReadyEvent; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.context.ApplicationListener; 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; /** @@ -42,12 +46,14 @@ import org.springframework.util.Assert; * @since 1.3.0 */ public class SpringApplicationAdminMXBeanRegistrar implements ApplicationContextAware, - InitializingBean, DisposableBean, ApplicationListener { + EnvironmentAware, InitializingBean, DisposableBean, ApplicationListener { private static final Log logger = LogFactory.getLog(SpringApplicationAdmin.class); private ConfigurableApplicationContext applicationContext; + private Environment environment = new StandardEnvironment(); + private final ObjectName objectName; private boolean ready = false; @@ -65,6 +71,11 @@ public class SpringApplicationAdminMXBeanRegistrar implements ApplicationContext this.applicationContext = (ConfigurableApplicationContext) applicationContext; } + @Override + public void setEnvironment(Environment environment) { + this.environment = environment; + } + @Override public void onApplicationEvent(ApplicationReadyEvent event) { this.ready = true; @@ -92,6 +103,17 @@ public class SpringApplicationAdminMXBeanRegistrar implements ApplicationContext 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 public void shutdown() { logger.info("Application shutdown requested."); diff --git a/spring-boot/src/main/java/org/springframework/boot/admin/SpringApplicationAdminMXBeanRegistrarTests.java b/spring-boot/src/main/java/org/springframework/boot/admin/SpringApplicationAdminMXBeanRegistrarTests.java index df5dece21e9..3d2c1ae0dcd 100644 --- a/spring-boot/src/main/java/org/springframework/boot/admin/SpringApplicationAdminMXBeanRegistrarTests.java +++ b/spring-boot/src/main/java/org/springframework/boot/admin/SpringApplicationAdminMXBeanRegistrarTests.java @@ -29,15 +29,15 @@ import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; import org.springframework.boot.SpringApplication; -import org.springframework.boot.admin.SpringApplicationAdminMXBeanRegistrar; import org.springframework.context.ApplicationListener; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.event.ContextRefreshedEvent; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.nullValue; +import static org.junit.Assert.assertThat; /** * Tests for {@link SpringApplicationAdminMXBeanRegistrar}. @@ -76,8 +76,7 @@ public class SpringApplicationAdminMXBeanRegistrarTests { @Override public void onApplicationEvent(ContextRefreshedEvent event) { try { - assertFalse("Application should not be ready yet", - isCurrentApplicationReady(objectName)); + assertThat(isApplicationReady(objectName), is(false)); } catch (Exception ex) { throw new IllegalStateException( @@ -86,8 +85,19 @@ public class SpringApplicationAdminMXBeanRegistrarTests { } }); this.context = application.run(); - assertTrue("application should be ready now", - isCurrentApplicationReady(objectName)); + assertThat(isApplicationReady(objectName), is(true)); + } + + @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 @@ -96,16 +106,36 @@ public class SpringApplicationAdminMXBeanRegistrarTests { SpringApplication application = new SpringApplication(Config.class); application.setWebEnvironment(false); this.context = application.run(); - assertTrue("application should be running", this.context.isRunning()); + assertThat(this.context.isRunning(), is(true)); 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.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 (Boolean) this.mBeanServer.getAttribute(objectName, "Ready"); + 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 getAttribute(ObjectName objectName, Class type, String attribute) { + try { + Object value = this.mBeanServer.getAttribute(objectName, attribute); + assertThat((value == null || type.isInstance(value)), is(true)); + return type.cast(value); } catch (Exception ex) { throw new IllegalStateException(ex.getMessage(), ex);