אריזה מחדש של קובץ 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 תלות:

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