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;
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);
......
......@@ -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.
......
......@@ -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()
......
......@@ -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<ApplicationReadyEvent> {
EnvironmentAware, InitializingBean, DisposableBean, ApplicationListener<ApplicationReadyEvent> {
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.");
......
......@@ -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 (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 {
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) {
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