將 WAR 檔案重新封裝成 JAR 檔案

本文說明如何將 Java 8 應用程式重新封裝為 JAR 檔案,以便在支援的 Java 執行階段中執行。如要使用支援的 Java 執行階段,您可以嵌入 Jetty 等伺服器,或使用 Docker 將應用程式容器化,以自訂執行階段,不必完全重寫應用程式。您可以在新式 Java 平台或彈性雲端環境中,執行現有的 WAR 應用程式。請從下列方法中,選擇最符合部署策略和基礎架構的方法:

準備 Java 8 網頁應用程式 (WAR 檔案)

將 Java 8 應用程式重新封裝為支援的 JAR 檔案前,您必須先建構 WAR 檔案。本節提供 Java 8 應用程式範例,可建構 WAR 檔案。按照操作說明建立 Java 8 hello-world 應用程式:

  1. 在來源目錄中建立 HelloServlet.java 檔案:

    
    import java.io.IOException;
    import javax.servlet.ServletException;
    import javax.servlet.annotation.WebServlet;
    import javax.servlet.http.HttpServlet;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    
    @WebServlet("/hello")
    public final class HelloServlet extends HttpServlet {
      /**
       * This method handles GET requests to the /hello endpoint.
       *
       * <p>Subclasses should not override this method.
       *
       * @param request the HttpServletRequest object
       * @param response the HttpServletResponse object
       * @throws ServletException if a servlet-specific error occurs
       * @throws IOException if an I/O error occurs
       */
      @Override
      protected void doGet(
          final HttpServletRequest request, final HttpServletResponse response)
          throws ServletException, IOException {
        response.setContentType("text/html");
        response.getWriter().println("<h1>Hello, World!</h1>");
      }
    }
  2. 建立 web.xml 部署作業描述元檔案,設定網路應用程式:

    <?xml version="1.0" encoding="UTF-8"?>
    <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
             version="4.0">
    
        <welcome-file-list>
            <welcome-file>index.jsp</welcome-file>
        </welcome-file-list>
    
    </web-app>
  3. 建立到達網頁 index.jsp

    <%@ page contentType="text/html;charset=UTF-8" language="java" %>
    <html>
    <head>
        <title>Hello App Engine</title>
    </head>
    <body>
        <h1>Welcome to Google App Engine!</h1>
        <p><a href="hello">Say Hello</a></p>
    </body>
    </html>
    
  4. pom.xml 檔案中加入下列程式碼,定義 Java 8 應用程式的建構作業:

    • WAR 封裝設定:

      <groupId>com.example</groupId>
      <artifactId>HelloWorldApp</artifactId>
      <version>1.0</version>
      <packaging>war</packaging>
      
    • maven-war-plugin 外掛程式,且來源和目標設為版本 1.8maven.compiler

      <properties>
          <maven.compiler.source>1.8</maven.compiler.source>
          <maven.compiler.target>1.8</maven.compiler.target>
          <java.version>8</java.version>
      </properties>
      
    • javax.servlet-api 依附元件:

      <dependencies>
          <dependency>
              <groupId>javax.servlet</groupId>
              <artifactId>javax.servlet-api</artifactId>
              <version>3.1.0</version>
              <scope>provided</scope>
          </dependency>
      </dependencies>
      
    • Maven 設定:

        <build>
            <plugins>
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-compiler-plugin</artifactId>
                    <version>3.8.1</version> <configuration>
                        <source>${maven.compiler.source}</source>
                        <target>${maven.compiler.target}</target>
                    </configuration>
                </plugin>
      
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-war-plugin</artifactId>
                    <version>3.3.2</version>
                </plugin>
      
            </plugins>
        </build>
      

    專案目錄應類似下列結構:

    ├── pom.xml
    └── src
        └── main
            ├── java
            │   └── com
            │       └── example
            │           └── HelloServlet.java
            └── webapp
                ├── WEB-INF
                │   └── web.xml
                └── index.jsp
    
    
  5. 在應用程式的專案目錄中執行 mvn install,即可在目標目錄中產生 WAR 檔案 HelloWorldApp-1.0.war

使用 Dockerfile 部署應用程式 (建議)

自訂執行階段適用於支援自訂容器的平台,例如 App Engine 自訂執行階段。自訂執行階段可讓您設定執行階段環境,因此非常靈活。如需部署自訂執行階段的逐步範例,請參閱「在 App Engine 彈性環境中建立自訂執行階段應用程式」。

以下操作說明描述如何使用 Dockerfile,將 Java 8 應用程式容器化:

  1. 準備 Java 8 網頁應用程式 (WAR 檔案)
  2. 建構容器映像檔並推送至 Artifact Registry
  3. 部署應用程式

建構容器映像檔並推送至 Artifact Registry

