使用容器进行 Java 开发
先决条件
按照 容器化你的应用 中的步骤,容器化你的应用程序。
概述
在本节中,你将逐步设置你在上一节中容器化的应用程序的本地开发环境。这包括:
- 添加本地数据库并持久化数据
- 创建开发容器以连接调试器
- 配置 Compose 在您编辑和保存代码时自动更新正在运行的 Compose 服务
添加本地数据库并持久化数据
你可以使用容器来设置本地服务,例如数据库。在本节中,你将更新 `docker-compose.yaml` 文件,以定义数据库服务和用于持久化数据的卷。此外,此应用程序使用系统属性来定义数据库类型,因此你需要更新 `Dockerfile`,以便在启动应用程序时传入系统属性。
在克隆的仓库目录中,使用 IDE 或文本编辑器打开 `docker-compose.yaml` 文件。你的 Compose 文件中有一个示例数据库服务,但你的独特应用程序需要进行一些更改。
在 `docker-compose.yaml` 文件中,你需要执行以下操作:
- 取消注释所有数据库指令。现在你将使用数据库服务而不是本地存储来存储数据。
- 删除顶层的 `secrets` 元素以及 `db` 服务中的元素。此示例使用环境变量作为密码,而不是 secrets。
- 从 `db` 服务中删除 `user` 元素。此示例在环境变量中指定用户。
- 更新数据库环境变量。这些由 Postgres 镜像定义。有关更多详细信息,请参阅 Postgres 官方 Docker 镜像。
- 更新 `db` 服务的健康检查测试并指定用户。默认情况下,健康检查使用 root 用户而不是你定义的 `petclinic` 用户。
- 在 `server` 服务中将数据库 URL 添加为环境变量。这将覆盖 `spring-petclinic/src/main/resources/application-postgres.properties` 中定义的默认值。
以下是更新后的 `docker-compose.yaml` 文件。所有注释均已删除。
services:
server:
build:
context: .
ports:
- 8080:8080
depends_on:
db:
condition: service_healthy
environment:
- POSTGRES_URL=jdbc:postgresql://db:5432/petclinic
db:
image: postgres
restart: always
volumes:
- db-data:/var/lib/postgresql/data
environment:
- POSTGRES_DB=petclinic
- POSTGRES_USER=petclinic
- POSTGRES_PASSWORD=petclinic
ports:
- 5432:5432
healthcheck:
test: ["CMD", "pg_isready", "-U", "petclinic"]
interval: 10s
timeout: 5s
retries: 5
volumes:
db-data:在 IDE 或文本编辑器中打开 `Dockerfile`。在 `ENTRYPOINT` 指令中,更新指令以传入 `spring-petclinic/src/resources/db/postgres/petclinic_db_setup_postgres.txt` 文件中指定的系统属性。
- ENTRYPOINT [ "java", "org.springframework.boot.loader.launch.JarLauncher" ]
+ ENTRYPOINT [ "java", "-Dspring.profiles.active=postgres", "org.springframework.boot.loader.launch.JarLauncher" ]
保存并关闭所有文件。
现在,运行以下 `docker compose up` 命令来启动你的应用程序。
$ docker compose up --build
打开浏览器并访问应用程序:https://:8080。你将看到一个简单的宠物诊所应用程序。浏览应用程序。导航到 **兽医 (Veterinarians)** 并通过能够列出兽医来验证应用程序是否已连接到数据库。
在终端中,按 ctrl+c 停止应用程序。
用于开发的 Dockerfile
你现在拥有的 Dockerfile 非常适合小型、安全的生产镜像,其中只包含运行应用程序所需的组件。在开发时,你可能需要一个具有不同环境的镜像。
例如,在开发镜像中,你可能希望设置镜像以启动应用程序,以便可以将调试器连接到正在运行的 Java 进程。
与其管理多个 Dockerfile,不如添加一个新阶段。你的 Dockerfile 可以生成一个可用于生产的最终镜像以及一个开发镜像。
用以下内容替换 Dockerfile 的内容。
# syntax=docker/dockerfile:1
FROM eclipse-temurin:21-jdk-jammy as deps
WORKDIR /build
COPY --chmod=0755 mvnw mvnw
COPY .mvn/ .mvn/
RUN --mount=type=bind,source=pom.xml,target=pom.xml \
--mount=type=cache,target=/root/.m2 ./mvnw dependency:go-offline -DskipTests
FROM deps as package
WORKDIR /build
COPY ./src src/
RUN --mount=type=bind,source=pom.xml,target=pom.xml \
--mount=type=cache,target=/root/.m2 \
./mvnw package -DskipTests && \
mv target/$(./mvnw help:evaluate -Dexpression=project.artifactId -q -DforceStdout)-$(./mvnw help:evaluate -Dexpression=project.version -q -DforceStdout).jar target/app.jar
FROM package as extract
WORKDIR /build
RUN java -Djarmode=layertools -jar target/app.jar extract --destination target/extracted
FROM extract as development
WORKDIR /build
RUN cp -r /build/target/extracted/dependencies/. ./
RUN cp -r /build/target/extracted/spring-boot-loader/. ./
RUN cp -r /build/target/extracted/snapshot-dependencies/. ./
RUN cp -r /build/target/extracted/application/. ./
ENV JAVA_TOOL_OPTIONS -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:8000
CMD [ "java", "-Dspring.profiles.active=postgres", "org.springframework.boot.loader.launch.JarLauncher" ]
FROM eclipse-temurin:21-jre-jammy AS final
ARG UID=10001
RUN adduser \
--disabled-password \
--gecos "" \
--home "/nonexistent" \
--shell "/sbin/nologin" \
--no-create-home \
--uid "${UID}" \
appuser
USER appuser
COPY --from=extract build/target/extracted/dependencies/ ./
COPY --from=extract build/target/extracted/spring-boot-loader/ ./
COPY --from=extract build/target/extracted/snapshot-dependencies/ ./
COPY --from=extract build/target/extracted/application/ ./
EXPOSE 8080
ENTRYPOINT [ "java", "-Dspring.profiles.active=postgres", "org.springframework.boot.loader.launch.JarLauncher" ]保存并关闭 `Dockerfile`。
在 `Dockerfile` 中,你添加了一个基于 `extract` 阶段的新阶段,标记为 `development`。在此阶段中,你将提取的文件复制到一个公共目录,然后运行命令启动应用程序。在命令中,你暴露了端口 8000,并声明了 JVM 的调试配置,以便你可以附加调试器。
使用 Compose 进行本地开发
当前的 Compose 文件不会启动你的开发容器。为此,你必须更新 Compose 文件以定位开发阶段。另外,更新服务器服务的端口映射以提供调试器访问权限。
打开 `docker-compose.yaml` 并将以下指令添加到文件中。
services:
server:
build:
context: .
target: development
ports:
- 8080:8080
- 8000:8000
depends_on:
db:
condition: service_healthy
environment:
- POSTGRES_URL=jdbc:postgresql://db:5432/petclinic
db:
image: postgres
restart: always
volumes:
- db-data:/var/lib/postgresql/data
environment:
- POSTGRES_DB=petclinic
- POSTGRES_USER=petclinic
- POSTGRES_PASSWORD=petclinic
ports:
- 5432:5432
healthcheck:
test: ["CMD", "pg_isready", "-U", "petclinic"]
interval: 10s
timeout: 5s
retries: 5
volumes:
db-data:现在,启动你的应用程序并确认它正在运行。
$ docker compose up --build
最后,测试你的 API 端点。运行以下 curl 命令:
$ curl --request GET \
--url https://:8080/vets \
--header 'content-type: application/json'
您应该收到以下响应
{
"vetList": [
{
"id": 1,
"firstName": "James",
"lastName": "Carter",
"specialties": [],
"nrOfSpecialties": 0,
"new": false
},
{
"id": 2,
"firstName": "Helen",
"lastName": "Leary",
"specialties": [{ "id": 1, "name": "radiology", "new": false }],
"nrOfSpecialties": 1,
"new": false
},
{
"id": 3,
"firstName": "Linda",
"lastName": "Douglas",
"specialties": [
{ "id": 3, "name": "dentistry", "new": false },
{ "id": 2, "name": "surgery", "new": false }
],
"nrOfSpecialties": 2,
"new": false
},
{
"id": 4,
"firstName": "Rafael",
"lastName": "Ortega",
"specialties": [{ "id": 2, "name": "surgery", "new": false }],
"nrOfSpecialties": 1,
"new": false
},
{
"id": 5,
"firstName": "Henry",
"lastName": "Stevens",
"specialties": [{ "id": 1, "name": "radiology", "new": false }],
"nrOfSpecialties": 1,
"new": false
},
{
"id": 6,
"firstName": "Sharon",
"lastName": "Jenkins",
"specialties": [],
"nrOfSpecialties": 0,
"new": false
}
]
}连接调试器
你将使用 IntelliJ IDEA 附带的调试器。你可以使用此 IDE 的社区版本。在 IntelliJ IDEA 中打开你的项目,转到 **Run (运行)** 菜单,然后选择 **Edit Configuration (编辑配置)**。添加一个新的远程 JVM 调试配置,类似于以下内容:

