Empacotar novamente um arquivo WAR em um arquivo JAR

Este documento descreve como reempacotar um aplicativo Java 8 como um arquivo JAR para ser executado em um ambiente de execução Java compatível. Para usar um ambiente de execução Java compatível, é possível incorporar um servidor como o Jetty ou conteinerizar o aplicativo com o Docker para um ambiente de execução personalizado sem reescrever completamente o aplicativo. É possível executar seus aplicativos WAR atuais em plataformas Java modernas ou ambientes de nuvem flexíveis. Escolha entre os seguintes métodos que melhor se adequam à sua estratégia de implantação e infraestrutura:

Preparar o aplicativo da Web Java 8 (arquivo WAR)

Antes de reempacotar o aplicativo Java 8 como um arquivo JAR compatível, é necessário criar um arquivo WAR. Esta seção fornece um exemplo de aplicativo Java 8 que cria um arquivo WAR. Siga as instruções para criar um aplicativo hello-world em Java 8:

  1. Crie um arquivo HelloServlet.java no diretório de origem:

    
    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. Crie um arquivo descritor de implantação web.xml para configurar seu aplicativo da 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. Crie uma 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. Adicione o código a seguir ao arquivo pom.xml para definir o build do seu aplicativo Java 8:

    • Configuração de empacotamento WAR:

      <groupId>com.example</groupId>
      <artifactId>HelloWorldApp</artifactId>
      <version>1.0</version>
      <packaging>war</packaging>
      
    • Plug-in maven-war-plugin com a origem e o destino maven.compiler definidos como versão 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>
      
    • Dependência javax.servlet-api:

      <dependencies>
          <dependency>
              <groupId>javax.servlet</groupId>
              <artifactId>javax.servlet-api</artifactId>
              <version>3.1.0</version>
              <scope>provided</scope>
          </dependency>
      </dependencies>
      
    • Configuração do 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>
      

    O diretório do projeto precisa ser semelhante à seguinte estrutura:

    ├── pom.xml
    └── src
        └── main
            ├── java
            │   └── com
            │       └── example
            │           └── HelloServlet.java
            └── webapp
                ├── WEB-INF
                │   └── web.xml
                └── index.jsp
    
    
  5. Execute mvn install no diretório do projeto do aplicativo para gerar o arquivo WAR HelloWorldApp-1.0.war no diretório de destino.

Usar Dockerfiles para implantar o aplicativo (recomendado)

Os ambientes de execução personalizados são adequados para plataformas que aceitam contêineres personalizados, como os ambientes de execução personalizados do App Engine. Os ambientes de execução personalizados oferecem flexibilidade ao permitir que você configure o ambiente de execução. Para um exemplo de implantação de ambientes de execução personalizados, consulte Criar um aplicativo de ambiente de execução personalizado no ambiente flexível do App Engine.

As instruções a seguir descrevem como criar um contêiner do seu aplicativo Java 8 usando um Dockerfile:

  1. Preparar o aplicativo da Web Java 8 (arquivo WAR)
  2. Crie a imagem do contêiner e envie-a para o Artifact Registry.
  3. Implantar o aplicativo

Crie a imagem do contêiner e envie-a para o Artifact Registry

Nesta seção, descrevemos como criar uma imagem do Docker usando o Cloud Build e enviá-la para um repositório do Artifact Registry. Siga estas etapas para criar uma imagem de contêiner do seu aplicativo:

  1. Crie um arquivo cloudbuild.yaml no diretório de origem para criar a imagem do Docker e envie para o 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"
    

    Substitua:

    • LOCATION com a região Google Cloud em que você implanta o app.
    • PROJECT com seu Google Cloud ID do projeto.
    • REPOSITORY pelo nome do repositório do Artifact Registry.
    • IMAGE com o URL da imagem do contêiner.
    • TAG com a tag da imagem do contêiner.
  2. Crie um Dockerfile com a seguinte configuração:

    
    # 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. Faça o download e instale o Docker para testar o app de amostra e execute o contêiner Hello World na sua máquina local.

  4. Crie a imagem do contêiner e envie-a para o Artifact Registry:

    gcloud builds submit .
    

