Vuelve a empaquetar un archivo WAR en un archivo JAR

En este documento, se describe cómo volver a empaquetar una aplicación de Java 8 como un archivo JAR para que se ejecute en un entorno de ejecución de Java compatible. Para usar un entorno de ejecución de Java compatible, puedes incorporar un servidor como Jetty o crear un contenedor para tu aplicación con Docker para un entorno de ejecución personalizado sin tener que reescribir completamente tu aplicación. Puedes ejecutar tus aplicaciones WAR existentes en plataformas Java modernas o entornos de nube flexibles. Elige uno de los siguientes métodos que mejor se adapte a tu estrategia de implementación y a tu infraestructura:

Prepara tu aplicación web de Java 8 (archivo WAR)

Antes de volver a empaquetar tu aplicación de Java 8 como un archivo JAR compatible, debes compilar un archivo WAR. En esta sección, se proporciona una aplicación de Java 8 de ejemplo que compila un archivo WAR. Sigue las instrucciones para crear una aplicación hello-world de Java 8:

  1. Crea un archivo HelloServlet.java en tu directorio del código fuente:

    
    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. Crea un archivo descriptor de implementación web.xml para configurar tu aplicación 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. Crea una página de destino 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. Agrega el siguiente código al archivo pom.xml para definir la compilación de tu aplicación en Java 8:

    • Configuración del empaquetado de WAR:

      <groupId>com.example</groupId>
      <artifactId>HelloWorldApp</artifactId>
      <version>1.0</version>
      <packaging>war</packaging>
      
    • Complemento de maven-war-plugin con el origen maven.compiler y el destino configurados en la versión 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>
      
    • Dependencia de javax.servlet-api:

      <dependencies>
          <dependency>
              <groupId>javax.servlet</groupId>
              <artifactId>javax.servlet-api</artifactId>
              <version>3.1.0</version>
              <scope>provided</scope>
          </dependency>
      </dependencies>
      
    • Configuración de 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>
      

    El directorio de tu proyecto debería ser similar a la siguiente estructura:

    ├── pom.xml
    └── src
        └── main
            ├── java
            │   └── com
            │       └── example
            │           └── HelloServlet.java
            └── webapp
                ├── WEB-INF
                │   └── web.xml
                └── index.jsp
    
    
  5. Ejecuta mvn install en el directorio del proyecto de tu aplicación para generar el archivo WAR HelloWorldApp-1.0.war en el directorio de destino.

Usa Dockerfiles para implementar tu aplicación (recomendado)

Los entornos de ejecución personalizados son adecuados para las plataformas que admiten contenedores personalizados, como los entornos de ejecución personalizados de App Engine. Los entornos de ejecución personalizados proporcionan flexibilidad, ya que te permiten configurar el entorno de ejecución. Para ver un ejemplo de cómo implementar entornos de ejecución personalizados, consulta Crea una app del entorno de ejecución personalizado en el entorno flexible de App Engine.

En las siguientes instrucciones, se describe cómo crear un contenedor para tu aplicación de Java 8 con un Dockerfile:

  1. Prepara tu aplicación web de Java 8 (archivo WAR)
  2. Compila la imagen del contenedor y envíala a Artifact Registry
  3. Implementa tu aplicación

Compila la imagen del contenedor y envíala a Artifact Registry

En esta sección, se describe cómo compilar una imagen de Docker con Cloud Build y enviarla a un repositorio de Artifact Registry. Sigue estos pasos para crear una imagen de contenedor de tu aplicación:

  1. Crea un archivo cloudbuild.yaml en tu directorio del código fuente para compilar la imagen de Docker y enviarla a 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"
    

    Reemplaza lo siguiente:

    • LOCATION por la región de Google Cloud en la que implementas tu app.
    • PROJECT por el ID de tu proyecto Google Cloud .
    • REPOSITORY por el nombre de tu repositorio de Artifact Registry
    • IMAGE por la URL de la imagen de contenedor
    • TAG por la etiqueta de la imagen de contenedor
  2. Crea un Dockerfile con la siguiente configuración:

    
    # 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. Descarga e instala Docker para probar tu app de muestra y ejecutar el contenedor de Hello World en tu máquina local.

  4. Compila la imagen del contenedor y envíala a Artifact Registry:

    gcloud builds submit .
    

