El desarrollo de aplicaciones Java ha empezado a prestarle realmente atención la obtención de un alto rendimiento con los recursos disponibles. En Java, existen opciones como Quarkus y Micronaut para lograr conseguir esa mayor eficiencia. Existe una tercera opción llamada Spring Native, que permite construir aplicaciones nativas de manera mínimamente disruptiva utilizando el conocido framework Spring Boot.
Spring Native, proyecto que dejó de ser experimental y en su versión oficial, liberado de la mano de Spring Boot 3 y Spring Framework 6, utiliza el compilador GraalVM para compilar aplicaciones Spring y generar ejecutables nativos con mejores tiempos de arranque, menor consumo de memoria y mejor rendimiento, especialmente beneficioso para arquitecturas de microservicios.
El artículo del día de hoy es una evolución del caso práctico visto en una entrada anterior, en el que todavía Spring Native estaba en fase de pruebas, de tal forma que ahora se construirá una versión incorporando la versión oficial productiva de Spring Native liberada con Spring Boot 3 de modo que podamos adaptar las aplicaciones con las nuevas configuraciones ya que no serán iguales a las que se tenían en fases experimentales.
Prerrequisitos
- Recomendable, ordenador con un mínimo de 12 GB RAM.
- JDK 17+ (JAVA_HOME Configurado)
- Apache Maven 3.3.9+
- IDE (IntelliJ IDEA, Eclipse o cualquier IDE de desarrollo)
- Base de datos (Para este ejemplo la imagen Oracle XE Docker)
- Docker para la construcción de aplicaciones nativas en imágenes.
- GraalVM para la construcción de imágenes nativas
Para el caso práctico se escoge una base de datos cuya dependencia para el compilador genere problemas de reflexión al momento generar o ejecutar la versión nativa de la aplicación para poder incluir Hints de Spring Native como solución.
Tips
Usuarios Windows
Para conseguir un proceso de construcción más fácil, en cuanto a tiempo y consumo de recursos, aconsejo utilizar un sistema operativo Linux. Ayudaremos a conseguir esto con Windows Subsystem for Linux (WSL): Guía para instalación.
Todos
Una vez disponible el OS Linux, mi recomendación es Ubuntu (una de las distribuciones de Linux más populares y ampliamente utilizadas), se procede a instalar Docker para este OS.
Otra herramienta que recomiendo para terminar de conseguir entornos de desarrollos más organizados y multifuncional es utilizar SDKMAN, herramienta para administrar en paralelo versiones de Kits de Desarrollo de Software en la mayoría de los sistemas basados en Unix, y que permitirá la instalación y utilización de GraalVM de forma más rápida.
Instalación SDKMAN
sudo apt-get update
sudo apt-get install curl
sudo apt-get install zip unzip
curl -s "https://get.sdkman.io" | bash
Instalación GraalVM
sdk install java 22.3.r17-nik
sdk use java 22.3.r17-nik
Instrucciones instalar GraalVM para Windows aquí.
Verificar Instalaciones
Visualizar versión Java
java -version
Output:
openjdk version "17.0.5" 2022-10-18 LTS
OpenJDK Runtime Environment GraalVM 22.3.0 (build 17.0.5+8-LTS)
OpenJDK 64-Bit Server VM GraalVM 22.3.0 (build 17.0.5+8-LTS, mixed mode, sharing)
Lista de los componentes instalados en GraalVM
gu list
Output:
ComponentId Version Component name Stability
----------------------------------------------------------------------------------------------------------
graalvm 22.3.0 GraalVM Core Supported
native-image 22.3.0 Native Image Early adopter
Generación Applicación Base
Inicialización
La manera más fácil de empezar con la construcción de la aplicación es utilizar Spring Initilizr, abrir la página y se escogen las siguientes opciones:
Paso | Opción | Descripción |
---|---|---|
1 | Tipo Proyecto | Maven |
2 | Lenguaje | Java |
3 | Versión Spring Boot | 3.0.6 |
4 | Metadata del proyecto | Editar los valores para ajustarlo al ejemplo |
5 | Versión Java | 17 |
6 | Dependencias | Seleccionar Native Support y Spring Web |
7 | Generar | Crear proyecto con las especificaciones seleccionadas |
Código Base
Agregar el sub-paquete controller al paquete base de la aplicación y crear dentro la clase SignalController.
@RestController
@RequestMapping("/services/v1")
public class SignalController {
@GetMapping("/signals")
public String getSignals() {
return "Hello from enmilocal";
}
}
Modificar la extensión del archivo application.properties a application.yaml. Luego agregar las siguientes configuraciones:
server:
port: 7080
spring:
devtools:
restart:
enabled: 'true'
En este punto, se compila e inicia la aplicación a través de comando MVN o del IDE de desarrollo para probar que la aplicación responda con el controlador básico creado.
Ejecutar con Spring Boot
mvn spring-boot:run
Ejecutar con Maven
mvn clean package
java -jar ./target/<ARTEFACT_NAME>.jar
Reemplazar ARTEFACT_NAME con el asignado en el POM de la aplicación.
Probar aplicación
curl http://localhost:7080/services/v1/signals
Output:
Hello from enmilocal
Creación y ejecución artefacto nativo
Para la creación del ejecutable nativo utilizaremos 2 aproximaciones, la primera utilizando Docker y la segunda utilizando directamente GraalVM.
Ambos procesos toman un tiempo significativo ya que la construcción requiere una cantidad considerable de recursos y tiempo.
Creación imagen Docker con ejecutable nativo
mvn -Pnative spring-boot:build-image -Dspring-boot.build-image.imageName=onoriel/demo-native:latest
Ejecución:
docker run --rm -p 7080:7080 onoriel/workshop-native
Creación ejecutable nativo con GraalVM
mvn -Pnative native:compile
Ejecución:
./target/demo-native
El nombre final del artefacto corresponde al asignado en el POM de la aplicación.
Agregar Native Swagger UI
Para facilitar las pruebas se utilizará Swagger UI para contar con un interfaz gráfica para interactuar de una forma más amigable con el API que define la aplicación.
Agregar la propiedad con la versión swagger al POM de la aplicación. En el tag de
<springdoc-openapi.version>2.0.2</springdoc-openapi.version>
Agregar las dependencias necesarias para incluir la funcionalidad al POM de la aplicación. En el tag de
<!-- SWAGGER -->
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
<version>${springdoc-openapi.version}</version>
</dependency>
<!-- Spring Validation -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
Crear el archivo de configuración para especificar las propiedades del API de la aplicación. Para ello, agregar el sub-paquete config al paquete base de la aplicación y crear dentro la clase AppSwaggerConfig.
@Configuration
public class AppSwaggerConfig {
@Bean
public GroupedOpenApi publicApi() {
return GroupedOpenApi.builder()
.displayName("Signals")
.group("io.enmilocalfunciona.native")
.pathsToMatch("/services/**")
.build();
}
@Bean
public OpenAPI springShopOpenAPI() {
Contact contact = new Contact();
contact.setEmail("info@onoriel.es");
contact.setName("ONORIEL");
contact.setUrl("https://www.onoriel.com");
return new OpenAPI()
.info(new Info().title("Native REST Api Application - Signals")
.description("Documentation REST API")
.version("Version 1.0.0")
.contact(contact)
.license(new License().name("License of Api for General use")))
.externalDocs(new ExternalDocumentation()
.description("Terms of service base into company terms of use"));
}
}
Agregar las propiedades relacionadas con Native Swagger UI modificando en el archivo application.yaml de la aplicación y agregando:
springdoc:
enable-native-support: 'true'
swagger-ui:
path: /swagger-ui
Probar cambios
Para la construcción y ejecución del artefacto nativo repetimos el proceso documentado anteriormente.
Una vez iniciada la aplicación bastará con abrir un navegador, navegar hacia el siguiente enlace e interactuar con el API:
http://localhost:7080/swagger-ui
Agregar Persistencia H2
Agregar las dependencias necesarias para incluir la persistencia en memoria H2 al POM de la aplicación. En el tag de
<!-- H2 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
</dependency>
Agregar las propiedades relacionadas con H2 modificando en el archivo application.yaml de la aplicación y agregando:
spring:
h2:
console.enabled: 'true'
datasource:
driver-class-name: org.h2.Driver
url: jdbc:h2:mem:testdb
username: sa
password:
Crear la clase modelo de la aplicación para transportar la información de las entidades. Para ello, agregar el sub-paquete model al paquete base de la aplicación y crear dentro la clase Signal.
public record Signal (String name){
}
Crear la clase repostorio de la aplicación para gobernar la información de las entidades. Para ello, agregar el sub-paquete repository al paquete base de la aplicación y crear dentro la clase SignalRepository.
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.stereotype.Repository;
import com.atsistemas.model.Signal;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;
@Repository
public class SignalRepository {
@Autowired
JdbcTemplate jdbcTemplate;
static class SignalRowMapper implements RowMapper<Signal> {
@Override
public Signal mapRow(ResultSet rs, int rowNum) throws SQLException {
Signal signal = new Signal(rs.getString("name"));
return signal;
}
}
public List<Signal> findAll() {
return jdbcTemplate.query("SELECT * FROM SIGNALS", new SignalRowMapper());
}
}
Modificar el controlador de la aplicación para que pueda utilizar el objeto repository:
// Agregar
@Autowired
SignalRepository repository;
//modificar
@GetMapping("/signals")
public List<Signal> getSignals() {
return repository.findAll();
}
Agregar un nuevo recurso sobre la ruta <src/main/resources> con nombre schema.sql con el siguiente contenido:
CREATE TABLE IF NOT EXISTS SIGNALS (
id INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
name VARCHAR(30)
);
Agregar un nuevo recurso sobre la ruta <src/main/resources> con nombre data.sql con el siguiente contenido:
delete from SIGNALS;
insert into SIGNALS values(1,'E1234567');
insert into SIGNALS values(2,'A1234568');
Probar cambios
Para la construcción y ejecución del artefacto nativo repetimos el proceso documentado anteriormente.
Una vez iniciada la aplicación bastará con abrir un navegador, navegar hacia el siguiente enlace e interactuar con el API:
http://localhost:7080/swagger-ui
Agregar Persistencia Oracle
Iniciar Servicio Oracle
Bastará con crear un contenedor utilizando una imagen Docker de la versión Oracle XE 11g.
docker run -d -p 8161:1521 -p 8080:8080 oracleinanutshell/oracle-xe-11g
URL [localhost:8161/xe] | Credenciales (system/oracle)
Ahora, conectar con la base de datos (por ejemplo, utilizando Oracle SQL Developer ) y crear la tabla de Signals:
CREATE TABLE SIGNALS
(
ID NUMBER
, NAME VARCHAR2(20 BYTE)
) ;
Cambios en la aplicación
Eliminar el archivo schema.sql de la ruta <src/main/resources>.
Agregar la propiedad con la versión de las dependencias Oracle al POM de la aplicación. En el tag de
<oracle.ojdbc.version>21.7.0.0</oracle.ojdbc.version>
Agregar las dependencias necesarias para incluir la persistencia Oracle al POM de la aplicación. En el tag de
<!-- JDBC -->
<dependency>
<groupId>com.oracle.database.jdbc</groupId>
<artifactId>ojdbc11</artifactId>
<version>${oracle.ojdbc.version}</version>
</dependency>
Agregar/modificar las propiedades relacionadas con Oracle modificando en el archivo application.yaml de la aplicación y agregando:
spring:
datasource:
driver-class-name: oracle.jdbc.driver.OracleDriver
userName: system
url: jdbc:oracle:thin:@${spring.datasource.plain-url}
plain-url: localhost:8161/xe
password: oracle
sql:
init:
mode: always
Probar cambios
Para la construcción y ejecución del artefacto nativo repetimos el proceso documentado anteriormente.
En este punto encontramos un error al tratar de ejecutar el artefacto nativo, ya sea a través de la imagen Docker generada o del mismo ejecutable creado por GraalVM, donde nos dice que no puede resolver algunas configuraciones native de reflexión:
AOT Hints
Para resolver el anterior error necesitamos indicarle a GraalVM que incluya algunos valores de configuraciones en el momento de construcción del artefacto que son resueltos por reflexión por la aplicación en tiempo de ejecución. De esta forma el artefacto contará con todas las dependencias necesarias para su correcto funcionamiento y que fueron ignoradas por GraalVM ya que no pudo detectar su dependencia por la configuración por defecto y análisis inicial realizado.
Primeramente, se debe crear una clase hints con las configuraciones ad-hoc necesarias para la aplicación. Para ello, agregar el sub-paquete hints al paquete base de la aplicación y crear dentro la clase WorkshopNativeHints.
import org.springframework.aot.hint.MemberCategory;
import org.springframework.aot.hint.RuntimeHints;
import org.springframework.aot.hint.RuntimeHintsRegistrar;
import oracle.jdbc.driver.OracleDriver;
public class WorkshopNativeHints implements RuntimeHintsRegistrar {
@Override
public void registerHints(RuntimeHints hints, ClassLoader classLoader) {
hints.reflection().registerType(OracleDriver.class, MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS);
}
}
Modificar la clase principal de la aplicación Spring Boot (main class) para importar la nueva definición de WorkshopNativeHints.
//Agregar en las anotaciones de la clase
@ImportRuntimeHints(WorkshopNativeHints.class)
Probar cambios
Para la construcción y ejecución del artefacto nativo repetimos el proceso documentado anteriormente.
Una vez iniciada la aplicación bastará con abrir un navegador, navegar hacia el siguiente enlace e interactuar con el API. Esta vez no debería presentarse ningún problema en su ejecución:
http://localhost:7080/swagger-ui
Conclusiones
Con este nuevo artículo se ha podido desarrollar una guía básica de desarrollo de aplicaciones Spring Boot 3.0 con características nativas y utilizando herramientas del framework (Hints) que permiten la configuración de los artefactos para incluir las dependencias necesarias para su construcción y el correcto funcionamiento en modo nativo. Con esto podemos alinear nuestro conocimiento con las nuevas funcionalidades nativas de Spring Boot 3.0 y su liberación oficial de Spring Native y empezar a construir o migrar aplicaciones Java que saquen el mayor rendimiento optimizando los recursos y tiempos de nuestro ecosistema de aplicaciones.
Código fuente Github.