spring-boot-devtools 自动重启不起作用

Posted

技术标签:

【中文标题】spring-boot-devtools 自动重启不起作用【英文标题】:spring-boot-devtools Automatic Restart not working 【发布时间】:2021-07-15 12:59:36 【问题描述】:

我有一个使用 mvn 构建的 Spring Boot 2.25 应用程序。根据this documentation我添加

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-devtools</artifactId>
        <optional>true</optional>
    </dependency>
</dependencies>

来自文档:

由于 DevTools 监控类路径资源,触发重启的唯一方法是更新类路径。更新类路径的方式取决于您使用的 IDE。在 Eclipse 中,保存修改后的文件会导致类路径更新并触发重新启动。在 IntelliJ IDEA 中,构建项目(Build -> Build Project)具有相同的效果。

随着应用程序的运行,我尝试了一个简单的

touch /path/to/app.jar

期待应用程序重新启动,但什么也没发生。

好吧,也许它正在做一些更聪明的事情。我修改了一些源 .java,重新编译了 .jar,然后cp'd 将它替换为正在运行的 .jar 文件,然后……什么也没发生。

也来自文档

DevTools 依赖应用程序上下文的关闭挂钩在重新启动期间将其关闭。如果您禁用了关闭挂钩 (SpringApplication.setRegisterShutdownHook(false)),它将无法正常工作。

我不这样做。

DevTools 需要自定义 ApplicationContext 使用的 ResourceLoader。如果您的应用程序已经提供了一个,它将被包装。不支持直接覆盖 ApplicationContext 上的 getResource 方法。

我不这样做。

如果这很重要,我会在 Docker 容器中运行它。来自文档:

运行完全打包的应用程序时,开发人员工具会自动禁用。如果您的应用程序是从 java -jar 启动的,或者它是从一个特殊的类加载器启动的,那么它被认为是“生产应用程序”。如果这不适用于您(即,如果您从容器运行应用程序),请考虑排除 devtools 或设置 -Dspring.devtools.restart.enabled=false 系统属性。

我不明白这是什么意思或是否相关。

我想重新编译一个 .jar 并在正在运行的 docker 容器中替换它,并在不重新启动容器的情况下触发和应用程序重新启动。我该怎么做?

编辑:我正在使用 mvn 重建 jar,然后使用 docker cp 在正在运行的容器中替换它。 (IntelliJ IDEA 声称要重建项目,但实际上 没有 触及 jar 文件,但这是另一回事。)我正在寻找非 IDE 特定的解决方案。

【问题讨论】:

项目菜单中的Build Automatically选项是否启用。 我认为你对 java 和 docker 的要求太多了。为此目的构建了许多持续部署工具。该名称指定用于开发环境而非生产的“开发”工具。您的解决方案是 CI/CD,它可以自动执行所有这些步骤。此外,如果您想自动执行此操作,您可以从 shell 脚本调用您的 git webhook,并在推送代码后构建 mvn,然后构建新的 docker 映像并运行您的映像。 【参考方案1】:

Spring Boot Devtools 为 Spring Boot 应用程序提供通常在 IntelliJ 等 IDE 中可用的功能,例如,当某些类或资源发生更改时,您可以在其中使用 restart an application 或强制 live browser reload。这在应用程序的开发阶段非常有用。

它通常与 IDE 结合使用,这样当在类路径中检测到它并且未禁用时,它将由 Spring Boot 与应用程序的其余部分一起启动。

虽然您可以将其配置为监控更多资源,但它通常会在您的应用程序代码、类和资源中查找更改。

重要的是要说,AFAIK,Devtools 将以 exploded 的方式监控您自己的类和资源,我的意思是,如果您覆盖整个应用程序 jar,重启过程将不起作用,只有如果你覆盖了classes 目录中的一些资源。

可以使用 Maven 测试此功能。请考虑从Spring Initializr 下载一个简单的蓝图,例如使用 Spring Boot、Spring Boot Devtools 和 Spring Web - 以保持应用程序运行。从终端,在包含pom.xml 文件的目录中,运行您的应用程序,例如,借助pom.xml 中包含的spring-boot-maven-plugin 插件:

mvn spring-boot:run

该命令将下载项目依赖项,编译并运行您的应用程序。

现在,在您的源代码中执行任何修改,无论是在您的类中还是在您的资源中,并从另一个终端在同一目录中重新编译您的资源:

mvn compile

如果您查看第一个终端窗口,您会看到应用程序已重新启动以反映更改。

如果您使用 docker 进行应用程序部署,尝试重现此行为可能会很棘手。

一方面,我不知道这是否有意义,但您可以尝试创建一个基于 maven 的图像并在其中运行您的代码,如上所述。您的Dockerfile 可能与此类似:

FROM maven:3.5-jdk-8 as maven

WORKDIR /app

# Copy project pom
COPY ./pom.xml ./pom.xml

# Fetch (and cache) dependencies
RUN mvn dependency:go-offline -B

# Copy source files
COPY ./src ./src

# Run your application
RUN mvn springboot:run