Implantar aplicativo

Para implantar o aplicativo do App Engine:

  1. Configure o arquivo app.yaml para usar um ambiente de execução personalizado no diretório de origem:

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

    O diretório do projeto precisa ser semelhante à seguinte estrutura:

    ├── Dockerfile
    ├── README.md
    ├── app.yaml
    ├── cloudbuild.yaml
    ├── pom.xml
    └── src
        └── main
            ├── java
            │   └── com
            │       └── example
            │           └── HelloServlet.java
            └── webapp
                ├── WEB-INF
                │   └── web.xml
                └── index.jsp
    
  2. Implante o aplicativo usando o comando gcloud app deploy:

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

    Substitua:

    • LOCATION com a região Google Cloud em que você implanta o app.
    • PROJECT com seu Google Cloud ID do projeto.
    • REPOSITORY pelo nome do repositório do Artifact Registry.
    • IMAGE com o URL da imagem do contêiner.
    • TAG com a tag da imagem do contêiner.

Usar um ambiente de execução Java incorporado

As instruções a seguir demonstram como reempacotar um aplicativo Java 8 do App Engine com um servidor incorporado (Jetty) para ser executado como um JAR independente em um ambiente de execução Java compatível:

  1. Criar um servidor Jetty incorporado
  2. Preparar o aplicativo da Web Java 8 (arquivo WAR)
  3. Execute o arquivo WAR com o Jetty incorporado e implante o aplicativo

Criar um servidor Jetty incorporado

Para agrupar o arquivo WAR do aplicativo com um servidor Jetty incorporado, siga estas etapas:

  1. Crie uma classe Main para inicializar e configurar o servidor Jetty para executar seu arquivo WAR. A classe Main configura a porta do servidor, que é 8080 por padrão. Também é possível modificar o código-fonte para usar uma porta especificada na variável de ambiente PORT. A classe Main configura o gerenciador WebAppContext para veicular seu arquivo 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. Crie o arquivo de projeto do Maven pom.xml e adicione a seguinte configuração:

    1. Defina as propriedades maven.compiler.source e maven.compiler.target para um ambiente de execução Java compatível:

      <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. Adicione dependências do 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. Configure a propriedade maven-assembly-plugin para empacotar dependências:

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

    O diretório do projeto precisa ser semelhante à seguinte estrutura:

    ├─src
    │  └─main
    │      └─java
    │          └─jetty
    │              └─Main.java
    └─pom.xml
    
  3. Execute o comando mvn install no diretório do projeto do executor do Jetty. Isso gera o jetty-jar-with-dependencies.jar no diretório de destino.

  4. Siga as instruções na seção Preparar o aplicativo da Web Java 8 (arquivo WAR) para criar um arquivo WAR.

Execute o arquivo WAR com o Jetty incorporado e implante o aplicativo.

Esta seção mostra as etapas para empacotar seu aplicativo em um arquivo JAR executável. Siga estas instruções para empacotar e implantar seu aplicativo:

  1. Coloque o JAR do executor do Jetty gerado jetty-jar-with-dependencies.jar e o arquivo WAR do aplicativo HelloWorldApp-1.0.war no mesmo diretório.

  2. Execute o aplicativo usando um ambiente de execução Java compatível:

        java -jar jetty-jar-with-dependencies.jar HelloWorldApp-1.0.war
    
    1. No navegador da Web, acesse http://localhost:8080. A página de boas-vindas do aplicativo vai aparecer.
  3. Crie um elemento entrypoint no arquivo app.yaml para chamar o arquivo jetty-jar-with-dependencies e insira o arquivo WAR como um argumento. A versão especificada no arquivo WAR precisa ser a mesma do arquivo 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. Implante o aplicativo usando o comando gcloud app deploy.