Volver a empaquetar un archivo WAR en un archivo JAR

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

Preparar tu aplicación web Java 8 (archivo WAR)

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

  1. Crea un archivo HelloServlet.java en el directorio de origen:

    
    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 de 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. Añade el siguiente código al archivo pom.xml para definir la compilación de tu aplicación Java 8:

    • Configuración del empaquetado de WAR:

      <groupId>com.example</groupId>
      <artifactId>HelloWorldApp</artifactId>
      <version>1.0</version>
      <packaging>war</packaging>
      
    • Complemento maven-war-plugin con el origen maven.compiler y el destino definidos 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>
      
    • javax.servlet-api dependencia:

      <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 tener una estructura similar a la siguiente:

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

Usar Dockerfiles para desplegar la aplicación (opción recomendada)

Los entornos de ejecución personalizados son adecuados para plataformas que admiten contenedores personalizados, como los entornos de ejecución personalizados de App Engine. Los entornos de ejecución personalizados ofrecen flexibilidad, ya que te permiten configurar el entorno de ejecución. Para ver un ejemplo de cómo implementar tiempos de ejecución personalizados, consulta Crear una aplicación de tiempo 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 Java 8 mediante un Dockerfile:

  1. Preparar una aplicación web Java 8 (archivo WAR)
  2. Crea la imagen de contenedor y envíala a Artifact Registry
  3. Desplegar una aplicación

Crea la imagen de contenedor y envíala a Artifact Registry

En esta sección se describe cómo crear una imagen 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 el directorio de origen para crear 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"
    

    Sustituye:

    • LOCATION con la Google Cloud región en la que implementes tu aplicación.
    • 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 del contenedor.
    • TAG con la etiqueta de tu 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 aplicación de ejemplo y ejecuta el contenedor Hello World en tu máquina local.

  4. Crea la imagen de contenedor y envíala a Artifact Registry:

    gcloud builds submit .
    

Desplegar una aplicación

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

  1. Configura el archivo app.yaml para que use un tiempo 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 tener una estructura similar a la siguiente:

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

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

    Sustituye:

    • LOCATION con la Google Cloud región en la que implementes tu aplicación.
    • 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 del contenedor.
    • TAG con la etiqueta de tu imagen de contenedor.

Usar un entorno de ejecución de Java insertado

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

  1. Crear un servidor Jetty insertado
  2. Preparar una aplicación web Java 8 (archivo WAR)
  3. Ejecutar el archivo WAR con Jetty insertado e implementar la aplicación

Crear un servidor Jetty insertado

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

  1. Crea una clase Main para inicializar y configurar el servidor Jetty para ejecutar 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 servir 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 de proyecto de Maven pom.xml y añade la siguiente configuración:

    1. Asigna a las propiedades maven.compiler.source y maven.compiler.target un tiempo 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. Añade las dependencias de 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 las 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 tener una estructura similar a la siguiente:

    ├─src
    │  └─main
    │      └─java
    │          └─jetty
    │              └─Main.java
    └─pom.xml
    
  3. Ejecuta el comando mvn install en el directorio del proyecto Jetty Runner. De esta forma, se genera el jetty-jar-with-dependencies.jar en el directorio de destino.

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

Ejecutar el archivo WAR con Jetty insertado y desplegar la aplicación

En esta sección se explica cómo empaquetar tu aplicación en un archivo JAR ejecutable. Sigue estas instrucciones para empaquetar y desplegar 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 tu archivo app.yaml para llamar al archivo jetty-jar-with-dependencies y pasa tu 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. Despliega tu aplicación con el comando gcloud app deploy.