CTRL + C w / Spring Boot&Gradle Kills Gradle Daemon

我使用Spring Boot Gradle插件启动Tomcat服务器和我的应用程序。 我通过gradle bootRun启动Tomcat服务器。 我还启用了Gradle守护进程,希望Gradle构建更快。

但是,启用守护进程是徒劳的。 每次我通过Ctrl + C停止服务器,然后使用gradle bootRun再次启动服务器,我会遇到以下消息:

 Starting a new Gradle Daemon for this build (subsequent builds will be faster). 

Ctrl + C不仅会停止Spring Boot的Tomcat服务器,还会杀死Gradle守护程序。 这违背了Gradle的守护进程模式的目的。

有没有更好的方法我应该停止服务器,希望通过同一个终端中的命令行界面我使用gradle bootRun启动tomcat,这使Gradle守护程序保持活动状态?

这仍然是Gradle 4中的一个问题。我最好的妥协/解决方案(建立charlie_pl的答案):

  1. ctrl+z将正在运行的进程发送到后台。
  2. 杀死这个过程: kill $(ps aux | grep "MyApp" | grep -v grep | awk '{print $2}')
  3. 重启: ./gradlew run ...

我不熟悉Spring Boot插件,所以可能没有’bootStop’命令(就像Jetty插件中那样)。 此外,经过广泛搜索后,我认为没有一个命令行选项可用于所需的结果。

一种选择,虽然无可否认是kludge,但是使用Tooling API。 (完整的代码示例在这里 。)

我们的想法是在Groovy脚本中启动长时间运行的任务。 在命令中,脚本将停止任务并调用gradle tasks以使守护程序发痒。

从上面链接的GitHub代码,一个长期运行的任务可能是:

 task runService() << { ant.delete(file: "runService.log") def count = 0 while(true) { new File("runService.log").withWriterAppend { it.writeLine("[runService] count: ${count}") } println "sleeping ...." try { Thread.sleep(5 * 1000) } catch (Exception ex) {} count++ } } 

Groovy脚本背后的想法是在后台线程中启动任务,然后在收到命令后发送取消令牌。

为清楚起见,我将说明两个部分。 第一部分是后台线程:

 class BuildRunner implements Runnable { def connector def tokenSource def taskName BuildRunner(connector, tokenSource, taskName) { this.connector = connector this.tokenSource = tokenSource this.taskName = taskName } public void run() { def connection = connector.connect() try { def build = connection.newBuild() build.withCancellationToken(tokenSource.token()) build.setStandardOutput(System.out) build.setStandardError(System.err) build.forTasks(taskName) build.run() println "${taskName} is finishing ..." } catch(BuildCancelledException bcex) { println "received cancel signal" println "tickling daemon ..." tickleDaemon(connector) println "Done." System.exit(0) } catch(Exception ex) { println "caught exception : " + ex } finally { connection.close() } } def tickleDaemon = { connector -> final String TASKS = "tasks" def connection = connector.connect() def build = connection.newBuild() build.forTasks(TASKS) build.run() } } 

另一部分是主控制台:

 // main ----------- // edit as appropriate final String TASK_NAME = "runService" final String GRADLE_INSTALL_DIR = "/Users/measter/tools/gradle-2.14.1" final String PROJECT_DIR = "../service" def connector = GradleConnector.newConnector() connector.useInstallation(new File(GRADLE_INSTALL_DIR)) connector.forProjectDirectory(new File(PROJECT_DIR)) def tokenSource = connector.newCancellationTokenSource() println "starting ${TASK_NAME}" def buildRunner = new BuildRunner(connector, tokenSource, TASK_NAME) new Thread(buildRunner).start() def console = new Scanner(System.in) println "Enter a command (S: stop task, Q: quit): " while (console.hasNextLine()) { def lineTokenizer = new Scanner(console.nextLine()) String token = lineTokenizer.next() if (token.equalsIgnoreCase("S")) { tokenSource.cancel() } else if (token.equalsIgnoreCase("Q")) { println "Done." System.exit(0) } } 

可以轻松地自定义此代码以执行其他任务,重新启动任务等。它提示了一个围绕个人命令行使用的美化包装。

以下是核心开发人员解释为什么Ctrl + C会杀死守护进程。

它总是以“按设计”的方式进行,但我们希望远离它,这样守护进程就不会经常被杀死。 我认为有些情况下我们不会传播ctrl + c,但那是运气。

如果你看看我们在2.5中为连续模式做了什么,我们就是在不杀死守护进程的情况下添加ctrl + d来退出Gradle进程。 我们的PlayRun与Play应用程序支持(playRun)有类似的问题,它使用相同的机制(ctrl + d)。 我想我们最终会做这样的事情,但是我们需要为现有的构建脚本提供一种替代方法,以便在我们一直捕获输入之前读取stdin。

– Sterling Greene (Gradle Core Dev)

bootRunspring-boot-gradle-plugin一个便利function。 它允许您在一个命令中执行两个步骤,并且它具有在过程中不生成.jar文件的非常小的好处。 它还有潜在的主要好处……

如果已将devtools添加到项目中,它将自动监视应用程序的更改。

如果您没有使用bootRun的实时更新function, bootRun 可以通过将构建/运行序列作为两个命令执行来解决此问题 ,如“运行应用程序”中所述 。 因为第二个命令不涉及Gradle,所以现在可以Ctrl-C服务器,而不会使Gradle处于CANCELED状态。

以下是应用此类方法的示例:

 gradle build && java -jar build/libs/myproject-0.0.1-SNAPSHOT.jar 

另一方面,如果您使用devtools ,则可能不需要经常手动重启服务器 – 只需重建并且服务器将自行重启(对于Groovy,您只需要更新源文件)。

使用spring-boot-devtools的应用程序将在类路径上的文件发生更改时自动重新启动。

 ./gradlew build -x test 

…如果您使用的是IDE(例如’vscode’),它可能会自动编译您的java文件,因此只需保存一个java文件即可间接启动服务器重启。 然后Java在这方面变得和Groovy一样无缝

我有同样的问题。 我开始使用dropwizard应用程序,并且杀死一个守护进程可能会增加重启应用程序的时间。

简单的解决方案:最后我只是搜索了dropwizard进程,并在命令行中杀死了它(简单的kill&ps aux&grep组合)。 这会关闭应用程序并使构建失败,但会保留守护程序。