Commit bce4bb88 authored by Phillip Webb's avatar Phillip Webb

Polish start stop support

parent 09a29a72
...@@ -43,7 +43,8 @@ import org.springframework.jmx.export.MBeanExporter; ...@@ -43,7 +43,8 @@ import org.springframework.jmx.export.MBeanExporter;
class SpringApplicationLifecycleAutoConfiguration { class SpringApplicationLifecycleAutoConfiguration {
/** /**
* The property to use to customize the {@code ObjectName} of the application lifecycle mbean. * The property to use to customize the {@code ObjectName} of the application
* lifecycle mbean.
*/ */
static final String JMX_NAME_PROPERTY = "spring.context.lifecycle.jmx-name"; static final String JMX_NAME_PROPERTY = "spring.context.lifecycle.jmx-name";
...@@ -61,10 +62,10 @@ class SpringApplicationLifecycleAutoConfiguration { ...@@ -61,10 +62,10 @@ class SpringApplicationLifecycleAutoConfiguration {
@Bean @Bean
public SpringApplicationLifecycleRegistrar springApplicationLifecycleRegistrar() public SpringApplicationLifecycleRegistrar springApplicationLifecycleRegistrar()
throws MalformedObjectNameException { throws MalformedObjectNameException {
String jmxName = this.environment
String jmxName = this.environment.getProperty(JMX_NAME_PROPERTY, DEFAULT_JMX_NAME); .getProperty(JMX_NAME_PROPERTY, DEFAULT_JMX_NAME);
if (mbeanExporter != null) { // Make sure to not register that MBean twice if (this.mbeanExporter != null) { // Make sure to not register that MBean twice
mbeanExporter.addExcludedBean(jmxName); this.mbeanExporter.addExcludedBean(jmxName);
} }
return new SpringApplicationLifecycleRegistrar(jmxName); return new SpringApplicationLifecycleRegistrar(jmxName);
} }
......
...@@ -17,6 +17,7 @@ ...@@ -17,6 +17,7 @@
package org.springframework.boot.autoconfigure.context; package org.springframework.boot.autoconfigure.context;
import java.lang.management.ManagementFactory; import java.lang.management.ManagementFactory;
import javax.management.InstanceNotFoundException; import javax.management.InstanceNotFoundException;
import javax.management.MBeanServer; import javax.management.MBeanServer;
import javax.management.MalformedObjectNameException; import javax.management.MalformedObjectNameException;
...@@ -28,7 +29,6 @@ import org.junit.Before; ...@@ -28,7 +29,6 @@ import org.junit.Before;
import org.junit.Rule; 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.test.EnvironmentTestUtils; import org.springframework.boot.test.EnvironmentTestUtils;
import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext;
...@@ -65,17 +65,16 @@ public class SpringApplicationLifecycleAutoConfigurationTests { ...@@ -65,17 +65,16 @@ public class SpringApplicationLifecycleAutoConfigurationTests {
} }
@Test @Test
public void notRegisteredByDefault() throws MalformedObjectNameException, InstanceNotFoundException { public void notRegisteredByDefault() throws MalformedObjectNameException,
InstanceNotFoundException {
load(); load();
this.thrown.expect(InstanceNotFoundException.class);
thrown.expect(InstanceNotFoundException.class);
this.mBeanServer.getObjectInstance(createDefaultObjectName()); this.mBeanServer.getObjectInstance(createDefaultObjectName());
} }
@Test @Test
public void registeredWithProperty() throws Exception { public void registeredWithProperty() throws Exception {
load(ENABLE_LIFECYCLE_PROP); load(ENABLE_LIFECYCLE_PROP);
ObjectName objectName = createDefaultObjectName(); ObjectName objectName = createDefaultObjectName();
ObjectInstance objectInstance = this.mBeanServer.getObjectInstance(objectName); ObjectInstance objectInstance = this.mBeanServer.getObjectInstance(objectName);
assertNotNull("Lifecycle bean should have been registered", objectInstance); assertNotNull("Lifecycle bean should have been registered", objectInstance);
...@@ -84,18 +83,17 @@ public class SpringApplicationLifecycleAutoConfigurationTests { ...@@ -84,18 +83,17 @@ public class SpringApplicationLifecycleAutoConfigurationTests {
@Test @Test
public void registerWithCustomJmxName() throws InstanceNotFoundException { public void registerWithCustomJmxName() throws InstanceNotFoundException {
String customJmxName = "org.acme:name=FooBar"; String customJmxName = "org.acme:name=FooBar";
System.setProperty(SpringApplicationLifecycleAutoConfiguration.JMX_NAME_PROPERTY, customJmxName); System.setProperty(SpringApplicationLifecycleAutoConfiguration.JMX_NAME_PROPERTY,
customJmxName);
try { try {
load(ENABLE_LIFECYCLE_PROP); load(ENABLE_LIFECYCLE_PROP);
try { try {
this.mBeanServer.getObjectInstance(createObjectName(customJmxName)); this.mBeanServer.getObjectInstance(createObjectName(customJmxName));
} }
catch (InstanceNotFoundException e) { catch (InstanceNotFoundException ex) {
fail("lifecycle MBean should have been exposed with custom name"); fail("lifecycle MBean should have been exposed with custom name");
} }
this.thrown.expect(InstanceNotFoundException.class); // Should not be exposed
thrown.expect(InstanceNotFoundException.class); // Should not be exposed
this.mBeanServer.getObjectInstance(createDefaultObjectName()); this.mBeanServer.getObjectInstance(createDefaultObjectName());
} }
finally { finally {
...@@ -111,18 +109,18 @@ public class SpringApplicationLifecycleAutoConfigurationTests { ...@@ -111,18 +109,18 @@ public class SpringApplicationLifecycleAutoConfigurationTests {
try { try {
return new ObjectName(jmxName); return new ObjectName(jmxName);
} }
catch (MalformedObjectNameException e) { catch (MalformedObjectNameException ex) {
throw new IllegalStateException("Invalid jmx name " + jmxName, e); throw new IllegalStateException("Invalid jmx name " + jmxName, ex);
} }
} }
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);
applicationContext.register(JmxAutoConfiguration.class, SpringApplicationLifecycleAutoConfiguration.class); applicationContext.register(JmxAutoConfiguration.class,
SpringApplicationLifecycleAutoConfiguration.class);
applicationContext.refresh(); applicationContext.refresh();
this.context = applicationContext; this.context = applicationContext;
} }
} }
...@@ -55,13 +55,6 @@ public class RunProcess { ...@@ -55,13 +55,6 @@ public class RunProcess {
return run(waitForProcess, Arrays.asList(args)); return run(waitForProcess, Arrays.asList(args));
} }
/**
* Kill this process.
*/
public void kill() {
doKill();
}
protected int run(boolean waitForProcess, Collection<String> args) throws IOException { protected int run(boolean waitForProcess, Collection<String> args) throws IOException {
ProcessBuilder builder = new ProcessBuilder(this.command); ProcessBuilder builder = new ProcessBuilder(this.command);
builder.command().addAll(args); builder.command().addAll(args);
...@@ -131,7 +124,7 @@ public class RunProcess { ...@@ -131,7 +124,7 @@ public class RunProcess {
return true; return true;
} }
} }
catch (Exception e) { catch (Exception ex) {
return true; return true;
} }
return false; return false;
...@@ -180,6 +173,13 @@ public class RunProcess { ...@@ -180,6 +173,13 @@ public class RunProcess {
} }
/**
* Kill this process.
*/
public void kill() {
doKill();
}
private boolean doKill() { private boolean doKill() {
// destroy the running process // destroy the running process
Process process = this.process; Process process = this.process;
...@@ -194,7 +194,6 @@ public class RunProcess { ...@@ -194,7 +194,6 @@ public class RunProcess {
Thread.currentThread().interrupt(); Thread.currentThread().interrupt();
} }
} }
return false; return false;
} }
......
...@@ -22,8 +22,7 @@ import javax.management.MBeanServer; ...@@ -22,8 +22,7 @@ import javax.management.MBeanServer;
import javax.management.ObjectName; import javax.management.ObjectName;
/** /**
* This sample app simulates the JMX Mbean that is exposed by the Spring * This sample app simulates the JMX Mbean that is exposed by the Spring Boot application.
* Boot application.
*/ */
public class SampleApplication { public class SampleApplication {
...@@ -31,7 +30,8 @@ public class SampleApplication { ...@@ -31,7 +30,8 @@ public class SampleApplication {
public static void main(String[] args) throws Exception { public static void main(String[] args) throws Exception {
MBeanServer mbs = ManagementFactory.getPlatformMBeanServer(); MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
ObjectName name = new ObjectName("org.springframework.boot:type=Lifecycle,name=springApplicationLifecycle"); ObjectName name = new ObjectName(
"org.springframework.boot:type=Lifecycle,name=springApplicationLifecycle");
SpringApplicationLifecycle mbean = new SpringApplicationLifecycle(); SpringApplicationLifecycle mbean = new SpringApplicationLifecycle();
mbs.registerMBean(mbean, name); mbs.registerMBean(mbean, name);
...@@ -41,7 +41,8 @@ public class SampleApplication { ...@@ -41,7 +41,8 @@ public class SampleApplication {
int waitAttempts = 0; int waitAttempts = 0;
while (!mbean.shutdownInvoked) { while (!mbean.shutdownInvoked) {
if (waitAttempts > 10) { if (waitAttempts > 10) {
throw new IllegalStateException("Shutdown should have been invoked by now"); throw new IllegalStateException(
"Shutdown should have been invoked by now");
} }
synchronized (lock) { synchronized (lock) {
lock.wait(250); lock.wait(250);
...@@ -50,7 +51,6 @@ public class SampleApplication { ...@@ -50,7 +51,6 @@ public class SampleApplication {
} }
} }
public interface SpringApplicationLifecycleMXBean { public interface SpringApplicationLifecycleMXBean {
boolean isReady(); boolean isReady();
...@@ -76,5 +76,7 @@ public class SampleApplication { ...@@ -76,5 +76,7 @@ public class SampleApplication {
this.shutdownInvoked = true; this.shutdownInvoked = true;
System.out.println("Shutdown requested"); System.out.println("Shutdown requested");
} }
} }
}
\ No newline at end of file }
...@@ -21,8 +21,7 @@ import javax.management.MBeanServer; ...@@ -21,8 +21,7 @@ import javax.management.MBeanServer;
import javax.management.ObjectName; import javax.management.ObjectName;
/** /**
* This sample app simulates the JMX Mbean that is exposed by the Spring * This sample app simulates the JMX Mbean that is exposed by the Spring Boot application.
* Boot application.
*/ */
public class SampleApplication { public class SampleApplication {
...@@ -30,7 +29,8 @@ public class SampleApplication { ...@@ -30,7 +29,8 @@ public class SampleApplication {
public static void main(String[] args) throws Exception { public static void main(String[] args) throws Exception {
MBeanServer mbs = ManagementFactory.getPlatformMBeanServer(); MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
ObjectName name = new ObjectName("org.springframework.boot:type=Lifecycle,name=springApplicationLifecycle"); ObjectName name = new ObjectName(
"org.springframework.boot:type=Lifecycle,name=springApplicationLifecycle");
SpringApplicationLifecycle mbean = new SpringApplicationLifecycle(); SpringApplicationLifecycle mbean = new SpringApplicationLifecycle();
mbs.registerMBean(mbean, name); mbs.registerMBean(mbean, name);
...@@ -40,7 +40,8 @@ public class SampleApplication { ...@@ -40,7 +40,8 @@ public class SampleApplication {
int waitAttempts = 0; int waitAttempts = 0;
while (!mbean.shutdownInvoked) { while (!mbean.shutdownInvoked) {
if (waitAttempts > 10) { if (waitAttempts > 10) {
throw new IllegalStateException("Shutdown should have been invoked by now"); throw new IllegalStateException(
"Shutdown should have been invoked by now");
} }
synchronized (lock) { synchronized (lock) {
lock.wait(250); lock.wait(250);
...@@ -49,7 +50,6 @@ public class SampleApplication { ...@@ -49,7 +50,6 @@ public class SampleApplication {
} }
} }
public interface SpringApplicationLifecycleMXBean { public interface SpringApplicationLifecycleMXBean {
boolean isReady(); boolean isReady();
...@@ -75,5 +75,7 @@ public class SampleApplication { ...@@ -75,5 +75,7 @@ public class SampleApplication {
this.shutdownInvoked = true; this.shutdownInvoked = true;
System.out.println("Shutdown requested"); System.out.println("Shutdown requested");
} }
} }
}
\ No newline at end of file }
...@@ -36,7 +36,6 @@ import org.apache.maven.plugins.annotations.Parameter; ...@@ -36,7 +36,6 @@ import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.project.MavenProject; import org.apache.maven.project.MavenProject;
import org.apache.maven.shared.artifact.filter.collection.AbstractArtifactFeatureFilter; import org.apache.maven.shared.artifact.filter.collection.AbstractArtifactFeatureFilter;
import org.apache.maven.shared.artifact.filter.collection.FilterArtifacts; import org.apache.maven.shared.artifact.filter.collection.FilterArtifacts;
import org.springframework.boot.loader.tools.FileUtils; import org.springframework.boot.loader.tools.FileUtils;
import org.springframework.boot.loader.tools.MainClassFinder; import org.springframework.boot.loader.tools.MainClassFinder;
...@@ -137,8 +136,7 @@ public abstract class AbstractRunMojo extends AbstractDependencyFilterMojo { ...@@ -137,8 +136,7 @@ public abstract class AbstractRunMojo extends AbstractDependencyFilterMojo {
* @return {@code true} if the application process should be forked * @return {@code true} if the application process should be forked
*/ */
protected boolean isFork() { protected boolean isFork() {
return (Boolean.TRUE.equals(this.fork) return (Boolean.TRUE.equals(this.fork) || (this.fork == null && (hasAgent() || hasJvmArgs())));
|| (this.fork == null && (hasAgent() || hasJvmArgs())));
} }
private boolean hasAgent() { private boolean hasAgent() {
...@@ -165,7 +163,7 @@ public abstract class AbstractRunMojo extends AbstractDependencyFilterMojo { ...@@ -165,7 +163,7 @@ public abstract class AbstractRunMojo extends AbstractDependencyFilterMojo {
} }
CodeSource source = loaded.getProtectionDomain().getCodeSource(); CodeSource source = loaded.getProtectionDomain().getCodeSource();
if (source != null) { if (source != null) {
this.agent = new File[] {new File(source.getLocation().getFile())}; this.agent = new File[] { new File(source.getLocation().getFile()) };
} }
} }
} }
...@@ -178,7 +176,8 @@ public abstract class AbstractRunMojo extends AbstractDependencyFilterMojo { ...@@ -178,7 +176,8 @@ public abstract class AbstractRunMojo extends AbstractDependencyFilterMojo {
} }
} }
private void run(String startClassName) throws MojoExecutionException, MojoFailureException { private void run(String startClassName) throws MojoExecutionException,
MojoFailureException {
findAgent(); findAgent();
if (isFork()) { if (isFork()) {
doRunWithForkedJvm(startClassName); doRunWithForkedJvm(startClassName);
...@@ -196,8 +195,8 @@ public abstract class AbstractRunMojo extends AbstractDependencyFilterMojo { ...@@ -196,8 +195,8 @@ public abstract class AbstractRunMojo extends AbstractDependencyFilterMojo {
} }
} }
private void doRunWithForkedJvm(String startClassName) private void doRunWithForkedJvm(String startClassName) throws MojoExecutionException,
throws MojoExecutionException, MojoFailureException { MojoFailureException {
List<String> args = new ArrayList<String>(); List<String> args = new ArrayList<String>();
addAgents(args); addAgents(args);
addJvmArgs(args); addJvmArgs(args);
...@@ -213,8 +212,8 @@ public abstract class AbstractRunMojo extends AbstractDependencyFilterMojo { ...@@ -213,8 +212,8 @@ public abstract class AbstractRunMojo extends AbstractDependencyFilterMojo {
* @throws MojoExecutionException * @throws MojoExecutionException
* @throws MojoFailureException * @throws MojoFailureException
*/ */
protected abstract void runWithForkedJvm(List<String> args) throws MojoExecutionException, MojoFailureException; protected abstract void runWithForkedJvm(List<String> args)
throws MojoExecutionException, MojoFailureException;
/** /**
* Run with the current VM, using the specified arguments. * Run with the current VM, using the specified arguments.
...@@ -277,8 +276,8 @@ public abstract class AbstractRunMojo extends AbstractDependencyFilterMojo { ...@@ -277,8 +276,8 @@ public abstract class AbstractRunMojo extends AbstractDependencyFilterMojo {
args.add("-cp"); args.add("-cp");
args.add(classpath.toString()); args.add(classpath.toString());
} }
catch (Exception e) { catch (Exception ex) {
throw new MojoExecutionException("Could not build classpath", e); throw new MojoExecutionException("Could not build classpath", ex);
} }
} }
...@@ -358,8 +357,8 @@ public abstract class AbstractRunMojo extends AbstractDependencyFilterMojo { ...@@ -358,8 +357,8 @@ public abstract class AbstractRunMojo extends AbstractDependencyFilterMojo {
getLog().debug(sb.toString().trim()); getLog().debug(sb.toString().trim());
} }
private static class TestArtifactFilter extends AbstractArtifactFeatureFilter { private static class TestArtifactFilter extends AbstractArtifactFeatureFilter {
public TestArtifactFilter() { public TestArtifactFilter() {
super("", Artifact.SCOPE_TEST); super("", Artifact.SCOPE_TEST);
} }
...@@ -368,6 +367,7 @@ public abstract class AbstractRunMojo extends AbstractDependencyFilterMojo { ...@@ -368,6 +367,7 @@ public abstract class AbstractRunMojo extends AbstractDependencyFilterMojo {
protected String getArtifactFeature(Artifact artifact) { protected String getArtifactFeature(Artifact artifact) {
return artifact.getScope(); return artifact.getScope();
} }
} }
/** /**
...@@ -423,7 +423,7 @@ public abstract class AbstractRunMojo extends AbstractDependencyFilterMojo { ...@@ -423,7 +423,7 @@ public abstract class AbstractRunMojo extends AbstractDependencyFilterMojo {
if (!mainMethod.isAccessible()) { if (!mainMethod.isAccessible()) {
mainMethod.setAccessible(true); mainMethod.setAccessible(true);
} }
mainMethod.invoke(null, new Object[] {this.args}); mainMethod.invoke(null, new Object[] { this.args });
} }
catch (NoSuchMethodException ex) { catch (NoSuchMethodException ex) {
Exception wrappedEx = new Exception( Exception wrappedEx = new Exception(
......
...@@ -42,7 +42,7 @@ class RunArguments { ...@@ -42,7 +42,7 @@ class RunArguments {
} }
public LinkedList<String> getArgs() { public LinkedList<String> getArgs() {
return args; return this.args;
} }
public String[] asArray() { public String[] asArray() {
......
...@@ -24,7 +24,6 @@ import org.apache.maven.plugins.annotations.Execute; ...@@ -24,7 +24,6 @@ import org.apache.maven.plugins.annotations.Execute;
import org.apache.maven.plugins.annotations.LifecyclePhase; import org.apache.maven.plugins.annotations.LifecyclePhase;
import org.apache.maven.plugins.annotations.Mojo; import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.ResolutionScope; import org.apache.maven.plugins.annotations.ResolutionScope;
import org.springframework.boot.loader.tools.JavaExecutable; import org.springframework.boot.loader.tools.JavaExecutable;
import org.springframework.boot.loader.tools.RunProcess; import org.springframework.boot.loader.tools.RunProcess;
...@@ -41,15 +40,17 @@ public class RunMojo extends AbstractRunMojo { ...@@ -41,15 +40,17 @@ public class RunMojo extends AbstractRunMojo {
@Override @Override
protected void runWithForkedJvm(List<String> args) throws MojoExecutionException { protected void runWithForkedJvm(List<String> args) throws MojoExecutionException {
try { try {
new RunProcess(new JavaExecutable().toString()).run(true, args new RunProcess(new JavaExecutable().toString()).run(true,
.toArray(new String[args.size()])); args.toArray(new String[args.size()]));
} }
catch (Exception ex) { catch (Exception ex) {
throw new MojoExecutionException("Could not exec java", ex); throw new MojoExecutionException("Could not exec java", ex);
} }
} }
protected void runWithMavenJvm(String startClassName, String... arguments) throws MojoExecutionException { @Override
protected void runWithMavenJvm(String startClassName, String... arguments)
throws MojoExecutionException {
IsolatedThreadGroup threadGroup = new IsolatedThreadGroup(startClassName); IsolatedThreadGroup threadGroup = new IsolatedThreadGroup(startClassName);
Thread launchThread = new Thread(threadGroup, new LaunchRunner(startClassName, Thread launchThread = new Thread(threadGroup, new LaunchRunner(startClassName,
arguments), startClassName + ".main()"); arguments), startClassName + ".main()");
......
...@@ -17,6 +17,7 @@ ...@@ -17,6 +17,7 @@
package org.springframework.boot.maven; package org.springframework.boot.maven;
import java.io.IOException; import java.io.IOException;
import javax.management.AttributeNotFoundException; import javax.management.AttributeNotFoundException;
import javax.management.InstanceNotFoundException; import javax.management.InstanceNotFoundException;
import javax.management.MBeanException; import javax.management.MBeanException;
...@@ -35,20 +36,19 @@ import org.apache.maven.plugin.MojoExecutionException; ...@@ -35,20 +36,19 @@ import org.apache.maven.plugin.MojoExecutionException;
* information about the lifecycle of a given Spring application. * information about the lifecycle of a given Spring application.
* *
* @author Stephane Nicoll * @author Stephane Nicoll
* @since 1.3.0
*/ */
class SpringApplicationLifecycleClient { class SpringApplicationLifecycleClient {
//Note: see org.springframework.boot.autoconfigure.test.SpringApplicationLifecycleAutoConfiguration // Note: see SpringApplicationLifecycleAutoConfiguration
static final String DEFAULT_OBJECT_NAME = static final String DEFAULT_OBJECT_NAME = "org.springframework.boot:type=Lifecycle,name=springApplicationLifecycle";
"org.springframework.boot:type=Lifecycle,name=springApplicationLifecycle";
private final MBeanServerConnection mBeanServerConnection; private final MBeanServerConnection connection;
private final ObjectName objectName; private final ObjectName objectName;
public SpringApplicationLifecycleClient(MBeanServerConnection mBeanServerConnection, String jmxName) { public SpringApplicationLifecycleClient(MBeanServerConnection connection,
this.mBeanServerConnection = mBeanServerConnection; String jmxName) {
this.connection = connection;
this.objectName = toObjectName(jmxName); this.objectName = toObjectName(jmxName);
} }
...@@ -66,30 +66,32 @@ class SpringApplicationLifecycleClient { ...@@ -66,30 +66,32 @@ class SpringApplicationLifecycleClient {
} }
/** /**
* Check if the spring application managed by this instance is ready. * Check if the spring application managed by this instance is ready. Returns
* <p>Returns {@code false} if the mbean is not yet deployed so this method * {@code false} if the mbean is not yet deployed so this method should be repeatedly
* should be repeatedly called until a timeout is reached. * called until a timeout is reached.
* @return {@code true} if the application is ready to service requests * @return {@code true} if the application is ready to service requests
* @throws MojoExecutionException if the JMX service could not be contacted * @throws MojoExecutionException if the JMX service could not be contacted
*/ */
public boolean isReady() throws MojoExecutionException { public boolean isReady() throws MojoExecutionException {
try { try {
return (Boolean) this.mBeanServerConnection.getAttribute(this.objectName, "Ready"); return (Boolean) this.connection.getAttribute(this.objectName, "Ready");
} }
catch (InstanceNotFoundException e) { catch (InstanceNotFoundException ex) {
return false; // Instance not available yet return false; // Instance not available yet
} }
catch (AttributeNotFoundException e) { catch (AttributeNotFoundException ex) {
throw new IllegalStateException("Unexpected: attribute 'Ready' not available", e); throw new IllegalStateException(
"Unexpected: attribute 'Ready' not available", ex);
} }
catch (ReflectionException e) { catch (ReflectionException ex) {
throw new MojoExecutionException("Failed to retrieve Ready attribute", e.getCause()); throw new MojoExecutionException("Failed to retrieve Ready attribute",
ex.getCause());
} }
catch (MBeanException e) { catch (MBeanException ex) {
throw new MojoExecutionException(e.getMessage(), e); throw new MojoExecutionException(ex.getMessage(), ex);
} }
catch (IOException e) { catch (IOException ex) {
throw new MojoExecutionException(e.getMessage(), e); throw new MojoExecutionException(ex.getMessage(), ex);
} }
} }
...@@ -99,15 +101,16 @@ class SpringApplicationLifecycleClient { ...@@ -99,15 +101,16 @@ class SpringApplicationLifecycleClient {
* @throws IOException if an I/O error occurs * @throws IOException if an I/O error occurs
* @throws InstanceNotFoundException if the lifecycle mbean cannot be found * @throws InstanceNotFoundException if the lifecycle mbean cannot be found
*/ */
public void stop() throws MojoExecutionException, IOException, InstanceNotFoundException { public void stop() throws MojoExecutionException, IOException,
InstanceNotFoundException {
try { try {
this.mBeanServerConnection.invoke(this.objectName, "shutdown", null, null); this.connection.invoke(this.objectName, "shutdown", null, null);
} }
catch (ReflectionException e) { catch (ReflectionException ex) {
throw new MojoExecutionException("Shutdown failed", e.getCause()); throw new MojoExecutionException("Shutdown failed", ex.getCause());
} }
catch (MBeanException e) { catch (MBeanException ex) {
throw new MojoExecutionException("Could not invoke shutdown operation", e); throw new MojoExecutionException("Could not invoke shutdown operation", ex);
} }
} }
......
...@@ -22,6 +22,8 @@ import java.net.ConnectException; ...@@ -22,6 +22,8 @@ import java.net.ConnectException;
import java.net.URLClassLoader; import java.net.URLClassLoader;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.concurrent.Callable;
import javax.management.MBeanServerConnection; import javax.management.MBeanServerConnection;
import javax.management.ReflectionException; import javax.management.ReflectionException;
import javax.management.remote.JMXConnector; import javax.management.remote.JMXConnector;
...@@ -32,15 +34,14 @@ import org.apache.maven.plugins.annotations.LifecyclePhase; ...@@ -32,15 +34,14 @@ import org.apache.maven.plugins.annotations.LifecyclePhase;
import org.apache.maven.plugins.annotations.Mojo; import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter; import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.plugins.annotations.ResolutionScope; import org.apache.maven.plugins.annotations.ResolutionScope;
import org.springframework.boot.loader.tools.JavaExecutable; import org.springframework.boot.loader.tools.JavaExecutable;
import org.springframework.boot.loader.tools.RunProcess; import org.springframework.boot.loader.tools.RunProcess;
/** /**
* Start a spring application. Contrary to the {@code run} goal, this does not * Start a spring application. Contrary to the {@code run} goal, this does not block and
* block and allows other goal to operate on the application. This goal is typically * allows other goal to operate on the application. This goal is typically used in
* used in integration test scenario where the application is started before a test * integration test scenario where the application is started before a test suite and
* suite and stopped after. * stopped after.
* *
* @author Stephane Nicoll * @author Stephane Nicoll
* @since 1.3.0 * @since 1.3.0
...@@ -54,30 +55,30 @@ public class StartMojo extends AbstractRunMojo { ...@@ -54,30 +55,30 @@ public class StartMojo extends AbstractRunMojo {
private static final String JMX_NAME_PROPERTY_PREFIX = "--spring.context.lifecycle.jmx-name="; private static final String JMX_NAME_PROPERTY_PREFIX = "--spring.context.lifecycle.jmx-name=";
/** /**
* The JMX name of the automatically deployed MBean managing the lifecycle * The JMX name of the automatically deployed MBean managing the lifecycle of the
* of the spring application. * spring application.
*/ */
@Parameter @Parameter
private String jmxName = SpringApplicationLifecycleClient.DEFAULT_OBJECT_NAME; private String jmxName = SpringApplicationLifecycleClient.DEFAULT_OBJECT_NAME;
/** /**
* The port to use to expose the platform MBeanServer if the application * The port to use to expose the platform MBeanServer if the application needs to be
* needs to be forked. * forked.
*/ */
@Parameter @Parameter
private int jmxPort = 9001; private int jmxPort = 9001;
/** /**
* The number of milli-seconds to wait between each attempt to check if the * The number of milli-seconds to wait between each attempt to check if the spring
* spring application is ready. * application is ready.
*/ */
@Parameter @Parameter
private long wait = 500; private long wait = 500;
/** /**
* The maximum number of attempts to check if the spring application is * The maximum number of attempts to check if the spring application is ready.
* ready. Combined with the "wait" argument, this gives a global timeout * Combined with the "wait" argument, this gives a global timeout value (30 sec by
* value (30 sec by default) * default)
*/ */
@Parameter @Parameter
private int maxAttempts = 60; private int maxAttempts = 60;
...@@ -85,26 +86,30 @@ public class StartMojo extends AbstractRunMojo { ...@@ -85,26 +86,30 @@ public class StartMojo extends AbstractRunMojo {
private final Object lock = new Object(); private final Object lock = new Object();
@Override @Override
protected void runWithForkedJvm(List<String> args) throws MojoExecutionException, MojoFailureException { protected void runWithForkedJvm(List<String> args) throws MojoExecutionException,
RunProcess runProcess; MojoFailureException {
try { RunProcess runProcess = runProcess(args);
runProcess = new RunProcess(new JavaExecutable().toString());
runProcess.run(false, args.toArray(new String[args.size()]));
}
catch (Exception ex) {
throw new MojoExecutionException("Could not exec java", ex);
}
try { try {
waitForSpringApplication(); waitForSpringApplication();
} }
catch (MojoExecutionException e) { catch (MojoExecutionException ex) {
runProcess.kill(); runProcess.kill();
throw e; throw ex;
} }
catch (MojoFailureException e) { catch (MojoFailureException ex) {
runProcess.kill(); runProcess.kill();
throw e; throw ex;
}
}
private RunProcess runProcess(List<String> args) throws MojoExecutionException {
try {
RunProcess runProcess = new RunProcess(new JavaExecutable().toString());
runProcess.run(false, args.toArray(new String[args.size()]));
return runProcess;
}
catch (Exception ex) {
throw new MojoExecutionException("Could not exec java", ex);
} }
} }
...@@ -113,7 +118,8 @@ public class StartMojo extends AbstractRunMojo { ...@@ -113,7 +118,8 @@ public class StartMojo extends AbstractRunMojo {
RunArguments applicationArguments = super.resolveApplicationArguments(); RunArguments applicationArguments = super.resolveApplicationArguments();
applicationArguments.getArgs().addLast(ENABLE_MBEAN_PROPERTY); applicationArguments.getArgs().addLast(ENABLE_MBEAN_PROPERTY);
if (isFork()) { if (isFork()) {
applicationArguments.getArgs().addLast(JMX_NAME_PROPERTY_PREFIX + this.jmxName); applicationArguments.getArgs().addLast(
JMX_NAME_PROPERTY_PREFIX + this.jmxName);
} }
return applicationArguments; return applicationArguments;
} }
...@@ -124,7 +130,7 @@ public class StartMojo extends AbstractRunMojo { ...@@ -124,7 +130,7 @@ public class StartMojo extends AbstractRunMojo {
if (isFork()) { if (isFork()) {
List<String> remoteJmxArguments = new ArrayList<String>(); List<String> remoteJmxArguments = new ArrayList<String>();
remoteJmxArguments.add("-Dcom.sun.management.jmxremote"); remoteJmxArguments.add("-Dcom.sun.management.jmxremote");
remoteJmxArguments.add("-Dcom.sun.management.jmxremote.port=" + jmxPort); remoteJmxArguments.add("-Dcom.sun.management.jmxremote.port=" + this.jmxPort);
remoteJmxArguments.add("-Dcom.sun.management.jmxremote.authenticate=false"); remoteJmxArguments.add("-Dcom.sun.management.jmxremote.authenticate=false");
remoteJmxArguments.add("-Dcom.sun.management.jmxremote.ssl=false"); remoteJmxArguments.add("-Dcom.sun.management.jmxremote.ssl=false");
jvmArguments.getArgs().addAll(remoteJmxArguments); jvmArguments.getArgs().addAll(remoteJmxArguments);
...@@ -132,18 +138,19 @@ public class StartMojo extends AbstractRunMojo { ...@@ -132,18 +138,19 @@ public class StartMojo extends AbstractRunMojo {
return jvmArguments; return jvmArguments;
} }
@Override
protected void runWithMavenJvm(String startClassName, String... arguments) throws MojoExecutionException { protected void runWithMavenJvm(String startClassName, String... arguments)
throws MojoExecutionException {
IsolatedThreadGroup threadGroup = new IsolatedThreadGroup(startClassName); IsolatedThreadGroup threadGroup = new IsolatedThreadGroup(startClassName);
Thread launchThread = new Thread(threadGroup, new LaunchRunner(startClassName, Thread launchThread = new Thread(threadGroup, new LaunchRunner(startClassName,
arguments), startClassName + ".main()"); arguments), startClassName + ".main()");
launchThread.setContextClassLoader(new URLClassLoader(getClassPathUrls())); launchThread.setContextClassLoader(new URLClassLoader(getClassPathUrls()));
launchThread.start(); launchThread.start();
waitForSpringApplication(this.wait, this.maxAttempts); waitForSpringApplication(this.wait, this.maxAttempts);
} }
private void waitForSpringApplication(long wait, int maxAttempts) throws MojoExecutionException { private void waitForSpringApplication(long wait, int maxAttempts)
throws MojoExecutionException {
SpringApplicationLifecycleClient helper = new SpringApplicationLifecycleClient( SpringApplicationLifecycleClient helper = new SpringApplicationLifecycleClient(
ManagementFactory.getPlatformMBeanServer(), this.jmxName); ManagementFactory.getPlatformMBeanServer(), this.jmxName);
getLog().debug("Waiting for spring application to start..."); getLog().debug("Waiting for spring application to start...");
...@@ -151,21 +158,26 @@ public class StartMojo extends AbstractRunMojo { ...@@ -151,21 +158,26 @@ public class StartMojo extends AbstractRunMojo {
if (helper.isReady()) { if (helper.isReady()) {
return; return;
} }
getLog().debug("Spring application is not ready yet, waiting " + wait + "ms (attempt " + (i + 1) + ")"); String message = "Spring application is not ready yet, waiting " + wait
+ "ms (attempt " + (i + 1) + ")";
getLog().debug(message);
synchronized (this.lock) { synchronized (this.lock) {
try { try {
this.lock.wait(wait); this.lock.wait(wait);
} }
catch (InterruptedException e) { catch (InterruptedException ex) {
throw new IllegalStateException("Interrupted while waiting for Spring Boot app to start."); throw new IllegalStateException(
"Interrupted while waiting for Spring Boot app to start.");
} }
} }
} }
throw new MojoExecutionException("Spring application did not start before the configured " + throw new MojoExecutionException(
"timeout (" + (wait * maxAttempts) + "ms"); "Spring application did not start before the configured timeout ("
+ (wait * maxAttempts) + "ms");
} }
private void waitForSpringApplication() throws MojoFailureException, MojoExecutionException { private void waitForSpringApplication() throws MojoFailureException,
MojoExecutionException {
try { try {
if (Boolean.TRUE.equals(isFork())) { if (Boolean.TRUE.equals(isFork())) {
waitForForkedSpringApplication(); waitForForkedSpringApplication();
...@@ -174,114 +186,133 @@ public class StartMojo extends AbstractRunMojo { ...@@ -174,114 +186,133 @@ public class StartMojo extends AbstractRunMojo {
doWaitForSpringApplication(ManagementFactory.getPlatformMBeanServer()); doWaitForSpringApplication(ManagementFactory.getPlatformMBeanServer());
} }
} }
catch (IOException e) { catch (IOException ex) {
throw new MojoFailureException("Could not contact Spring Boot application", e); throw new MojoFailureException("Could not contact Spring Boot application",
ex);
} }
catch (Exception e) { catch (Exception ex) {
throw new MojoExecutionException("Could not figure out if the application has started", e); throw new MojoExecutionException(
"Could not figure out if the application has started", ex);
} }
} }
private void waitForForkedSpringApplication() throws IOException, MojoFailureException, MojoExecutionException { private void waitForForkedSpringApplication() throws IOException,
final JMXConnector jmxConnector; MojoFailureException, MojoExecutionException {
try { try {
getLog().debug("Connecting to local MBeanServer at port " + this.jmxPort); getLog().debug("Connecting to local MBeanServer at port " + this.jmxPort);
jmxConnector = execute(wait, maxAttempts, new RetryCallback<JMXConnector>() { JMXConnector connector = execute(this.wait, this.maxAttempts,
@Override new CreateJmxConnector(this.jmxPort));
public JMXConnector retry() throws Exception { if (connector == null) {
try { throw new MojoExecutionException(
return SpringApplicationLifecycleClient.createLocalJmxConnector(jmxPort); "JMX MBean server was not reachable before the configured "
} + "timeout (" + (this.wait * this.maxAttempts) + "ms");
catch (IOException e) {
if (hasCauseWithType(e, ConnectException.class)) { // Not there yet
getLog().debug("MBean server at port " + jmxPort + " is not up yet...");
return null;
}
else {
throw e;
}
}
}
});
if (jmxConnector == null) {
throw new MojoExecutionException("JMX MBean server was not reachable before the configured " +
"timeout (" + (this.wait * this.maxAttempts) + "ms");
} }
getLog().debug("Connected to local MBeanServer at port " + this.jmxPort); getLog().debug("Connected to local MBeanServer at port " + this.jmxPort);
try { try {
MBeanServerConnection mBeanServerConnection = jmxConnector.getMBeanServerConnection(); MBeanServerConnection connection = connector.getMBeanServerConnection();
doWaitForSpringApplication(mBeanServerConnection); doWaitForSpringApplication(connection);
} }
finally { finally {
jmxConnector.close(); connector.close();
} }
} }
catch (IOException e) { catch (IOException ex) {
throw e; throw ex;
} }
catch (Exception e) { catch (Exception ex) {
throw new MojoExecutionException("Failed to connect to MBean server at port " + this.jmxPort, e); throw new MojoExecutionException("Failed to connect to MBean server at port "
+ this.jmxPort, ex);
} }
} }
private void doWaitForSpringApplication(MBeanServerConnection connection) private void doWaitForSpringApplication(MBeanServerConnection connection)
throws IOException, MojoExecutionException, MojoFailureException { throws IOException, MojoExecutionException, MojoFailureException {
final SpringApplicationLifecycleClient client = new SpringApplicationLifecycleClient(
final SpringApplicationLifecycleClient client = connection, this.jmxName);
new SpringApplicationLifecycleClient(connection, this.jmxName);
try { try {
execute(this.wait, this.maxAttempts, new RetryCallback<Boolean>() { execute(this.wait, this.maxAttempts, new Callable<Boolean>() {
@Override @Override
public Boolean retry() throws Exception { public Boolean call() throws Exception {
boolean ready = client.isReady(); return (client.isReady() ? true : null);
// Wait until the app is ready
return (ready ? true : null);
} }
}); });
} }
catch (ReflectionException e) { catch (ReflectionException ex) {
throw new MojoExecutionException("Unable to retrieve Ready attribute", e.getCause()); throw new MojoExecutionException("Unable to retrieve 'ready' attribute",
ex.getCause());
} }
catch (Exception e) { catch (Exception ex) {
throw new MojoFailureException("Could not invoke shutdown operation", e); throw new MojoFailureException("Could not invoke shutdown operation", ex);
} }
} }
public <T> T execute(long wait, int maxAttempts, RetryCallback<T> callback) throws Exception { /**
* Execute a task, retrying it on failure.
* @param wait the wait time
* @param maxAttempts the maximum number of attempts
* @param callback the task to execute (possibly multiple times). The callback should
* return {@code null} to indicate that another attempt should be made
* @return the result
* @throws Exception
*/
public <T> T execute(long wait, int maxAttempts, Callable<T> callback)
throws Exception {
getLog().debug("Waiting for spring application to start..."); getLog().debug("Waiting for spring application to start...");
for (int i = 0; i < maxAttempts; i++) { for (int i = 0; i < maxAttempts; i++) {
T result = callback.retry(); T result = callback.call();
if (result != null) { if (result != null) {
return result; return result;
} }
getLog().debug("Spring application is not ready yet, waiting " + wait + "ms (attempt " + (i + 1) + ")"); String message = "Spring application is not ready yet, waiting " + wait
+ "ms (attempt " + (i + 1) + ")";
getLog().debug(message);
synchronized (this.lock) { synchronized (this.lock) {
try { try {
this.lock.wait(wait); this.lock.wait(wait);
} }
catch (InterruptedException e) { catch (InterruptedException ex) {
throw new IllegalStateException("Interrupted while waiting for Spring Boot app to start."); throw new IllegalStateException(
"Interrupted while waiting for Spring Boot app to start.");
} }
} }
} }
throw new MojoExecutionException("Spring application did not start before the configured " + throw new MojoExecutionException(
"timeout (" + (wait * maxAttempts) + "ms"); "Spring application did not start before the configured " + "timeout ("
+ (wait * maxAttempts) + "ms");
} }
private static boolean hasCauseWithType(Throwable t, Class<? extends Exception> type) { private class CreateJmxConnector implements Callable<JMXConnector> {
return type.isAssignableFrom(t.getClass()) || t.getCause() != null && hasCauseWithType(t.getCause(), type);
} private final int port;
public CreateJmxConnector(int port) {
this.port = port;
}
@Override
public JMXConnector call() throws Exception {
try {
return SpringApplicationLifecycleClient
.createLocalJmxConnector(this.port);
}
catch (IOException ex) {
if (hasCauseWithType(ex, ConnectException.class)) {
String message = "MBean server at port " + this.port
+ " is not up yet...";
getLog().debug(message);
return null;
}
throw ex;
}
}
interface RetryCallback<T> { private boolean hasCauseWithType(Throwable t, Class<? extends Exception> type) {
return type.isAssignableFrom(t.getClass()) || t.getCause() != null
&& hasCauseWithType(t.getCause(), type);
}
/**
* Attempt to execute an operation. Throws an exception in case of fatal
* exception, returns {@code null} to indicate another attempt should be
* made if possible.
*/
T retry() throws Exception;
} }
} }
...@@ -18,6 +18,7 @@ package org.springframework.boot.maven; ...@@ -18,6 +18,7 @@ package org.springframework.boot.maven;
import java.io.IOException; import java.io.IOException;
import java.lang.management.ManagementFactory; import java.lang.management.ManagementFactory;
import javax.management.InstanceNotFoundException; import javax.management.InstanceNotFoundException;
import javax.management.MBeanServerConnection; import javax.management.MBeanServerConnection;
import javax.management.remote.JMXConnector; import javax.management.remote.JMXConnector;
...@@ -40,23 +41,23 @@ import org.apache.maven.plugins.annotations.Parameter; ...@@ -40,23 +41,23 @@ import org.apache.maven.plugins.annotations.Parameter;
public class StopMojo extends AbstractMojo { public class StopMojo extends AbstractMojo {
/** /**
* Flag to indicate if the run processes should be forked. Must be aligned to the value * Flag to indicate if the run processes should be forked. Must be aligned to the
* used to {@link StartMojo start} the process * value used to {@link StartMojo start} the process
* @since 1.2 * @since 1.2
*/ */
@Parameter(property = "fork") @Parameter(property = "fork")
private Boolean fork; private Boolean fork;
/** /**
* The JMX name of the automatically deployed MBean managing the lifecycle * The JMX name of the automatically deployed MBean managing the lifecycle of the
* of the application. * application.
*/ */
@Parameter @Parameter
private String jmxName = SpringApplicationLifecycleClient.DEFAULT_OBJECT_NAME; private String jmxName = SpringApplicationLifecycleClient.DEFAULT_OBJECT_NAME;
/** /**
* The port to use to lookup the platform MBeanServer if the application * The port to use to lookup the platform MBeanServer if the application has been
* has been forked. * forked.
*/ */
@Parameter @Parameter
private int jmxPort = 9001; private int jmxPort = 9001;
...@@ -72,36 +73,38 @@ public class StopMojo extends AbstractMojo { ...@@ -72,36 +73,38 @@ public class StopMojo extends AbstractMojo {
stop(); stop();
} }
} }
catch (IOException e) { catch (IOException ex) {
// The response won't be received as the server has died - ignoring // The response won't be received as the server has died - ignoring
getLog().debug("Service is not reachable anymore (" + e.getMessage() + ")"); getLog().debug("Service is not reachable anymore (" + ex.getMessage() + ")");
} }
} }
private void stop() throws IOException, MojoFailureException, MojoExecutionException { private void stopForkedProcess() throws IOException, MojoFailureException,
doStop(ManagementFactory.getPlatformMBeanServer()); MojoExecutionException {
} JMXConnector connector = SpringApplicationLifecycleClient
.createLocalJmxConnector(this.jmxPort);
private void stopForkedProcess() throws IOException, MojoFailureException, MojoExecutionException {
JMXConnector jmxConnector = SpringApplicationLifecycleClient.createLocalJmxConnector(this.jmxPort);
try { try {
MBeanServerConnection mBeanServerConnection = jmxConnector.getMBeanServerConnection(); MBeanServerConnection connection = connector.getMBeanServerConnection();
doStop(mBeanServerConnection); doStop(connection);
} }
finally { finally {
jmxConnector.close(); connector.close();
} }
} }
private void doStop(MBeanServerConnection connection) private void stop() throws IOException, MojoFailureException, MojoExecutionException {
throws IOException, MojoExecutionException { doStop(ManagementFactory.getPlatformMBeanServer());
SpringApplicationLifecycleClient helper = new SpringApplicationLifecycleClient(connection, this.jmxName); }
private void doStop(MBeanServerConnection connection) throws IOException,
MojoExecutionException {
try { try {
helper.stop(); new SpringApplicationLifecycleClient(connection, this.jmxName).stop();
} }
catch (InstanceNotFoundException e) { catch (InstanceNotFoundException ex) {
throw new MojoExecutionException("Spring application lifecycle JMX bean not found (fork is " + throw new MojoExecutionException(
"" + this.fork + "). Could not stop application gracefully", e); "Spring application lifecycle JMX bean not found (fork is " + ""
+ this.fork + "). Could not stop application gracefully", ex);
} }
} }
......
...@@ -17,13 +17,13 @@ ...@@ -17,13 +17,13 @@
package org.springframework.boot.context; package org.springframework.boot.context;
import java.lang.management.ManagementFactory; import java.lang.management.ManagementFactory;
import javax.management.MBeanServer; import javax.management.MBeanServer;
import javax.management.MalformedObjectNameException; import javax.management.MalformedObjectNameException;
import javax.management.ObjectName; import javax.management.ObjectName;
import org.apache.commons.logging.Log; import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory; 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;
...@@ -52,50 +52,51 @@ public class SpringApplicationLifecycleRegistrar implements ApplicationContextAw ...@@ -52,50 +52,51 @@ public class SpringApplicationLifecycleRegistrar implements ApplicationContextAw
private boolean ready = false; private boolean ready = false;
public SpringApplicationLifecycleRegistrar(String name) throws MalformedObjectNameException { public SpringApplicationLifecycleRegistrar(String name)
throws MalformedObjectNameException {
this.objectName = new ObjectName(name); this.objectName = new ObjectName(name);
} }
@Override @Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { public void setApplicationContext(ApplicationContext applicationContext)
Assert.isTrue(applicationContext instanceof ConfigurableApplicationContext, throws BeansException {
Assert.state(applicationContext instanceof ConfigurableApplicationContext,
"ApplicationContext does not implement ConfigurableApplicationContext"); "ApplicationContext does not implement ConfigurableApplicationContext");
this.applicationContext = (ConfigurableApplicationContext) applicationContext; this.applicationContext = (ConfigurableApplicationContext) applicationContext;
} }
@Override @Override
public void onApplicationEvent(ApplicationReadyEvent event) { public void onApplicationEvent(ApplicationReadyEvent event) {
ready = true; this.ready = true;
} }
@Override @Override
public void afterPropertiesSet() throws Exception { public void afterPropertiesSet() throws Exception {
MBeanServer server = ManagementFactory.getPlatformMBeanServer(); MBeanServer server = ManagementFactory.getPlatformMBeanServer();
server.registerMBean(new SpringApplicationLifecycle(), objectName); server.registerMBean(new SpringApplicationLifecycle(), this.objectName);
if (logger.isDebugEnabled()) { if (logger.isDebugEnabled()) {
logger.debug("Application lifecycle MBean registered with name '" + objectName + "'"); logger.debug("Application lifecycle MBean registered with name '"
+ this.objectName + "'");
} }
} }
@Override @Override
public void destroy() throws Exception { public void destroy() throws Exception {
ManagementFactory.getPlatformMBeanServer().unregisterMBean(objectName); ManagementFactory.getPlatformMBeanServer().unregisterMBean(this.objectName);
} }
private class SpringApplicationLifecycle implements SpringApplicationLifecycleMXBean { private class SpringApplicationLifecycle implements SpringApplicationLifecycleMXBean {
@Override @Override
public boolean isReady() { public boolean isReady() {
return ready; return SpringApplicationLifecycleRegistrar.this.ready;
} }
@Override @Override
public void shutdown() { public void shutdown() {
logger.info("Application shutdown requested."); logger.info("Application shutdown requested.");
applicationContext.close(); SpringApplicationLifecycleRegistrar.this.applicationContext.close();
} }
} }
} }
...@@ -17,6 +17,7 @@ ...@@ -17,6 +17,7 @@
package org.springframework.boot.context; package org.springframework.boot.context;
import java.lang.management.ManagementFactory; import java.lang.management.ManagementFactory;
import javax.management.InstanceNotFoundException; import javax.management.InstanceNotFoundException;
import javax.management.MBeanServer; import javax.management.MBeanServer;
import javax.management.MalformedObjectNameException; import javax.management.MalformedObjectNameException;
...@@ -27,7 +28,6 @@ import org.junit.Before; ...@@ -27,7 +28,6 @@ import org.junit.Before;
import org.junit.Rule; 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.context.ApplicationListener; import org.springframework.context.ApplicationListener;
import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.ConfigurableApplicationContext;
...@@ -75,15 +75,18 @@ public class SpringApplicationLifecycleRegistrarTests { ...@@ -75,15 +75,18 @@ public class SpringApplicationLifecycleRegistrarTests {
@Override @Override
public void onApplicationEvent(ContextRefreshedEvent event) { public void onApplicationEvent(ContextRefreshedEvent event) {
try { try {
assertFalse("Application should not be ready yet", isCurrentApplicationReady(objectName)); assertFalse("Application should not be ready yet",
isCurrentApplicationReady(objectName));
} }
catch (Exception e) { catch (Exception ex) {
throw new IllegalStateException("Could not contact spring application lifecycle bean", e); throw new IllegalStateException(
"Could not contact spring application lifecycle bean", ex);
} }
} }
}); });
this.context = application.run(); this.context = application.run();
assertTrue("application should be ready now", isCurrentApplicationReady(objectName)); assertTrue("application should be ready now",
isCurrentApplicationReady(objectName));
} }
@Test @Test
...@@ -95,8 +98,7 @@ public class SpringApplicationLifecycleRegistrarTests { ...@@ -95,8 +98,7 @@ public class SpringApplicationLifecycleRegistrarTests {
assertTrue("application should be running", this.context.isRunning()); assertTrue("application should be running", this.context.isRunning());
invokeShutdown(objectName); invokeShutdown(objectName);
assertFalse("application should not be running", this.context.isRunning()); assertFalse("application should not be running", this.context.isRunning());
this.thrown.expect(InstanceNotFoundException.class); // JMX cleanup
thrown.expect(InstanceNotFoundException.class); // JMX cleanup
this.mBeanServer.getObjectInstance(objectName); this.mBeanServer.getObjectInstance(objectName);
} }
...@@ -127,16 +129,15 @@ public class SpringApplicationLifecycleRegistrarTests { ...@@ -127,16 +129,15 @@ public class SpringApplicationLifecycleRegistrarTests {
} }
} }
@Configuration @Configuration
static class Config { static class Config {
@Bean @Bean
public SpringApplicationLifecycleRegistrar springApplicationLifecycle() public SpringApplicationLifecycleRegistrar springApplicationLifecycle()
throws MalformedObjectNameException { throws MalformedObjectNameException {
return new SpringApplicationLifecycleRegistrar(OBJECT_NAME); return new SpringApplicationLifecycleRegistrar(OBJECT_NAME);
} }
} }
} }
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