通过此设置,您可以使用docker cp 将您的资源复制到/app/target 目录,这将触发应用程序重新启动。作为替代方案,请考虑在容器中挂载一个卷,而不是使用 docker cp

好多了,并且考虑到覆盖您的应用程序 jar 可能不起作用的事实,您可以尝试复制您的类和库依赖项,并以爆炸的方式运行您的应用程序。考虑以下Dockerfile

FROM maven:3.5-jdk-8 as maven

WORKDIR /app

# Copy your project pom
COPY ./pom.xml ./pom.xml

# Fetch (and cache) dependencies
RUN mvn dependency:go-offline -B

# Copy source files
COPY ./src ./src

# Compile application and library dependencies
# The dependencies will, by default, be copied to target/dependency
RUN mvn clean compile dependency:copy-dependencies -Dspring-boot.repackage.skip=true

# Final run image (based on https://***.com/questions/53691781/how-to-cache-maven-dependencies-in-docker)
FROM openjdk:8u171-jre-alpine
# OPTIONAL: copy dependencies so the thin jar won't need to re-download them
# COPY --from=maven /root/.m2 /root/.m2

# Change working directory
WORKDIR /app

# Copy classes from maven image
COPY --from=maven /app/target/classes ./classes

# Copy dependent libraries
COPY --from=maven /app/target/dependency ./lib

EXPOSE 8080

# Please, modify your main class name as appropriate
ENTRYPOINT ["java", "-cp", "/app/classes:/app/lib/*", "com.example.demo.DemoApplication"]

Dockerfile 中重要的一行是这样的:

mvn clean compile dependency:copy-dependencies -Dspring-boot.repackage.skip=true

它将指示 maven 编译您的资源并复制所需的库。虽然对于运行 spring-boot-maven-plugin repackage 目标的典型 Maven 阶段是多余的,但标志 spring-boot.repackage.skip=true 将指示此插件不要重新打包应用程序。

使用这个Dockerfile,构建你的图像(例如,让我们标记它devtools-demo):

docker build -t devtools-demo .

然后运行它:

docker run devtools-demo:latest

使用此设置,如果您现在更改类和/或资源,并在本地运行 mvn

mvn compile

您应该能够使用以下docker cp 命令强制容器中的重启机制:

docker cp classes <container name>:/app/classes

请再次考虑在您的容器中安装一个卷,而不是使用docker cp

我测试了设置,它工作正常。

要记住的重要一点是替换您的爆炸资源,而不是整个应用程序 jar。

作为另一种选择,您可以采用类似于 cmets 中指示的方法并在远程模式下运行您的 Devtools:

FROM maven:3.5-jdk-8 as maven

WORKDIR /app

# Copy project pom
COPY ./pom.xml ./pom.xml

# Fetch (and cache) dependencies
RUN mvn dependency:go-offline -B

# Copy source files
COPY ./src ./src

# Build jar
RUN mvn package && cp target/your-app-version.jar app.jar

# Final run image (based on https://***.com/questions/53691781/how-to-cache-maven-dependencies-in-docker)
FROM openjdk:8u171-jre-alpine
# OPTIONAL: copy dependencies so the thin jar won't need to re-download them
# COPY --from=maven /root/.m2 /root/.m2

# Change working directory
WORKDIR /app

# Copy artifact from the maven image
COPY --from=maven /app/app.jar ./app.jar

ENV JAVA_DOCKER_OPTS "-agentlib:jdwp=transport=dt_socket,server=y,address=*:8000,suspend=n"

ENV JAVA_OPTS "-Dspring.devtools.restart.enabled=true"

EXPOSE 8000

EXPOSE 8080

ENTRYPOINT ["/bin/bash", "-lc", "exec java $JAVA_DOCKER_OPTS $JAVA_OPTS -jar /app/app.jar"]

要让 Spring Boot Devtools 远程模式正常工作,您需要几件事(Opri 在他/她的回答中也指出了其中一些)。

首先,你需要在你的应用jar中配置spring-boot-maven-plugin到include the devtools(否则默认会被排除):

<plugin>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-maven-plugin</artifactId>
  <configuration>
    <excludeDevtools>false</excludeDevtools>
  </configuration>
</plugin>

然后,您需要为配置属性spring.devtools.remote.secret 设置一个值。此属性与 Spring Boot Devtools 中远程调试的工作方式有关。

远程调试功能由两部分组成,客户端和服务器。基本上,客户端是您的服务器代码的副本,它使用spring.devtools.remote.secret 配置属性的值来针对服务器验证自己。

此客户端代码应为 run from an IDE,并且您将 IDE 调试过程附加到从该客户端公开的本地服务器。

在客户端监控的资源中执行的每个更改,请记住,与在您的服务器中执行的一样,都会被推送到远程服务器,并在必要时触发重新启动。

如您所见,从开发的角度来看,此功能再次更合适。

如果您需要通过覆盖您的 jar 应用程序文件来实际重新启动您的应用程序,也许更好的方法是将您的 docker 容器配置为在您的 ENTRYPOINTCMD 中运行一个 shell 脚本。此 shell 脚本将监视某个目录中的 jar 副本。如果由于您的docker cp 而导致该资源发生更改,则此 shell 脚本将停止当前正在运行的应用程序版本 - 此应用程序应该从不同的位置运行以避免更新 jar 时出现问题 - 将当前 jar 替换为新的,然后启动新的应用程序版本。不一样,但请考虑阅读this related SO answer。

无论如何,当您在容器中运行应用程序时,您都在尝试为其提供一致且独立于平台的部署方式。从这个角度来看,与其监视 docker 容器中的更改,更方便的方法可能是生成并部署具有这些新更改的容器映像的新版本。使用 Jenkins、Travis 等工具可以极大地自动化此过程。这些工具允许您定义 CI/CD 管道,例如,响应代码提交,可以使用您的代码动态生成 docker 映像,并进行相应配置,稍后将此映像部署到某些 docker 风格的服务或Kubernetes,在本地或云端。其中一些,尤其是 Kubernetes,但 swarm 甚至 docker compose 也将允许您执行滚动更新,而不会或在最少的应用程序服务中断的情况下。

总之,它可能不符合您的需求,但请注意,您可以直接使用spring-boot-starter-actuator 或与Spring Boot Admin 一起使用,例如,重新启动您的应用程序。

最后,正如 Spring Boot Devtools 文档中已经指出的那样,您可以尝试不同的选项,不是基于重新启动,而是基于应用程序重新加载,在热插拔中。此功能由JRebel 等商业产品提供,尽管也有一些开源替代品,主要是dcevm 和HotswapAgent。 This related article 提供了有关这最后两个产品如何工作的一些见解。 This Github project 提供了有关如何在 docker 容器中运行它的补充信息。

【讨论】:

【参考方案2】:

我在使用 intellij idea 时遇到了类似的问题,我在某处看到您必须使用构建按钮才能使其工作。

在 jsp 中,应用程序重新加载文件,它不是完全自动的,因为 intellij 会自动保存 -> 这种行为是默认的,但我认为有一种方法可以改变它。 -> 手动录制,然后自动重新加载。

仅适用于 jsp 应用程序,如果您在标准应用程序上尝试此操作,它将创建双帧执行 (swing)

【讨论】:

自动构建,看这篇文章:medium.com/@bhanuchaddha/…(不知道有没有用) 我正在使用 mvn 构建,更喜欢非 IDE 解决方案。我也有this other problem【参考方案3】:

我不支持,因为如果你尝试过这些事情,你并没有明确地说:

尝试将此设置为 true:(SpringApplication.setRegisterShutdownHook(true))

尝试在 dockerfile 中手动添加此属性-Dspring.devtools.restart.enabled=true

我知道它说默认情况下应该为真,但请尝试这样做 手动

也许向我们展示 dockerfile。

后期编辑: 在文档中看到了这一点:

默认情况下,重新打包的档案不包含 devtools。如果你想 使用某些远程开发工具功能,您需要禁用 excludeDevtools 构建属性以包含它。该物业是 支持 Maven 和 Gradle 插件。

Spring Boot 开发者工具不仅限于本地开发。您还可以在远程运行应用程序时使用多种功能。远程支持是可选的,要启用它,您需要确保 devtools 包含在重新打包的存档中:

<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
            <configuration>
                <excludeDevtools>false</excludeDevtools>
            </configuration>
        </plugin>
    </plugins>
</build>

然后你需要设置一个spring.devtools.remote.secret属性,例如:

spring.devtools.remote.secret=mysecret

远程开发工具支持分两部分提供;有一个接受连接的服务器端端点,以及一个在 IDE 中运行的客户端应用程序。设置 spring.devtools.remote.secret 属性时,服务器组件会自动启用。客户端组件必须手动启动。

Documents from spring

【讨论】:

我省略了一些不相关的专有内容和样板,但 dockerfile 基本上是 ENV JAVA_DOCKER_OPTS "-agentlib:jdwp=transport=dt_socket,server=y,address=*:8000,suspend=n" ENV JAVA_OPTS "-Dspring.devtools.restart.enabled=true" WORKDIR $APPLICATION_DIR ENTRYPOINT ["/bin/bash", "-lc", "exec java $JAVA_DOCKER_OPTS $JAVA_OPTS -jar $APPLICATION_DIR/$APPLICATION_NAME.jar"]【参考方案4】:

要使用 devtools 重新启动应用程序,您需要确保以下事项。

    使用任何 IDE 或构建工具(如 maven gradle)启动应用程序 使用 java -jar devtools 将无法工作,因为它会打包应用程序。 使用 maven,您可以运行像 mvn spring-boot:run 这样的应用程序

Refer official documentation for more details.

【讨论】:

以上是关于spring-boot-devtools 自动重启不起作用的主要内容,如果未能解决你的问题,请参考以下文章

Intellij IDEA 使用Spring-boot-devTools无效解决办法

Intellij IDEA 使用Spring-boot-devTools无效解决办法

Intellij IDEA 使用Spring-boot-devTools无效解决办法

maven工程使用spring-boot-devtools进行热部署,更改代码避免重启web容器

maven工程使用spring-boot-devtools进行热部署,更改代码避免重启web容器

spring-boot-devtools 快速重启的秘密!#yyds干货盘点#