设置一个断点。
打开 `src/main/java/org/springframework/samples/petclinic/vet/VetController.java` 并在 `showResourcesVetList` 函数中添加一个断点。
要启动调试会话,请选择 **Run (运行)** 菜单,然后选择 **Debug *NameOfYourConfiguration* (调试 *你的配置名称*)**。

现在你应该能在 Compose 应用程序的日志中看到连接。

现在你可以调用服务器端点。
$ curl --request GET --url https://:8080/vets
你应该已经看到代码在标记行中断,现在你可以像往常一样使用调试器。你还可以检查和监视变量,设置条件断点,查看堆栈跟踪,并执行许多其他操作。

在终端中按 ctrl+c 停止您的应用程序。
自动更新服务
使用 Compose Watch 在您编辑和保存代码时自动更新正在运行的 Compose 服务。有关 Compose Watch 的更多详细信息,请参阅使用 Compose Watch。
在 IDE 或文本编辑器中打开你的 `docker-compose.yaml` 文件,然后添加 Compose Watch 指令。以下是更新后的 `docker-compose.yaml` 文件。
services:
server:
build:
context: .
target: development
ports:
- 8080:8080
- 8000:8000
depends_on:
db:
condition: service_healthy
environment:
- POSTGRES_URL=jdbc:postgresql://db:5432/petclinic
develop:
watch:
- action: rebuild
path: .
db:
image: postgres
restart: always
volumes:
- db-data:/var/lib/postgresql/data
environment:
- POSTGRES_DB=petclinic
- POSTGRES_USER=petclinic
- POSTGRES_PASSWORD=petclinic
ports:
- 5432:5432
healthcheck:
test: ["CMD", "pg_isready", "-U", "petclinic"]
interval: 10s
timeout: 5s
retries: 5
volumes:
db-data:运行以下命令,使用 Compose Watch 运行您的应用程序。
$ docker compose watch
打开网页浏览器并访问应用程序:https://:8080。你应该会看到 Spring Pet Clinic 主页。
你本地机器上应用程序源文件的任何更改现在都将自动反映在运行中的容器中。
在 IDE 或文本编辑器中打开 `spring-petclinic/src/main/resources/templates/fragments/layout.html`,并通过添加感叹号更新 `Home` 导航字符串。
- <li th:replace="~{::menuItem ('/','home','home page','home','Home')}">
+ <li th:replace="~{::menuItem ('/','home','home page','home','Home!')}">
保存对 `layout.html` 的更改,然后你可以继续开发,而容器会自动重建。
容器重建并运行后,刷新 https://:8080,然后验证菜单中现在是否显示 **Home! (主页!)**。
在终端中按 `ctrl+c` 停止 Compose Watch。
摘要
在本节中,你了解了如何在本地运行数据库并持久化数据。你还创建了一个包含 JDK 的开发镜像,并允许你附加调试器。最后,你设置了 Compose 文件以暴露调试端口,并配置了 Compose Watch 以实时重新加载你的更改。
相关信息
后续步骤
在下一节中,你将了解如何在 Docker 中运行单元测试。