openxava / documentación / Spring Boot

×Novedad: Spring Boot con OpenXava - 25 de mayo · Leer más

Las aplicaciones OpenXava son aplicaciones web Java estándar. Tradicionalmente se empaquetan como un WAR y se ejecutan en un Tomcat externo. Sin embargo, también puedes añadir Spring Boot a una aplicación OpenXava existente para que pueda arrancar con un Tomcat embebido, desde un método main() de Java, manteniendo la estructura habitual de OpenXava.

En este tutorial vamos a partir de un proyecto OpenXava normal, por ejemplo uno creado con el arquetipo Maven de OpenXava, y a añadir la configuración mínima necesaria para ejecutarlo sobre Spring Boot.

Usar Spring Boot 2 con OpenXava 7

OpenXava 7.x usa las APIs Java EE javax.*. Por esta razón, usa Spring Boot 2.x, no Spring Boot 3.x, porque Spring Boot 3 usa las APIs Jakarta EE jakarta.*.

Los ejemplos siguientes usan OpenXava 7.7.2 y Spring Boot 2.7.18. Añade estas propiedades al archivo pom.xml de tu proyecto, sustituyendo la sección <properties> existente si es necesario:

<properties>
    <openxava.version>7.7.2</openxava.version>
    <spring-boot.version>2.7.18</spring-boot.version>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.source>17</maven.compiler.source>
    <maven.compiler.target>17</maven.compiler.target>
</properties>

Añadir Spring Boot a pom.xml

Primero, añade la gestión de dependencias de Spring Boot al archivo pom.xml de tu proyecto, al mismo nivel que <dependencies> y <build>:

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-dependencies</artifactId>
            <version>${spring-boot.version}</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

Después añade estas dependencias a la sección <dependencies> del archivo pom.xml de tu proyecto. El starter web de Spring Boot añade el servidor web embebido, spring-boot-starter-websocket añade soporte para el chat de OpenXava, mientras que tomcat-embed-jasper añade soporte para páginas JSP. OpenXava usa JSPs, por lo que tomcat-embed-jasper es necesario cuando se ejecuta con Tomcat embebido:

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-websocket</artifactId>
    </dependency>

    <dependency>
        <groupId>org.apache.tomcat.embed</groupId>
        <artifactId>tomcat-embed-jasper</artifactId>
    </dependency>

    <dependency>
        <groupId>org.openxava</groupId>
        <artifactId>openxava</artifactId>
        <version>${openxava.version}</version>
    </dependency>

    <!-- Otras dependencias usadas por tu proyecto -->
</dependencies>

Mantener las librerías de Tomcat en el WAR

Un proyecto OpenXava normal excluye las librerías de Tomcat del WAR generado porque espera ejecutarse en un Tomcat externo. Con el Tomcat embebido de Spring Boot necesitas esas librerías, por lo que debes eliminar esta exclusión de <packagingExcludes> en el maven-war-plugin del archivo pom.xml de tu proyecto si está presente:

WEB-INF/lib/tomcat-*.jar

El maven-war-plugin del archivo pom.xml de tu proyecto puede seguir excluyendo otras librerías, pero no las de Tomcat:

<plugin>
    <artifactId>maven-war-plugin</artifactId>
    <version>3.3.2</version>
    <configuration>
        <packagingExcludes>WEB-INF/lib/ecj-3*.jar,
            WEB-INF/lib/fontbox-*.jar,
            WEB-INF/lib/htmlunit-*.jar,
            WEB-INF/lib/httpclient-*.jar,
            WEB-INF/lib/httpcore-*.jar,
            WEB-INF/lib/httpmime-*.jar,
            WEB-INF/lib/jetty-*.jar,
            WEB-INF/lib/junit-*.jar,
            WEB-INF/lib/neko-htmlunit-*.jar,
            WEB-INF/lib/pdfbox-*.jar,
            WEB-INF/lib/serializer-*.jar,
            WEB-INF/lib/websocket-*.jar,
            WEB-INF/lib/xalan-*.jar,
            WEB-INF/lib/xercesImpl-*.jar,
            WEB-INF/lib/xml-apis-*.jar</packagingExcludes>
    </configuration>
</plugin>

Añadir el plugin Maven de Spring Boot

Añade este plugin Maven de Spring Boot a la sección <plugins> del archivo pom.xml de tu proyecto, indicando la clase principal que crearemos en la siguiente sección. En este ejemplo la aplicación se llama invoicing y la clase principal es com.yourcompany.invoicing.InvoicingApplication:

<plugin>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-maven-plugin</artifactId>
    <version>${spring-boot.version}</version>
    <configuration>
        <mainClass>com.yourcompany.invoicing.InvoicingApplication</mainClass>
    </configuration>