本節說明如何使用 Cloud Build 建構 Docker 映像檔,並將其推送至 Artifact Registry 存放區。請按照下列步驟建立應用程式的容器映像檔:

  1. 在來源目錄中建立 cloudbuild.yaml 檔案,建構 Docker 映像檔並推送至 Artifact Registry:

    steps:
    # Step 1: Build the Docker image
    - name: "gcr.io/cloud-builders/docker"
        args:
        - "build"
        - "-t"
        - "$LOCATION-docker.pkg.dev/$PROJECT/$REPOSITORY/SERVICE:VERSION"
        - "."
    
    # Step 2: Push the Docker image to Artifact Registry
    - name: "gcr.io/cloud-builders/docker"
        args:
        - "push"
        - "$LOCATION-docker.pkg.dev/$PROJECT/$REPOSITORY/SERVICE:VERSION"
    
    images:
    - "$LOCATION-docker.pkg.dev/$PROJECT/$REPOSITORY/SERVICE:VERSION"
    

    取代:

    • LOCATION 改成您部署應用程式的 Google Cloud 區域。
    • PROJECT 改成您的專案 ID。 Google Cloud
    • REPOSITORY 替換為 Artifact Registry 存放區的名稱。
    • IMAGE 改為容器映像檔網址。
    • TAG 改為容器映像檔標記。
  2. 建立 Dockerfile 並加入以下設定:

    
    # Use Maven to build the project with JDK 8
    FROM maven:3.8.6-openjdk-8 AS build
    
    # Set working directory
    WORKDIR /app
    
    # Copy the application source code
    COPY . .
    
    # Build the application
    RUN mvn clean package
    
    # Use Jetty as the runtime
    FROM jetty:9.4-jdk8
    
    # Set Jetty working directory
    WORKDIR /var/lib/jetty/webapps
    
    # Copy the built WAR file
    COPY --from=build /app/target/*.war ./ROOT.war
    
    # Expose the default Jetty port
    EXPOSE 8080
    
    # Start Jetty correctly
    CMD ["java", "-Djetty.base=/var/lib/jetty", "-jar", "/usr/local/jetty/start.jar"]
  3. 下載並安裝 Docker,測試範例應用程式,然後在本機電腦上執行 Hello World 容器。

  4. 建構容器映像檔並推送至 Artifact Registry:

    gcloud builds submit .
    

部署應用程式

如要部署 App Engine 應用程式:

  1. 設定 app.yaml 檔案,在來源目錄中使用自訂執行階段:

    runtime: custom
    env: flex
    instance_class: F1
    
    handlers:
      - url: /.*
        script: auto

    專案目錄應類似下列結構:

    ├── Dockerfile
    ├── README.md
    ├── app.yaml
    ├── cloudbuild.yaml
    ├── pom.xml
    └── src
        └── main
            ├── java
            │   └── com
            │       └── example
            │           └── HelloServlet.java
            └── webapp
                ├── WEB-INF
                │   └── web.xml
                └── index.jsp
    
  2. 使用 gcloud app deploy 指令部署應用程式:

    gcloud app deploy --image-url=REGION-docker.pkg.dev/PROJECT_ID/REPOSITORY/IMAGE:tag
    

    取代:

    • LOCATION 改成您部署應用程式的 Google Cloud 區域。
    • PROJECT 改成您的專案 ID。 Google Cloud
    • REPOSITORY 替換為 Artifact Registry 存放區的名稱。
    • IMAGE 改為容器映像檔網址。
    • TAG 改為容器映像檔標記。

使用內嵌的 Java 執行階段

下列操作說明示範如何重新封裝內含嵌入式伺服器 (Jetty) 的 App Engine Java 8 應用程式,以便在支援的 Java 執行階段中以獨立 JAR 形式執行:

  1. 建立內嵌的 Jetty 伺服器
  2. 準備 Java 8 網頁應用程式 (WAR 檔案)
  3. 使用內嵌 Jetty 執行 WAR 檔案,並部署應用程式

建立內嵌 Jetty 伺服器

如要將應用程式 WAR 檔案與內嵌 Jetty 伺服器組合在一起,請按照下列步驟操作:

  1. 建立 Main 類別,初始化及設定 Jetty 伺服器,以執行 WAR 檔案。Main 類別會設定伺服器連接埠,預設為 8080。您也可以修改原始碼,使用 PORT 環境變數中指定的通訊埠。Main 類別會設定 WebAppContext 處理常式,以提供 WAR 檔案:

    import org.eclipse.jetty.server.Server;
    import org.eclipse.jetty.webapp.Configuration.ClassList;
    import org.eclipse.jetty.webapp.WebAppContext;
    
    /** Simple Jetty Main that can execute a WAR file when passed as an argument. */
    public class Main {
    
      public static void main(String[] args) throws Exception {
        if (args.length != 1) {
          System.err.println("Usage: need a relative path to the war file to execute");
          System.exit(1);
        }
        System.setProperty("org.eclipse.jetty.util.log.class", "org.eclipse.jetty.util.log.StrErrLog");
        System.setProperty("org.eclipse.jetty.LEVEL", "INFO");
    
        // Create a basic Jetty server object that will listen on port defined by
        // the PORT environment variable when present, otherwise on 8080.
        int port = Integer.parseInt(System.getenv().getOrDefault("PORT", "8080"));
        Server server = new Server(port);
    
        // The WebAppContext is the interface to provide configuration for a web
        // application. In this example, the context path is being set to "/" so
        // it is suitable for serving root context requests.
        WebAppContext webapp = new WebAppContext();
        webapp.setContextPath("/");
        webapp.setWar(args[0]);
        ClassList classlist = ClassList.setServerDefault(server);
    
        // Enable Annotation Scanning.
        classlist.addBefore(
            "org.eclipse.jetty.webapp.JettyWebXmlConfiguration",
            "org.eclipse.jetty.annotations.AnnotationConfiguration");
    
        // Set the the WebAppContext as the ContextHandler for the server.
        server.setHandler(webapp);
    
        // Start the server! By using the server.join() the server thread will
        // join with the current thread. See
        // "http://docs.oracle.com/javase/1.5.0/docs/api/java/lang/Thread.html#join()"
        // for more details.
        server.start();
        server.join();
      }
    }
  2. 建立 Maven 專案檔案 pom.xml,並新增下列設定:

    1. maven.compiler.sourcemaven.compiler.target 屬性設為支援的 Java 執行階段:

      <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>21</maven.compiler.source>
        <maven.compiler.target>21</maven.compiler.target>
        <jetty.version>9.4.57.v20241219</jetty.version>
      </properties>
    2. 新增 Jetty 的依附元件:

      <dependencies>
        <!-- Embedded Jetty dependencies -->
        <dependency>
          <groupId>org.eclipse.jetty</groupId>
          <artifactId>jetty-server</artifactId>
          <version>${jetty.version}</version>
        </dependency>
        <dependency>
          <groupId>org.eclipse.jetty</groupId>
          <artifactId>jetty-webapp</artifactId>
          <version>${jetty.version}</version>
          <type>jar</type>
        </dependency>
        <dependency>
          <groupId>org.eclipse.jetty</groupId>
          <artifactId>jetty-util</artifactId>
          <version>${jetty.version}</version>
        </dependency>
        <dependency>
          <groupId>org.eclipse.jetty</groupId>
          <artifactId>jetty-annotations</artifactId>
          <version>${jetty.version}</version>
        </dependency>
        <!-- extra explicit dependency needed because there is a JSP in the sample-->
        <dependency>
          <groupId>org.eclipse.jetty</groupId>
          <artifactId>apache-jsp</artifactId>
          <version>${jetty.version}</version>
        </dependency>
      </dependencies>
    3. maven-assembly-plugin 屬性設定為套件依附元件:

      
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-assembly-plugin</artifactId>
        <version>3.0.0</version>
        <configuration>
          <finalName>jetty</finalName>
          <descriptorRefs>
            <descriptorRef>jar-with-dependencies</descriptorRef>
          </descriptorRefs>
          <archive>
            <manifest>
              <mainClass>com.example.appengine.jetty.Main</mainClass>
            </manifest>
          </archive>
        </configuration>
        <executions>
          <execution>
            <id>make-assembly</id>
            <phase>package</phase>
            <goals>
              <goal>single</goal>
            </goals>
          </execution>
        </executions>
      </plugin>
      

    專案目錄應類似下列結構:

    ├─src
    │  └─main
    │      └─java
    │          └─jetty
    │              └─Main.java
    └─pom.xml
    
  3. 在 Jetty 執行器專案目錄中執行 mvn install 指令。這會在目標目錄中產生 jetty-jar-with-dependencies.jar

  4. 按照「準備 Java 8 網頁應用程式 (WAR 檔案)」一節中的操作說明建立 WAR 檔案。

