Home / Class/ StartMojo Class — spring-boot Architecture

StartMojo Class — spring-boot Architecture

Architecture documentation for the StartMojo class in StartMojo.java from the spring-boot codebase.

Entity Profile

Source Code

build-plugin/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/StartMojo.java lines 53–241

@Mojo(name = "start", requiresProject = true, defaultPhase = LifecyclePhase.PRE_INTEGRATION_TEST,
		requiresDependencyResolution = ResolutionScope.TEST)
public class StartMojo extends AbstractRunMojo {

	private static final String ENABLE_MBEAN_PROPERTY = "--spring.application.admin.enabled=true";

	private static final String JMX_NAME_PROPERTY_PREFIX = "--spring.application.admin.jmx-name=";

	/**
	 * The JMX name of the automatically deployed MBean managing the lifecycle of the
	 * spring application.
	 */
	@Parameter(defaultValue = SpringApplicationAdminClient.DEFAULT_OBJECT_NAME)
	@SuppressWarnings("NullAway.Init")
	private String jmxName;

	/**
	 * The port to use to expose the platform MBeanServer.
	 */
	@Parameter(defaultValue = "9001")
	private int jmxPort;

	/**
	 * The number of milliseconds to wait between each attempt to check if the spring
	 * application is ready.
	 */
	@Parameter(property = "spring-boot.start.wait", defaultValue = "500")
	private long wait;

	/**
	 * The maximum number of attempts to check if the spring application is ready.
	 * Combined with the "wait" argument, this gives a global timeout value (30 sec by
	 * default)
	 */
	@Parameter(property = "spring-boot.start.maxAttempts", defaultValue = "60")
	private int maxAttempts;

	private final Object lock = new Object();

	/**
	 * Flag to include the test classpath when running.
	 */
	@Parameter(property = "spring-boot.run.useTestClasspath", defaultValue = "false")
	private boolean useTestClasspath;

	@Inject
	public StartMojo(ToolchainManager toolchainManager) {
		super(toolchainManager);
	}

	@Override
	protected void run(JavaProcessExecutor processExecutor, File workingDirectory, List<String> args,
			Map<String, String> environmentVariables) throws MojoExecutionException, MojoFailureException {
		RunProcess runProcess = processExecutor.runAsync(workingDirectory, args, environmentVariables);
		try {
			waitForSpringApplication();
		}
		catch (MojoExecutionException | MojoFailureException ex) {
			runProcess.kill();
			throw ex;
		}
	}

	@Override
	protected RunArguments resolveApplicationArguments() {
		RunArguments applicationArguments = super.resolveApplicationArguments();
		applicationArguments.getArgs().addLast(ENABLE_MBEAN_PROPERTY);
		applicationArguments.getArgs().addLast(JMX_NAME_PROPERTY_PREFIX + this.jmxName);
		return applicationArguments;
	}

	@Override
	protected RunArguments resolveJvmArguments() {
		RunArguments jvmArguments = super.resolveJvmArguments();
		List<String> remoteJmxArguments = new ArrayList<>();
		remoteJmxArguments.add("-Dcom.sun.management.jmxremote");
		remoteJmxArguments.add("-Dcom.sun.management.jmxremote.port=" + this.jmxPort);
		remoteJmxArguments.add("-Dcom.sun.management.jmxremote.authenticate=false");
		remoteJmxArguments.add("-Dcom.sun.management.jmxremote.ssl=false");
		remoteJmxArguments.add("-Djava.rmi.server.hostname=127.0.0.1");
		jvmArguments.getArgs().addAll(remoteJmxArguments);
		return jvmArguments;
	}

	private void waitForSpringApplication() throws MojoFailureException, MojoExecutionException {
		try {
			getLog().debug("Connecting to local MBeanServer at port " + this.jmxPort);
			try (JMXConnector connector = execute(this.wait, this.maxAttempts, new CreateJmxConnector(this.jmxPort))) {
				if (connector == 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);
				MBeanServerConnection connection = connector.getMBeanServerConnection();
				doWaitForSpringApplication(connection);
			}
		}
		catch (IOException ex) {
			throw new MojoFailureException("Could not contact Spring Boot application via JMX on port " + this.jmxPort
					+ ". Please make sure that no other process is using that port", ex);
		}
		catch (Exception ex) {
			throw new MojoExecutionException("Failed to connect to MBean server at port " + this.jmxPort, ex);
		}
	}

	private void doWaitForSpringApplication(MBeanServerConnection connection)
			throws MojoExecutionException, MojoFailureException {
		final SpringApplicationAdminClient client = new SpringApplicationAdminClient(connection, this.jmxName);
		try {
			Callable<@Nullable Boolean> isReady = () -> (client.isReady() ? true : null);
			execute(this.wait, this.maxAttempts, isReady);
		}
		catch (ReflectionException ex) {
			throw new MojoExecutionException("Unable to retrieve 'ready' attribute", ex.getCause());
		}
		catch (Exception ex) {
			throw new MojoFailureException("Could not invoke shutdown operation", ex);
		}
	}

	/**
	 * Execute a task, retrying it on failure.
	 * @param <T> the result type
	 * @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 in case of execution errors
	 */
	public <T> T execute(long wait, int maxAttempts, Callable<@Nullable T> callback) throws Exception {
		getLog().debug("Waiting for spring application to start...");
		for (int i = 0; i < maxAttempts; i++) {
			T result = callback.call();
			if (result != null) {
				return result;
			}
			String message = "Spring application is not ready yet, waiting " + wait + "ms (attempt " + (i + 1) + ")";
			getLog().debug(message);
			synchronized (this.lock) {
				try {
					this.lock.wait(wait);
				}
				catch (InterruptedException ex) {
					Thread.currentThread().interrupt();
					throw new IllegalStateException("Interrupted while waiting for Spring Boot app to start.");
				}
			}
		}
		throw new MojoExecutionException(
				"Spring application did not start before the configured timeout (" + (wait * maxAttempts) + "ms");
	}

	@Override
	protected boolean isUseTestClasspath() {
		return this.useTestClasspath;
	}

	private class CreateJmxConnector implements Callable<@Nullable JMXConnector> {

		private final int port;

		CreateJmxConnector(int port) {
			this.port = port;
		}

		@Override
		public @Nullable JMXConnector call() throws Exception {
			try {
				return SpringApplicationAdminClient.connect(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;
			}
		}

		private boolean hasCauseWithType(Throwable t, Class<? extends Exception> type) {
			return type.isAssignableFrom(t.getClass()) || t.getCause() != null && hasCauseWithType(t.getCause(), type);
		}

	}

}

Analyze Your Own Codebase

Get architecture documentation, dependency graphs, and domain analysis for your codebase in minutes.

Try Supermodel Free