将 WAR 文件重新封装为 JAR 文件

本文档介绍了如何将 Java 8 应用重新封装为 JAR 文件,以便在受支持的 Java 运行时环境中运行。如需使用受支持的 Java 运行时,您可以嵌入 Jetty 等服务器,也可以使用 Docker 将应用容器化以实现自定义运行时,而无需完全重写应用。您可以在现代 Java 平台或灵活的云环境中运行现有的 WAR 应用。请从以下方法中选择最适合您的部署策略和基础设施的方法:

准备 Java 8 Web 应用 (WAR 文件)

在将 Java 8 应用重新封装为受支持的 JAR 文件之前,您必须构建 WAR 文件。本部分提供了一个构建 WAR 文件的 Java 8 应用示例。按照以下说明创建 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 部署描述符文件以配置您的 Web 应用:

    <?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 应用的 build:

    • WAR 打包配置:

      <groupId>com.example</groupId>
      <artifactId>HelloWorldApp</artifactId>
      <version>1.0</version>
      <packaging>war</packaging>
      
    • maven-war-plugin 插件,并将 maven.compiler 源代码和目标代码设置为版本 1.8

      <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 Web 应用 (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 替换为您的 Google Cloud 项目 ID。
    • 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 替换为您的 Google Cloud 项目 ID。
    • REPOSITORY 替换为 Artifact Registry 代码库的名称。
    • IMAGE 替换为容器映像网址。
    • TAG 替换为您的容器映像标记。

使用嵌入式 Java 运行时

以下说明演示了如何将 App Engine Java 8 应用与嵌入式服务器 (Jetty) 重新封装,以便在受支持的 Java 运行时环境中作为独立 JAR 运行:

  1. 创建嵌入式 Jetty 服务器
  2. 准备 Java 8 Web 应用 (WAR 文件)
  3. 运行包含嵌入式 Jetty 的 WAR 文件并部署应用

创建嵌入式 Jetty 服务器

如需将应用 WAR 文件与嵌入式 Jetty 服务器捆绑在一起,请按以下步骤操作:

  1. 创建 Main 类以初始化和配置用于运行 WAR 文件的 Jetty 服务器。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 Web 应用 (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 命令部署您的应用。