Implementa tu aplicación

Para implementar tu aplicación de App Engine, sigue estos pasos:

  1. Configura tu archivo app.yaml para usar un entorno de ejecución personalizado en el directorio de origen:

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

    El directorio de tu proyecto debería ser similar a la siguiente estructura:

    ├── Dockerfile
    ├── README.md
    ├── app.yaml
    ├── cloudbuild.yaml
    ├── pom.xml
    └── src
        └── main
            ├── java
            │   └── com
            │       └── example
            │           └── HelloServlet.java
            └── webapp
                ├── WEB-INF
                │   └── web.xml
                └── index.jsp
    
  2. Implementa la aplicación con el comando gcloud app deploy:

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

    Reemplaza lo siguiente:

    • LOCATION por la región de Google Cloud en la que implementas tu app.
    • PROJECT por el ID de tu proyecto Google Cloud .
    • REPOSITORY por el nombre de tu repositorio de Artifact Registry
    • IMAGE por la URL de la imagen de contenedor
    • TAG por la etiqueta de la imagen de contenedor

Usa un entorno de ejecución de Java integrado

En las siguientes instrucciones, se muestra cómo volver a empaquetar una aplicación de Java 8 de App Engine con un servidor integrado (Jetty) para que se ejecute como un archivo JAR independiente en un entorno de ejecución de Java compatible:

  1. Crea un servidor Jetty incorporado
  2. Prepara tu aplicación web de Java 8 (archivo WAR)
  3. Ejecuta el archivo WAR con Jetty integrado y, luego, implementa tu aplicación

Crea un servidor Jetty integrado

Para empaquetar el archivo WAR de tu aplicación con un servidor Jetty integrado, sigue estos pasos:

  1. Crea una clase Main para inicializar y configurar el servidor Jetty para que ejecute tu archivo WAR. La clase Main configura el puerto del servidor, que tiene el valor predeterminado 8080. También puedes modificar el código fuente para usar un puerto especificado en la variable de entorno PORT. La clase Main configura el controlador WebAppContext para que entregue tu archivo 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. Crea el archivo del proyecto de Maven pom.xml y agrega la siguiente configuración:

    1. Establece las propiedades maven.compiler.source y maven.compiler.target en un entorno de ejecución de Java compatible:

      <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. Agrega dependencias para 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. Configura la propiedad maven-assembly-plugin para empaquetar dependencias:

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

    El directorio de tu proyecto debería ser similar a la siguiente estructura:

    ├─src
    │  └─main
    │      └─java
    │          └─jetty
    │              └─Main.java
    └─pom.xml
    
  3. Ejecuta el comando mvn install en el directorio del proyecto del ejecutor de Jetty. Esto genera el jetty-jar-with-dependencies.jar en tu directorio de destino.

  4. Sigue las instrucciones de la sección Prepara tu aplicación web de Java 8 (archivo WAR) para crear un archivo WAR.

Ejecuta el archivo WAR con Jetty integrado y, luego, implementa tu aplicación

En esta sección, se proporcionan los pasos para empaquetar tu aplicación en un archivo JAR ejecutable. Sigue estas instrucciones para empaquetar e implementar tu aplicación:

  1. Coloca el archivo JAR del ejecutor de Jetty generado jetty-jar-with-dependencies.jar y el archivo WAR de tu aplicación HelloWorldApp-1.0.war en el mismo directorio.

  2. Ejecuta la aplicación con un entorno de ejecución de Java compatible:

        java -jar jetty-jar-with-dependencies.jar HelloWorldApp-1.0.war
    
    1. En tu navegador web, ve a http://localhost:8080. Deberías ver la página de bienvenida de tu aplicación.
  3. Crea un elemento entrypoint en el archivo app.yaml para llamar al archivo jetty-jar-with-dependencies y pasar el archivo WAR como argumento. La versión que especifiques en el archivo WAR debe ser la misma que la del archivo 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. Implementa la aplicación con el comando gcloud app deploy.