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.
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>
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>
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ñ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>
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>
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.
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:
@ServletComponentScan escanea los componentes web anotados de OpenXava y NaviOX.SpringBootServletInitializer mantiene la aplicación desplegable como un WAR.NaviOXFilter se registra manualmente porque OpenXava lo define en un web fragment,
no como un bean de Spring.ServerEndpointExporter registra el endpoint WebSocket del chat de OpenXava
ChatEndpoint en el Tomcat embebido.tomcat.enableNaming() habilita JNDI en el Tomcat embebido.postProcessContext() es donde se define el datasource para Spring Boot.
Crea el datasource JNDI que OpenXava espera desde persistence.xml.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().
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
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