אריזה מחדש של קובץ WAR לקובץ JAR

במסמך הזה נסביר איך לארוז מחדש אפליקציית Java 8 כקובץ JAR כדי להפעיל אותה בסביבת זמן ריצה של Java נתמכת. כדי להשתמש בסביבת זמן ריצה נתמכת של Java, אפשר להטמיע שרת כמו Jetty או להפוך את האפליקציה לקונטיינר באמצעות Docker כדי ליצור סביבת זמן ריצה בהתאמה אישית, בלי לשכתב את האפליקציה לחלוטין. אפשר להפעיל את אפליקציות ה-WAR הקיימות בפלטפורמות Java מודרניות או בסביבות גמישות בענן. אפשר לבחור מבין השיטות הבאות את השיטה שהכי מתאימה לאסטרטגיית הפריסה ולתשתית שלכם:

הכנת אפליקציית אינטרנט ב-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 כדי להגדיר את ה-build לאפליקציית Java 8:

    • הגדרת אריזת 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 dependency:

      <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.

ההוראות הבאות מתארות איך להכניס את אפליקציית Java 8 שלכם לקונטיינר באמצעות Dockerfile:

  1. הכנת אפליקציית אינטרנט ב-Java 8 (קובץ WAR)
  2. יצירת קובץ אימג' של קונטיינר והעברה שלו בדחיפה ל-Artifact Registry
  3. פריסת האפליקציה

יצירת קובץ אימג' של קונטיינר והעברה שלו בדחיפה ל-Artifact Registry

בקטע הזה מוסבר איך ליצור קובץ אימג' של Docker באמצעות Cloud Build ולהעביר אותו בדחיפה למאגר של 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 .
    • REPOSITORY בשם המאגר ב-Artifact Registry.
    • IMAGE בכתובת ה-URL של קובץ אימג' של קונטיינר.
    • 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 .
    • REPOSITORY בשם המאגר ב-Artifact Registry.
    • IMAGE בכתובת ה-URL של קובץ אימג' של קונטיינר.
    • TAG בתג של קובץ אימג' של קונטיינר.

שימוש בסביבת זמן ריצה מוטמעת של Java

ההוראות הבאות מראות איך לארוז מחדש אפליקציית Java 8 של App Engine עם שרת מוטמע (Jetty) כדי להפעיל אותה כקובץ JAR עצמאי בסביבת ריצה של Java נתמכת:

  1. יצירת שרת Jetty מוטמע
  2. הכנת אפליקציית אינטרנט ב-Java 8 (קובץ WAR)
  3. הפעלת קובץ ה-WAR עם Jetty מוטמע ופריסת האפליקציה

יצירת שרת Jetty מוטמע

כדי לארוז את קובץ ה-WAR של האפליקציה עם שרת Jetty מוטמע, פועלים לפי השלבים הבאים:

  1. יוצרים מחלקה Main כדי לאתחל ולהגדיר את שרת Jetty להרצת קובץ ה-WAR. הסיווג Main מגדיר את יציאת השרת שמוגדרת כברירת מחדל ל-8080. אפשר גם לשנות את קוד המקור כדי להשתמש ביציאה שצוינה במשתנה הסביבה PORT. המחלקות Main מגדירות את ה-handler 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.source ו-maven.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. מריצים את הפקודה mvn install בספריית הפרויקט של כלי ההרצה של Jetty. הפעולה הזו יוצרת את jetty-jar-with-dependencies.jar בספריית היעד.

  4. פועלים לפי ההוראות שבקטע הכנת אפליקציית אינטרנט ב-Java 8 (קובץ WAR) כדי ליצור קובץ WAR.

מריצים את קובץ ה-WAR עם Jetty מוטמע ופורסים את האפליקציה

בקטע הזה מוסבר איך לארוז את האפליקציה בקובץ JAR שניתן להפעלה. כדי לארוז ולפרוס את האפליקציה:

  1. ממקמים את קובץ ה-JAR של Jetty runner שנוצר 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. יוצרים רכיב entrypoint בקובץ app.yaml כדי לקרוא לקובץ 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.