Merge pull request #1483 from liujiong1982/spring-boot-1412
* spring-boot-1412: Add fork option for mvn spring-boot:run Closes gh-1483
This commit is contained in:
commit
bec7b5f118
|
|
@ -0,0 +1,31 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
<groupId>org.springframework.boot.maven.it</groupId>
|
||||||
|
<artifactId>run-fork</artifactId>
|
||||||
|
<version>0.0.1.BUILD-SNAPSHOT</version>
|
||||||
|
<properties>
|
||||||
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
|
</properties>
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>@project.groupId@</groupId>
|
||||||
|
<artifactId>@project.artifactId@</artifactId>
|
||||||
|
<version>@project.version@</version>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<phase>package</phase>
|
||||||
|
<goals>
|
||||||
|
<goal>run</goal>
|
||||||
|
</goals>
|
||||||
|
<configuration>
|
||||||
|
<fork>true</fork>
|
||||||
|
</configuration>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
</project>
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
package org.test;
|
||||||
|
|
||||||
|
public class SampleApplication {
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
System.out.println("I haz been run");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
def file = new File(basedir, "build.log")
|
||||||
|
return file.text.contains("I haz been run")
|
||||||
|
|
||||||
|
|
@ -18,8 +18,10 @@ package org.springframework.boot.maven;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
import java.net.MalformedURLException;
|
import java.net.MalformedURLException;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
|
import java.net.URLClassLoader;
|
||||||
import java.security.CodeSource;
|
import java.security.CodeSource;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
|
@ -49,6 +51,7 @@ import org.springframework.boot.loader.tools.RunProcess;
|
||||||
*
|
*
|
||||||
* @author Phillip Webb
|
* @author Phillip Webb
|
||||||
* @author Stephane Nicoll
|
* @author Stephane Nicoll
|
||||||
|
* @author David Liu
|
||||||
*/
|
*/
|
||||||
@Mojo(name = "run", requiresProject = true, defaultPhase = LifecyclePhase.VALIDATE, requiresDependencyResolution = ResolutionScope.TEST)
|
@Mojo(name = "run", requiresProject = true, defaultPhase = LifecyclePhase.VALIDATE, requiresDependencyResolution = ResolutionScope.TEST)
|
||||||
@Execute(phase = LifecyclePhase.TEST_COMPILE)
|
@Execute(phase = LifecyclePhase.TEST_COMPILE)
|
||||||
|
|
@ -73,7 +76,8 @@ public class RunMojo extends AbstractDependencyFilterMojo {
|
||||||
private boolean addResources;
|
private boolean addResources;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Path to agent jar.
|
* Path to agent jar. NOTE: the use of agents means that processes will be started by
|
||||||
|
* forking a new JVM.
|
||||||
* @since 1.0
|
* @since 1.0
|
||||||
*/
|
*/
|
||||||
@Parameter(property = "run.agent")
|
@Parameter(property = "run.agent")
|
||||||
|
|
@ -126,6 +130,14 @@ public class RunMojo extends AbstractDependencyFilterMojo {
|
||||||
@Parameter(defaultValue = "${project.build.outputDirectory}", required = true)
|
@Parameter(defaultValue = "${project.build.outputDirectory}", required = true)
|
||||||
private File classesDirectory;
|
private File classesDirectory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Flag to indicate if the run processes should be forked. By default process forking
|
||||||
|
* is only used if an agent or jvmArguments are specified.
|
||||||
|
* @since 1.2
|
||||||
|
*/
|
||||||
|
@Parameter(property = "fork", defaultValue = "false")
|
||||||
|
private boolean fork;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void execute() throws MojoExecutionException, MojoFailureException {
|
public void execute() throws MojoExecutionException, MojoFailureException {
|
||||||
final String startClassName = getStartClass();
|
final String startClassName = getStartClass();
|
||||||
|
|
@ -156,6 +168,16 @@ public class RunMojo extends AbstractDependencyFilterMojo {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void run(String startClassName) throws MojoExecutionException {
|
private void run(String startClassName) throws MojoExecutionException {
|
||||||
|
if (this.fork || (this.agent != null && this.agent.length > 0)
|
||||||
|
|| (this.jvmArguments != null && this.jvmArguments.length() > 0)) {
|
||||||
|
runWithForkedJvm(startClassName);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
runWithMavenJvm(startClassName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void runWithForkedJvm(String startClassName) throws MojoExecutionException {
|
||||||
List<String> args = new ArrayList<String>();
|
List<String> args = new ArrayList<String>();
|
||||||
addAgents(args);
|
addAgents(args);
|
||||||
addJvmArgs(args);
|
addJvmArgs(args);
|
||||||
|
|
@ -166,11 +188,21 @@ public class RunMojo extends AbstractDependencyFilterMojo {
|
||||||
new RunProcess(new JavaExecutable().toString()).run(args
|
new RunProcess(new JavaExecutable().toString()).run(args
|
||||||
.toArray(new String[args.size()]));
|
.toArray(new String[args.size()]));
|
||||||
}
|
}
|
||||||
catch (Exception e) {
|
catch (Exception ex) {
|
||||||
throw new MojoExecutionException("Could not exec java", e);
|
throw new MojoExecutionException("Could not exec java", ex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void runWithMavenJvm(String startClassName) throws MojoExecutionException {
|
||||||
|
IsolatedThreadGroup threadGroup = new IsolatedThreadGroup(startClassName);
|
||||||
|
Thread launchThread = new Thread(threadGroup, new LaunchRunner(startClassName,
|
||||||
|
this.arguments), startClassName + ".main()");
|
||||||
|
launchThread.setContextClassLoader(new URLClassLoader(getClassPathUrls()));
|
||||||
|
launchThread.start();
|
||||||
|
join(threadGroup);
|
||||||
|
threadGroup.rethrowUncaughtException();
|
||||||
|
}
|
||||||
|
|
||||||
private void addAgents(List<String> args) {
|
private void addAgents(List<String> args) {
|
||||||
findAgent();
|
findAgent();
|
||||||
if (this.agent != null) {
|
if (this.agent != null) {
|
||||||
|
|
@ -287,6 +319,27 @@ public class RunMojo extends AbstractDependencyFilterMojo {
|
||||||
getLog().debug(sb.toString().trim());
|
getLog().debug(sb.toString().trim());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void join(ThreadGroup threadGroup) {
|
||||||
|
boolean hasNonDaemonThreads;
|
||||||
|
do {
|
||||||
|
hasNonDaemonThreads = false;
|
||||||
|
Thread[] threads = new Thread[threadGroup.activeCount()];
|
||||||
|
threadGroup.enumerate(threads);
|
||||||
|
for (Thread thread : threads) {
|
||||||
|
if (thread != null && !thread.isDaemon()) {
|
||||||
|
try {
|
||||||
|
hasNonDaemonThreads = true;
|
||||||
|
thread.join();
|
||||||
|
}
|
||||||
|
catch (InterruptedException ex) {
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
while (hasNonDaemonThreads);
|
||||||
|
}
|
||||||
|
|
||||||
private static class TestArtifactFilter extends AbstractArtifactFeatureFilter {
|
private static class TestArtifactFilter extends AbstractArtifactFeatureFilter {
|
||||||
public TestArtifactFilter() {
|
public TestArtifactFilter() {
|
||||||
super("", Artifact.SCOPE_TEST);
|
super("", Artifact.SCOPE_TEST);
|
||||||
|
|
@ -298,4 +351,73 @@ public class RunMojo extends AbstractDependencyFilterMojo {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Isolated {@link ThreadGroup} to capture uncaught exceptions.
|
||||||
|
*/
|
||||||
|
class IsolatedThreadGroup extends ThreadGroup {
|
||||||
|
|
||||||
|
private Throwable exception;
|
||||||
|
|
||||||
|
public IsolatedThreadGroup(String name) {
|
||||||
|
super(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void uncaughtException(Thread thread, Throwable ex) {
|
||||||
|
if (!(ex instanceof ThreadDeath)) {
|
||||||
|
synchronized (this) {
|
||||||
|
this.exception = (this.exception == null ? ex : this.exception);
|
||||||
|
}
|
||||||
|
getLog().warn(ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized void rethrowUncaughtException() throws MojoExecutionException {
|
||||||
|
if (this.exception != null) {
|
||||||
|
throw new MojoExecutionException("An exception occured while running. "
|
||||||
|
+ this.exception.getMessage(), this.exception);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Runner used to launch the application.
|
||||||
|
*/
|
||||||
|
class LaunchRunner implements Runnable {
|
||||||
|
|
||||||
|
private final String startClassName;
|
||||||
|
private final String[] args;
|
||||||
|
|
||||||
|
public LaunchRunner(String startClassName, String... args) {
|
||||||
|
this.startClassName = startClassName;
|
||||||
|
this.args = (args != null ? args : new String[] {});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
Thread thread = Thread.currentThread();
|
||||||
|
ClassLoader classLoader = thread.getContextClassLoader();
|
||||||
|
try {
|
||||||
|
Class<?> startClass = classLoader.loadClass(this.startClassName);
|
||||||
|
Method mainMethod = startClass.getMethod("main",
|
||||||
|
new Class[] { String[].class });
|
||||||
|
if (!mainMethod.isAccessible()) {
|
||||||
|
mainMethod.setAccessible(true);
|
||||||
|
}
|
||||||
|
mainMethod.invoke(null, new Object[] { this.args });
|
||||||
|
}
|
||||||
|
catch (NoSuchMethodException ex) {
|
||||||
|
Exception wrappedEx = new Exception(
|
||||||
|
"The specified mainClass doesn't contain a "
|
||||||
|
+ "main method with appropriate signature.", ex);
|
||||||
|
thread.getThreadGroup().uncaughtException(thread, wrappedEx);
|
||||||
|
}
|
||||||
|
catch (Exception ex) {
|
||||||
|
thread.getThreadGroup().uncaughtException(thread, ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -99,9 +99,13 @@ Usage
|
||||||
mvn spring-boot:run
|
mvn spring-boot:run
|
||||||
---
|
---
|
||||||
|
|
||||||
The application is forked in a separate process. If you need to specify some JVM arguments
|
By default the application is executed directly from the Maven JVM. If you need to run
|
||||||
(i.e. for debugging purposes), you can use the <<<jvmArguments>>> parameter, see
|
in a forked process you can use the 'fork' option. Forking will also occur if the
|
||||||
{{{./examples/run-debug.html}Debug the application}} for more details.
|
'jvmArguments' or 'agent' options are specified.
|
||||||
|
|
||||||
|
If you need to specify some JVM arguments (i.e. for debugging purposes), you can use
|
||||||
|
the <<<jvmArguments>>> parameter, see {{{./examples/run-debug.html}Debug the application}}
|
||||||
|
for more details.
|
||||||
|
|
||||||
By default, any <<src/main/resources>> folder will be added to the application classpath
|
By default, any <<src/main/resources>> folder will be added to the application classpath
|
||||||
when you run the application and any duplicate found in <<target/classes>> will be
|
when you run the application and any duplicate found in <<target/classes>> will be
|
||||||
|
|
@ -135,4 +139,4 @@ mvn spring-boot:run
|
||||||
In order to be consistent with the <<<repackage>>> goal, the <<<run>>> goal builds the classpath
|
In order to be consistent with the <<<repackage>>> goal, the <<<run>>> goal builds the classpath
|
||||||
in such a way that any dependency that is excluded in the plugin's configuration gets excluded
|
in such a way that any dependency that is excluded in the plugin's configuration gets excluded
|
||||||
from the classpath as well. See {{{./examples/exclude-dependency.html}Exclude a dependency}} for
|
from the classpath as well. See {{{./examples/exclude-dependency.html}Exclude a dependency}} for
|
||||||
more details.
|
more details.
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue