Spring Boot在Docker Compose中热重载导致应用运行两次的问题咨询
问题描述
我有一个Spring Boot应用,正尝试在开发阶段配置热重载功能,使用Docker Compose通过卷挂载将主机目录映射到应用容器中。启用热重载以自动反映代码变更后,应用出现了运行两次的情况。这是预期行为吗?是否是我的配置有误导致热重载时出现这种重复运行的问题?
提供的配置文件
Dockerfile
FROM maven:3.8.4-openjdk-17-slim AS build WORKDIR /app COPY pom.xml . RUN mvn dependency:go-offline -B COPY src ./src RUN mvn package -DskipTests FROM openjdk:17-jdk-slim AS runtime COPY --from=build /app/target/book-course-1.0.0.jar . CMD ["java", "-jar", "book-course-1.0.0.jar"] WORKDIR /app
docker-compose.yml
version: "3.7" services: app: build: . env_file: ./.env ports: - $SPRING_PORT:$SPRING_PORT depends_on: - mysqldb volumes: - .:/app restart: on-failure command: sh -c "./mvnw spring-boot:run" mysqldb: image: mysql:8.0.22
问题分析
应用运行两次不是预期行为,核心是配置冲突或热重载触发机制异常导致的,主要原因如下:
1. Spring DevTools的重复触发
如果项目引入了spring-boot-devtools依赖,它会监听类路径文件变更并自动重启应用。Docker卷挂载主机目录到容器时,主机文件保存后的同步过程可能产生多次文件变更事件,导致DevTools触发多次重启,看起来像是应用运行了两次。
2. Docker配置的冗余与冲突
你的Dockerfile的runtime阶段已经定义了启动jar包的CMD,但docker-compose又通过command指定了用mvnw spring-boot:run启动应用。虽然command会覆盖CMD,但卷挂载后容器内的目录结构被主机目录覆盖,可能导致启动逻辑出现异常分支,增加重复启动的概率。
另外,docker-compose.yml存在缩进错误(app和mysqldb下的配置项未正确缩进),虽然可能不影响启动,但会导致配置可读性差,增加排查难度。
解决方案
方案1:调整DevTools触发策略
在application.properties或application.yml中添加配置,避免文件同步时的误触发:
# 仅当手动修改.trigger文件时才触发重启 spring.devtools.restart.trigger-file=.reloadtrigger # 延长文件变更检测间隔,避免重复触发 spring.devtools.restart.poll-interval=2000ms
使用时,只需在主机项目根目录下创建或修改.reloadtrigger文件,即可触发一次热重载。
方案2:简化Docker配置(推荐)
开发阶段不需要预先构建jar包,直接用maven镜像运行应用,简化配置:
修改后的Dockerfile
FROM maven:3.8.4-openjdk-17-slim WORKDIR /app COPY pom.xml . RUN mvn dependency:go-offline -B COPY . . CMD ["./mvnw", "spring-boot:run"]
修改后的docker-compose.yml(修复缩进+优化挂载)
version: "3.7" services: app: build: . env_file: ./.env ports: - $SPRING_PORT:$SPRING_PORT depends_on: - mysqldb volumes: - .:/app - ~/.m2:/root/.m2 # 挂载本地maven仓库,避免重复下载依赖 restart: on-failure mysqldb: image: mysql:8.0.22
去掉docker-compose中的冗余command,保持启动逻辑统一,同时挂载本地maven仓库提升依赖加载速度。
方案3:禁用自动重启(按需选择)
如果不需要自动热重载,可直接关闭DevTools的重启功能:
spring.devtools.restart.enabled=false
内容的提问来源于stack exchange,提问作者Ali Dehkhodaei