</plugin>

Ejemplo completo de pom.xml

Después de aplicar todos los cambios explicados arriba, el archivo pom.xml de tu proyecto podría quedar así. Adapta groupId, artifactId, version, finalName y la clase principal de Spring Boot a tu propia aplicación:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">

    <modelVersion>4.0.0</modelVersion>

    <groupId>com.yourcompany</groupId>
    <artifactId>invoicing</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>war</packaging>

    <properties>
        <openxava.version>7.7.2</openxava.version>
        <spring-boot.version>2.7.18</spring-boot.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>17</maven.compiler.source>
        <maven.compiler.target>17</maven.compiler.target>
    </properties>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>${spring-boot.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-websocket</artifactId>
        </dependency>

        <dependency>
            <groupId>org.apache.tomcat.embed</groupId>
            <artifactId>tomcat-embed-jasper</artifactId>
        </dependency>

        <dependency>
            <groupId>org.openxava</groupId>
            <artifactId>openxava</artifactId>
            <version>${openxava.version}</version>
        </dependency>

        <dependency>
            <groupId>org.openxava</groupId>
            <artifactId>openxava-7.7-chat-jdk17</artifactId>
            <version>${openxava.version}</version>
        </dependency>
    </dependencies>

    <build>
        <finalName>invoicing</finalName>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-dependency-plugin</artifactId>
                <executions>
                    <execution>
                        <id>unpack</id>
                        <phase>package</phase>
                        <goals>
                            <goal>unpack</goal>
                        </goals>
                        <configuration>
                            <artifactItems>
                                <artifactItem>
                                    <groupId>org.openxava</groupId>
                                    <artifactId>openxava</artifactId>
                                    <version>${openxava.version}</version>
                                    <outputDirectory>src/main/resources</outputDirectory>
                                    <includes>xava/dtds/*</includes>
                                </artifactItem>
                            </artifactItems>
                        </configuration>
                    </execution>
                </executions>
            </plugin>

            <plugin>
                <artifactId>maven-war-plugin</artifactId>
                <version>3.3.2</version>
                <configuration>
                    <packagingExcludes>WEB-INF/lib/ecj-3*.jar,
                        WEB-INF/lib/fontbox-*.jar,
                        WEB-INF/lib/htmlunit-*.jar,
                        WEB-INF/lib/httpclient-*.jar,
                        WEB-INF/lib/httpcore-*.jar,
                        WEB-INF/lib/httpmime-*.jar,
                        WEB-INF/lib/jetty-*.jar,
                        WEB-INF/lib/junit-*.jar,
                        WEB-INF/lib/neko-htmlunit-*.jar,
                        WEB-INF/lib/pdfbox-*.jar,
                        WEB-INF/lib/serializer-*.jar,
                        WEB-INF/lib/websocket-*.jar,
                        WEB-INF/lib/xalan-*.jar,
                        WEB-INF/lib/xercesImpl-*.jar,
                        WEB-INF/lib/xml-apis-*.jar</packagingExcludes>
                </configuration>
            </plugin>

            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>3.2.5</version>
                <configuration>
                    <skipTests>true</skipTests>
                </configuration>
            </plugin>

            <plugin>
                <groupId>org.codehaus.mojo</groupId>
                <artifactId>exec-maven-plugin</artifactId>
                <version>3.1.0</version>
                <configuration>
                    <mainClass>${project.groupId}.${project.artifactId}.run.${project.artifactId}</mainClass>
                </configuration>
            </plugin>

            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <version>${spring-boot.version}</version>
                <configuration>
                    <mainClass>com.yourcompany.invoicing.InvoicingApplication</mainClass>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

Crear application.properties

Crea src/main/resources/application.properties. Define el context path con el nombre de tu aplicación OpenXava. Debe coincidir con el nombre en xava/application.xml y con el nombre final del WAR si quieres mantener la misma URL cuando uses un Tomcat externo:

server.servlet.context-path=/invoicing
server.tomcat.additional-tld-skip-patterns=htmlunit-*.jar,jetty-*.jar

La entrada additional-tld-skip-patterns evita escanear algunas librerías que no son relevantes para las librerías de etiquetas JSP.

Crear la clase de aplicación Spring Boot

Crea una clase en el paquete raíz de tu aplicación. Para una aplicación con paquetes bajo com.yourcompany.invoicing, crea src/main/java/com/yourcompany/invoicing/InvoicingApplication.java:

package com.yourcompany.invoicing;

import java.util.EnumSet;

import javax.servlet.DispatcherType;

import org.apache.catalina.Context;
import org.apache.catalina.startup.Tomcat;
import org.apache.tomcat.util.descriptor.web.ContextResource;
import org.openxava.chat.ChatEndpoint;
import org.openxava.util.DBServer;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
import org.springframework.boot.web.embedded.tomcat.TomcatWebServer;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.boot.web.servlet.ServletComponentScan;
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
import org.springframework.context.annotation.Bean;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;

import com.openxava.naviox.web.NaviOXFilter;

@SpringBootApplication
@ServletComponentScan(basePackages = { "org.openxava", "com.openxava" })
public class InvoicingApplication extends SpringBootServletInitializer implements WebMvcConfigurer {

    public static void main(String[] args) throws Exception {
        DBServer.start("invoicing-db");
        SpringApplication.run(InvoicingApplication.class, args);
    }

    @Override
    protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
        return application.sources(InvoicingApplication.class);
    }

    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController("/").setViewName("forward:/index.jsp");
    }

    @Bean
    public FilterRegistrationBean<NaviOXFilter> naviOXFilter() {
        FilterRegistrationBean<NaviOXFilter> registration = new FilterRegistrationBean<>();
        registration.setFilter(new NaviOXFilter());
        registration.setName("naviox");
        registration.addUrlPatterns("*.jsp", "/modules/*", "/phone/index.jsp", "/m/*");
        registration.setDispatcherTypes(EnumSet.of(DispatcherType.REQUEST, DispatcherType.FORWARD));
        return registration;
    }

    @Bean
    public ServerEndpointExporter serverEndpointExporter() {
        ServerEndpointExporter exporter = new ServerEndpointExporter();
        exporter.setAnnotatedEndpointClasses(ChatEndpoint.class);
        return exporter;
    }

    @Bean
    public TomcatServletWebServerFactory tomcatFactory() {
        return new TomcatServletWebServerFactory() {

            @Override
            protected TomcatWebServer getTomcatWebServer(Tomcat tomcat) {
                tomcat.enableNaming();
                return super.getTomcatWebServer(tomcat);
            }

            @Override
            protected void postProcessContext(Context context) {
                ContextResource resource = new ContextResource();
                resource.setName("jdbc/invoicingDS");
                resource.setType("javax.sql.DataSource");
                resource.setProperty("driverClassName", "org.hsqldb.jdbc.JDBCDriver");
                resource.setProperty("url", "jdbc:hsqldb:hsql://localhost:1666");
                resource.setProperty("username", "sa");
                resource.setProperty("password", "");
                resource.setProperty("maxTotal", "20");
                resource.setProperty("maxIdle", "5");
                resource.setProperty("maxWaitMillis", "10000");
                context.getNamingResources().addResource(resource);
            }
        };
    }

}

Hay varios detalles importantes en esta clase:

Esto es importante porque el Tomcat embebido de Spring Boot no usa el archivo src/main/webapp/META-INF/context.xml para crear el datasource. Por tanto, cuando se ejecuta con Spring Boot, el datasource debe definirse programáticamente en postProcessContext(). Si no usas el servidor HSQLDB embebido arrancado por OpenXava durante el desarrollo, elimina la llamada a DBServer.start() y configura tu propio driver JDBC, URL, usuario y contraseña en el ContextResource creado dentro de postProcessContext().

Ejecutar la aplicación

Ahora puedes ejecutar la aplicación desde tu IDE ejecutando la clase lanzadora de Spring Boot. En este tutorial la clase lanzadora es com.yourcompany.invoicing.InvoicingApplication, la clase que contiene el método main() con SpringApplication.run().

También puedes ejecutarla desde Maven con el plugin de Spring Boot, desde la carpeta raíz de tu proyecto:

mvn spring-boot:run

Cuando la aplicación arranque, abre:

http://localhost:8080/invoicing

Resumen

Para ejecutar OpenXava sobre Spring Boot no tienes que reescribir tu aplicación OpenXava. Mantienes tus entidades, controladores, recursos, JSPs, xava/application.xml, persistence.xml y la estructura de aplicación web. Los añadidos esenciales son las dependencias de Spring Boot, el soporte JSP, una clase principal de Spring Boot, la configuración JNDI del Tomcat embebido, el registro manual del filtro NaviOX y un pequeño application.properties.

Con esta configuración tu aplicación OpenXava puede ejecutarse como una aplicación Spring Boot, mientras sigue empaquetándose como un WAR.

Esta integración también te permite usar las herramientas del ecosistema Spring en tu aplicación OpenXava, por ejemplo crear servicios web usando Spring MVC REST.

Descarga y ejecuta un ejemplo completo y funcional basado en este tutorial