使用容器進行 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 除錯配置,類似於以下內容:

Java Connect a Debugger

設定一個斷點。

開啟 `src/main/java/org/springframework/samples/petclinic/vet/VetController.java` 並在 `showResourcesVetList` 函式中新增一個斷點。

要啟動除錯會話,請選擇 **Run (執行)** 選單,然後選擇 **Debug *NameOfYourConfiguration* (除錯 *你的配置名稱*)**。

Debug menu

現在你應該能在 Compose 應用程式的日誌中看到連線。

Compose log file

現在你可以呼叫伺服器端點。

$ curl --request GET --url https://:8080/vets

你應該已經看到程式碼在標記行中斷,現在你可以像往常一樣使用偵錯程式。你還可以檢查和監視變數,設定條件斷點,檢視堆疊跟蹤,並執行許多其他操作。

Debugger code breakpoint

在終端中按 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 中執行單元測試。