使用內嵌 Jetty 執行 WAR 檔案,並部署應用程式

本節提供將應用程式封裝至可執行 JAR 檔案的步驟。請按照下列操作說明封裝及部署應用程式:

  1. 將產生的 Jetty 執行器 JAR jetty-jar-with-dependencies.jar 和應用程式 WAR 檔案 HelloWorldApp-1.0.war 放在同一個目錄中。

  2. 使用支援的 Java 執行階段執行應用程式:

        java -jar jetty-jar-with-dependencies.jar HelloWorldApp-1.0.war
    
    1. 在網路瀏覽器中前往 http://localhost:8080。您應該會看到應用程式的歡迎頁面。
  3. app.yaml 檔案中建立 entrypoint 元素,呼叫 jetty-jar-with-dependencies 檔案,並將 WAR 檔案做為引數傳遞。WAR 檔案中指定的版本必須與 pom.xml 檔案中的版本相同:

    runtime: java
    env: flex
    runtime_config:
      operating_system: ubuntu22
      runtime_version: 21
    entrypoint: "java -jar jetty-jar-with-dependencies.jar sample.war"
    handlers:
      - url: /.*
        script: this field is required, but ignored
    
    manual_scaling:
      instances: 1
  4. 使用 gcloud app deploy 指令部署應用程式。