Skip to content

spring

Spring Boot 2 (y 5)

Para cerrar el ejemplo de uso básico de Spring Boot 2, en este artículo se revisan algunas formas de empaquetar una aplicación web de cara a su distribución. Decidir utilizar un formato u otro depende de cada caso de uso en concreto, no existe una opción mejor que las demás. En algunos entornos/empresas se sigue requiriendo un WAR/EAR para su despliegue en un servidor de aplicaciones, en otros se requiere un único fichero JAR que se pueda ejecutar con una determinada máquina virtual, y en otros se requiere una imagen de Docker.

War

Spring Boot permite empaquetar una aplicación en un fichero WAR, eliminando el servidor embebido del empaquetado, y que se puede desplegar sobre un servidor de aplicaciones. Lo que quiere decir que lo que se obtiene es un único fichero con la aplicación, con sus dependencias incluidas dentro del propio WAR, o proporcionadas por el servidor de aplicaciones con objeto de ser compartidas por todas las aplicaciones desplegadas en el mismo.

Para generar un WAR hay que cambiar el formato de empaquetado del proyecto en el fichero pom.xml

Para excluir el servidor Tomcat embebido por defecto por Spring Boot dentro de la aplicación hay que añadir una entrada en el fichero pom.xml indicando que las librerías de Tomcat son proporcionadas de forma externa:

Para hacer que la aplicación pueda reaccionar al proceso de inicialización del servidor de aplicaciones, ya que ahora será el servidor y no Spring Boot quien arranque la aplicación, se debe cambiar la clase con el punto de entrada de la aplicación para que extienda de SpringBootServletInitializer y sobreescribir su método configure:

Y por último, para generar el WAR, hay que ejecutar la tarea de empaquetado estándar de Maven:

Como resultado de la ejecución de la tarea se genera un fichero WAR en el directorio \target que puede desplegarse sobre un servidor de aplicaciones de la forma acostumbrada.

A pesar de considerarse un modelo obsoleto, esta forma de distribución se sigue utilizando en muchas empresas. Dejar un fichero en un directorio para que lo desplieguen en un servidor de aplicaciones sigue siendo bastante más habitual de lo que debería.

Nested JARs

Spring Boot permite crear un único fichero JAR que contenga todas las dependencias en un formato propio llamado Nested JARs. Lo que quiere decir que lo que se obtiene es un único fichero, con la aplicación y sus dependencias, y se necesita una máquina virtual para ejecutarlo.

Para generar este tipo de ficheros se debe añadir al fichero pom.xml un plugin de Spring Boot:

Y ejecutar la tarea estándar de empaquetado de Maven:

Como resultado de la ejecución de la tarea se genera un fichero JAR en el directorio \target que puede ejecutarse con la máquina virtual de Java de la forma acostumbrada:

El fichero JAR que genera el plugin de Spring Boot contiene tanto las clases de la aplicación como las librerías de terceros. Es un modelo de empaquetado similar al formato Uber JAR, en el que todas las clases de todas las librerías de terceros se incluyen dentro del JAR como si fueran parte de la aplicación. La diferencia es que Spring Boot incluye los JARs completos, en vez de sólo sus clases, y utiliza un loader propio para poder realizar la carga de dichos JARs.

Es conveniente abrir el JAR generado y examinarlo para entender como está construido. Las clases en el directorio raíz son el cargador de Spring Boot, y las librerías están en un directorio propio llamado BOOT-INF, de forma similar a como se encuentran en el directorio WEB-INF de una aplicación web.

En la práctica, sólo he visto utilizar este modelo de distribución en un proyecto, concretamente para una aplicación de escritorio que se incluía dentro de los terminales de punto de venta de una gran cadena comercial.

Docker

Las aplicaciones empaquetadas en un único fichero son adecuadas para su distribución en entornos cloud. La mayoría de plataformas de computación en la nube permiten configurar una capa por encima de las aplicaciones con la máquina virtual de Java o el servidor de aplicaciones que necesiten.

Si una aplicación se distribuye como un WAR necesita un servidor de aplicaciones y una máquina virtual de Java, pero puede haber problemas en el entorno de producción si hay diferencias entre el software proporcionado por la plataforma y el utilizado en el entorno de desarrollo. De igual forma, si una aplicación se distribuye como un JAR con un servidor embebido, sólo necesita una máquina virtual de Java, pero todavía puede haber problemas si las versiones en los entornos de producción y desarrollo son distintas. Por su parte, en una imagen de Docker se empaqueta tanto la aplicación como todo el software que necesita, consiguiendo que los entornos de desarrollo y producción sean lo más parecidos posibles.

La forma más directa de generar una imagen de Docker es escribir manualmente un fichero de texto plano con nombre Dockerfile, tal cual, sin extensión. En este fichero se indica la imagen base que se quiere utilizar y los pasos necesarios para construir la nueva imagen a partir de la base.

Para nuestro ejemplo necesitamos una imagen que tenga una máquina virtual de Java 10. La imagen oficial proporcionada por OpenJDK en su versión slim está basada en Debian y pesa casi 400Mb, pero para el ejemplo el tamaño no importa.

Spring Boot recomienda utilizar un fichero Dockerfile como el siguiente:

En el fichero se parte de la imagen del OpenJDK, se indica que se cree un directorio temporal, se copie el JAR de la aplicación, y que cuando se ejecute la imagen se levante la maquina virtual de Java para correr la aplicación desde el JAR copiado.

El directorio temporal no es estrictamente necesario, pero Spring Boot recomienda crearlo para garantizar que todas las aplicaciones de Java funcionen correctamente, ya que algunas lo necesitan. El parámetro java.security.egd se configura para hacer que Tomcat arranque más rápido, y lo que hace es definir un fuente de entropía no bloqueante para la generación de números aleatorios necesarios para las librerías de seguridad.

Una vez escrito el fichero Dockerfile lo siguiente es invocar a docker desde línea de comandos para construir la imagen de la forma acostumbrada:

La nueva imagen se listará junto con el resto:

Y se podrá ejecutar por su nombre (tag) haciendo público el puerto en el que se encuentra el servicio:

Si todo el proceso se ejecuta de forma correcta la aplicación arrancará y el servicio estará disponible en el puerto indicado.

Para terminar, comentar que existen otros modelos de distribución a parte de los tres vistos. Uno de ellos es empaquetar la aplicación como un autoejecutable, de forma que pueda ejecutarse sin necesidad de anteponer  java -jar, pero sólo funciona en determinados entornos UNIX.

Spring Boot 2 (4)

Una parte importante de todo desarrollo moderno es la elaboración y ejecución automatizada de pruebas. Y a este respecto Spring Boot hereda todas las capacidades que ofrece el framework de Spring. Tanto para pruebas de integración, es decir, aquellas que requieren el auxilio de recursos externos a la propia aplicación, como pruebas unitarias, que pueden realizarse de forma independiente.

Para nuestro caso de uso, la construcción de servicios REST con Spring Boot 2, tiene sentido hacer pruebas para comprobar que el servidor web se levanta, mapea las rutas, y retorna un JSON que cumple con el formato esperado.

Caso de Uso

Como ejemplo utilizaremos una versión modificada de uno de los servicios REST construidos con Spring MVC en artículos anteriores, el que retorna una lista de números desde el 1 a un número dado. Teniendo en cuenta siempre que lo que interesa es probar que se cumple el contrato del servicio, no la implementación del mismo.

El primer paso es incluir las dependencias de Spring MVC en el fichero pom.xml mediante el starter de Spring Boot:

El segundo paso es crear en /src/main/java/{package}  la clase DTO que retornará el servicio:

El tercer paso es crear en /src/main/java/{package} la clase que implementa el controlador del servicio:

Y el último paso es compilar y ejecutar la aplicación con Maven:

Invocando a http://localhost:9999/counter?count=3 desde un navegador debería retornarse un JSON con un campo que contenga el array con la secuencia de números:

Notar que el ejemplo está cambiado con respecto a artículos anteriores con objeto de que no falle si no se proporciona el parámetro de entrada, y para que retorne un JSON en vez de un array. Aún así, si se le pasa un valor que no represente un número entero, el servicio fallará con una excepción de tipo NumberFormatException, mostrándose la página de error por defecto de Spring Boot. En un sistema real explotado en producción, y expuesto de forma pública en Internet, los parámetros deben validarse siempre y este tipo de información no debería exponerse nunca por motivos de seguridad.

SpringBootTest

El primer paso para probar el servicio de ejemplo es añadir al fichero pom.xml las dependencias de Spring Test a través del starter de Spring Boot:

Esta dependencia añade alguna de las librerías más populares utilizadas para la elaboración de pruebas. En particular JUnit, Hamcrest, Mockito, AssertJ y JSONassert, entre otras. Esto permite implementar las pruebas directamente con JUnit. Lo que es una ventaja en la medida que la mayoría de IDEs se integran con este framework y permiten lanzar las pruebas sin necesidad de pasar por Maven.

El segundo paso es crear en /src/test/java/{package}  una clase con un caso de prueba:

La anotación @RunWith sirve para indicar el runner que se quiere utilizar con JUnit. La anotación @SpringBootTest sirve para indicar que la clase ejecuta casos de prueba de una aplicación de Spring Boot. Y el parámetro WebEnvironment.RANDOM_PORT indica que se quiere arrancar el servidor en un puerto aleatorio, para evitar conflictos en caso de lanzar varias pruebas en paralelo.

La instancia inyectada de la clase TestRestTemplate es registrada automáticamente como resultado de aplicar la anotación @SpringBootTest sobre la clase, y tiene la ventaja de que todas las llamadas HTTP que realice las hará contra el servidor web embebido arrancado para la prueba. Con la versión 5 de Spring se prefiere el uso de la clase WebTestClient en vez de TestRestTemplate, ya que su API sigue un estilo fluent para las aserciones, pero en la release actual sólo se registra automáticamente cuando se utiliza WebFlux y no Spring MVC.

Cuando se ejecute la prueba se arrancará el servidor web embebido, se invocará al servicio a través de restTemplate, y se comprobará que por defecto devuelve una cadena de texto que representa un JSON con un array vacío.

En un IDE moderno, como Eclipse Photon, las pruebas con JUnit se pueden ejecutar de varias formas de una manera extremadamente sencilla. Por ejemplo, pulsando el botón derecho sobre el método de la prueba y ejecutándolo a través del menú contextual.

WebMvcTest

En algunos casos tener que arrancar un servidor web puede ser un inconveniente, sobre todo cuando lo que se quiere probar es sólo un controlador de la forma más rápida posible. Para evitar esto es posible reescribir la prueba anterior utilizando las facilidades de prueba de Spring MVC, en vez de las de Spring Boot. Lo que quiere decir que se puede invocar al controlador, a la manera de Spring MCV, sin tener que levantar el servidor, que es la manera de Spring Boot.

La anotación @WebMvcTest permite especificar el controlador que se quiere probar y tiene el efecto añadido que registra algunos beans de Spring, en particular una instancia de la clase MockMvc, que se puede utilizar para invocar al controlador simulando la llamada HTTP sin tener que arrancar realmente ningún servidor web.

Si se ejecuta la prueba se puede comprobar en el log que el servidor embebido no arranca, pero que el servicio es invocado y retorna el objeto JSON con un array vacío de igual forma que con el método del apartado anterior.

JSON

El inconveniente de los métodos de prueba de los apartados anteriores es que comprueban el resultado como una cadena de texto, sin ninguna estructura. Un espacio en blanco, o un orden distinto en los campos retornados, provocaría que la prueba fallase.

Para evitar estos problemas es mejor tratar el resultado como JSON y reescribir el método para que no opere con cadenas de texto.

Una forma sencilla de hacerlo es cambiar el método string por el método json:

La diferencia entre ambos métodos es que el primero es estricto en la comprobación que realiza entre las dos cadenas de texto, mientras que el segundo comprueba la similitud entre las dos cadenas tratándolas como si fueran representaciones en formato texto de objetos JSON. Por eso la prueba sigue ejecutándose con éxito, a pesar de los espacios añadidos a la cadena de texto utilizada para la comprobación.

Evidentemente sigue sin ser una solución correcta, ya que implica seguir escribiendo cadenas de texto, e impide utilizar las facilidades de comprobación de nombres y tipos del IDE. Una mejor solución es trabajar directamente con clases Java mapeadas como objetos JSON.

La anotación @AutoConfigureJsonTesters registra automáticamente una serie de beans de Spring para trabajar con librerías como Jackson, Gson y Jsonb, existiendo además anotaciones específicas para cada una de estas librerías. La instancia inyectada de la clase JacksonTester trabaja con Jackson y se utiliza para la prueba porque es la librería que se utiliza por defecto en las aplicaciones de Spring Boot.

De esta forma se evita utilizar cadenas de texto y se pueden escribir pruebas más elaboradas.

Otra opción disponible es comparar el JSON devuelto por el servicio con un JSON almacenado en un fichero.

Resumiendo, Spring ofrece toda una variedad de configuraciones para la realización de pruebas, no sólo las básicas vistas en este artículo, sino otras más específicas para probar servicios que utilizan JPA, LDAP, Redis, MongoDB, junto con algunas otras que merece la pena explorar en la documentación oficial de referencia.

Spring Boot 2 (3)

Continuando con el ejemplo de construcción de aplicaciones básicas con Spring Boot 2, lo siguiente es añadir un servicio REST a la aplicación, ya que hoy en día es la tecnología más utilizada para construir microservicios. Tarea que puede acometerse utilizando Spring MVC, Spring WebFlux, o una implementación de JAX-RS, como Jersey o Apache CXF.

Spring MVC

Para crear un servicio REST con Spring MVC hay que añadir el starter web de Spring Boot en el fichero pom.xml:

Esta dependencia incluye las librerías de Spring MVC en el proyecto y permite construir aplicaciones web basadas en servlets.

Como ejemplo de servicio REST vamos a escribir un controlador que reciba un parámetro de tipo texto y retorne un JSON con un campo que contenga dicho parámetro. Es decir, el típico “saludador” (greeter), que recibe un nombre y retorna “Hello {{nombre}}!”.

Lo primero es crear en /src/main/java/{paquete} la clase que se quiere retornar como resultado de la ejecución del servicio. Es decir, el típico DTO:

Lo siguiente es crear en src/main/java/{paquete} la clase con el controlador, utilizando las anotaciones de Spring MVC para indicar el verbo, ruta y nombre del parámetro HTTP a través de los que se atenderán las peticiones de servicio:

La anotación @RestController hace que la respuesta del método del controlador se convierta automáticamente en un objeto de tipo JSON. La anotación @GetMapping hace que el método atienda peticiones HTTP de tipo GET. Y la anotación @RequestParam hace que el valor del parámetro HTTP con nombre “name” se asigne automáticamente al parámetro de entrada del método.

El último paso es compilar y ejecutar la aplicación utilizando el propio plugin de Maven de Spring Boot:

Si todo funciona correctamente arrancará la aplicación mostrando por consola el log de ejecución, en el que debe verse la ruta mapeada por el servicio y el servidor web embebido utilizado:

El servicio arrancado de forma local se puede invocar desde un navegador a través del puerto en el que se encuentre levantado el servidor web embebido:

http://localhost:9999/hello?name=World

La respuesta será un JSON con un mensaje que contendrá el nombre pasado como parámetro:

Spring WebFlux

Para crear un servicio REST con Spring WebFlux hay que añadir el starter de WebFlux de Spring Boot en el fichero pom.xml:

Esta dependencia incluye las librerías de Spring WebFlux en el proyecto y permite construir aplicaciones asíncronas no bloqueantes basadas en el paradigma reactivo.

Para convertir el ejemplo del apartado anterior en una aplicación de WebFlux sólo hay que modificar la clase del controlador, encapsulando con Mono<T> el tipo retornado. Esta clase es proporcionada por la librería Reactor y actúa como un publicador de un reactive stream que se resuelve con un único elemento o un error.

La nueva aplicación se puede compilar y ejecutar utilizando el plugin de Maven de Spring Boot de igual forma que en el apartado anterior:

Si todo funciona correctamente arrancará la aplicación mostrando por consola el log de ejecución, en el que debe verse la nueva ruta mapeada y el servidor embebido, que por defecto es Netty:

Y de igual forma que en el apartado anterior, el servicio arrancado de forma local se puede invocar desde un navegador a través del puerto en el que se encuentre levantado el servidor web embebido:

http://localhost:9999/hello?name=World

La respuesta será un JSON con un mensaje que contendrá el nombre pasado como parámetro:

En el caso de que el servicio REST tuviera que retornar una lista de objetos el resultado habría que encapsularlo con Flux<T>, otra clase proporcionada por la librería Reactor y que representa un publicador de reactive streams que se resuelve con 0, n elementos o error. Como ejemplo vamos a construir un nuevo controlador que admite un número como parámetro y retorna una lista de números desde 1 al número dado.

El primer paso es crear en /src/main/java/{paquete} una nueva clase para el DTO:

El segundo paso es crear en /src/main/java/{paquete} una nueva clase para el controlador:

Y el último paso es el mismo de siempre: compilar, ejecutar con el plugin e invocar al servicio desde el navegador:

http://localhost:9999/counter?count=3

La respuesta será un array de objetos:

WebFlux permite además definir las rutas (endpoints) sin utilizar anotaciones, de una forma más funcional, construyendo las rutas que se quiere que atiendan los servicios. Por ejemplo, el servicio contador anterior se puede escribir de forma equivalente creando beans de Spring de tipo RouterFunction. Beans que tienen que crearse al arrancar la aplicación, como parte del proceso de configuracion, por lo que tienen que definirse en una clase anotada con @Configuration como la siguiente:

Como se observa, con este modelo es responsabilidad de la aplicación extraer los parámetros de la petición, construir el resultado y cuerpo de la respuesta, y mapear la ruta junto con el verbo. Es un enfoque más funcional evitando el uso de anotaciones y orientado a definir manejadores (handlers). En el ejemplo no se aprecia, ya que se hace todo dentro de un mismo método. En la práctica la responsabilidad estará repartida entre distintos componentes. El stream de datos normalmente procederá de algún repositorio, el manejador estará en un servicio, y el método que define la ruta sólo contendrá la parte técnica relacionada con HTTP. Notar que en este ejemplo además resulta un poco engorroso la cantidad de clases estáticas que se requiere importar.

Spring Boot 2 (2)

Hace un tiempo elaboré una breve introducción a Spring Boot con el objetivo de que pudiera ser utilizada como referencia dentro de una empresa en la que trabajaba. Con el tiempo los fuentes correspondientes acabaron publicados, y ahora con el cambio de versión de Spring Boot han quedado un poco desactualizados. Afortunadamente la documentación de Spring Boot es bastante detallada y es la mejor referencia disponible. El objetivo de este artículo y sucesivos es enmendar esas notas introductorias utilizando Spring Boot 2 para construir una serie de aplicaciones básicas usando versiones más recientes de software, como Java 10, Eclipse Photon o Tomcat 9.

Configuración Automática

Una de las ventajas de usar Spring Boot es que crear un proyecto con Maven o Gradle es muy sencillo si se utiliza Spring Initializr, una web que genera esqueletos de proyectos para Spring Boot. La web ofrece una interface muy simple que permite seleccionar el tipo de proyecto, el lenguaje de programación, la versión de Spring Boot, las dependencias con librerías de terceros, y genera automáticamente un fichero zip que contiene el proyecto listo para ser compilado y ejecutado.

Configuración Manual

Para crear un proyecto de una forma más manual con Maven hay que crear un fichero pom.xml básico como el siguiente:

A partir de ese fichero básico ya se puede empezar a añadir o quitar entradas para construir un tipo u otro de proyecto. Por ejemplo, para conseguir que el proyecto sea un proyecto de Spring Boot es necesario añadir org.springframework.boot:spring-boot-starter-parent como proyecto padre en el fichero pom.xml:

Haciendo que org.springframework.boot:spring-boot-starter-parent sea el padre de un proyecto se consigue que de forma automática se haga referencia a una serie concreta de versiones de librerías, tanto de Spring como de terceros, que funcionan correctamente de forma conjunta con la versión usada de Spring Boot, evitando así a los proyectos tener que configurar las dependencias de manera individual.

Lo que es importante en este punto es entender que al configurar el proyecto padre no se añaden las dependencias (librerías) al proyecto, sólo se indican las versiones de las mismas que se quieren utilizar. De hecho, es incluso posible configurar el proyecto sin tener como padre a Spring Boot, importando directamente en el fichero pom.xml el conjunto de referencias de Spring Boot:

Las dos opciones vistas, utilizar el proyecto padre o importar las referencias, son válidas, pudiéndose utilizar una u otra dependiendo de cada caso en concreto. Para el ejemplo se continuará con la primera opción, lo importante es conocer que existen distintas opciones.

Aplicación de Consola

Aunque tradicionalmente Spring se ha asociado a la construcción de aplicaciones web, y Spring Boot a la construcción de microservicios REST, en realidad el núcleo de Spring siempre se ha podido utilizar para aplicaciones de línea de comandos.

Spring Boot facilita la creación de distintos tipos de aplicación mediante módulos Maven llamados “starters”. Así, para crear una aplicación de consola, el primer paso es añadir la dependencia org.springframework.boot:spring-boot-starter en el fichero pom.xml:

El siguiente paso es añadir una clase que sirva como punto de entrada a la aplicación de Spring Boot. Para ello basta con crear en /src/main/java/{paquete} una clase anotada con @SpringBootApplication y con un método estático main estándar de Java:

La anotación @SpringBootApplication es equivalente, entre otras, a las anotaciones @Configuration y @ComponentScan de Spring.

El último paso es compilar y ejecutar la aplicación utilizando el propio plugin de Maven de Spring Boot:

Si todo funciona correctamente, arrancará la aplicación de Spring Boot mostrando por consola el log de ejecución de la aplicación, donde, como mínimo, deberá verse el famoso ascii-art de Spring Boot (¡que en Spring Boot 2 se puede animar!) y la versión concreta utilizada:

Lógicamente, la aplicación de consola arrancará y terminará inmediatamente, ya que no hace nada, pero cumple su función de hacer entender como construir un proyecto básico desde cero.

Aplicación web

Para construir una aplicación web con Spring Boot basada en Spring MVC hay que añadir el starter para web al fichero pom.xml:

Crear en /src/main/java/{paquete} una clase anotada con @EnableAutoConfiguration:

Y compilar y ejecutar la aplicación con el plugin de Maven de Spring Boot:

Si todo funciona correctamente arrancará una instancia de Tomcat 8.5 y se mostrará por consola el log de ejecución, incluyendo el puerto y el contexto en que se encuentra escuchando el servidor, siendo esto último, por cierto, una novedad de Spring Boot 2:

Una vez arrancado el servidor se puede parar con la habitual combinación de teclas Control+C.

Cambiar Puerto de Tomcat

Para cambiar el puerto por defecto de arranque de Tomcat hay que crear un fichero de propiedades e indicar el nuevo puerto. El fichero de propiedades se debe ubicar en /src/main/resources, debe llamarse application, y puede estar en el formato .properties clásico de Java o en formato YAML.

Si se utiliza application.properties el fichero debe tener el siguiente contenido:

Si se utiliza application.yml el fichero debe tener el siguiente contenido:

Al arrancar de nuevo el servidor, Tomcat escuchará en el nuevo puerto indicado:

El uso del fichero de propiedades es una constante en Spring Boot. Por defecto el framework utiliza una serie de valores predefinidos según el criterio del equipo de Spring, de forma que se puede arrancar una aplicación sin tener que configurar ningún valor, pero deja la puerta abierta a cambiar dichos valores por parte de los desarrolladores a través del fichero de propiedades.

En lo sucesivo se sobreentiende que se utiliza el formato YAML para los ejemplos, pero usar un formato u otro es indistinto.

Cambiar Versión de Tomcat

Para cambiar la versión de Tomcat hay que añadir una propiedad en el fichero pom.xml:

Al arrancar de nuevo el servidor, la versión de Tomcat utilizada será la nueva indicada:

Notar que la propiedad se añade al fichero pom.xml, y no al fichero de propiedades de la aplicación, porque es un parámetro necesario para construir la aplicación, para descargar la versión indicada de Tomcat y embeberla dentro de la aplicación, no un parámetro que se vaya a utilizar en tiempo de ejecución.

Cambiar Servidor de Aplicaciones

Para utilizar Jetty o Undertow en vez de Tomcat hay que excluir la dependencia con Tomcat del fichero pom.xml e incluir la dependencia con el nuevo servidor que se quiera utilizar.

Por ejemplo, para utilizar Jetty hay que realizar la siguiente modificación:

Al volver a arrancar la aplicación se levantará una instancia de Jetty en vez de Tomcat:

En todos los casos, accediendo a la url http://localhost:9999/ se puede ver la página de error 404 por defecto de Spring, ya que de momento no se ha añadido ningún servicio al servidor.

Spring Boot 2 (1)

La última versión de Spring Boot supone la primera gran actualización que recibe desde que se publicó. Las principales novedades a mi juicio son la necesidad de utilizar la versión 8 o superior de Java y el uso de la versión 5 de Spring Framework.

La necesidad de utilizar como mínimo la versión 8 de Java supone poder explotar todas las novedades que se añadieron a dicha versión, como interfaces funcionales, funciones lambda o interfaces con métodos implementados por defecto, por citar algunas.

El uso de la versión 5 de Spring Framework supone poder implementar directamente el paradigma reactivo con Spring Boot. Lo que quiere decir que se puede utilizar Netty como servidor embebido, en vez de contenedores de servlets más tradicionales como Tomcat, Jetty o Undertow.

En este punto es curioso comentar como los distintos lenguajes de programación, y las herramientas construidas en torno a ellos, se están complementando. Por ejemplo, la forma de escribir una aplicación con Angular, el popular framework para JavaScript, se parece cada vez a más a la forma en que se escriben aplicaciones en Spring. Es decir, utilizando un estilo basado en anotaciones, llamados decoradores en JavaScript, para indicar si las clases son controladores o servicios, o para enriquecerlas con cualquier otra metainformación. Por su parte, Spring ha adoptado de forma nativa en Java el modelo de servidor con un único hilo de ejecución utilizado tradicionalmente en JavaScript, como en Express, el popular servidor HTTP de node.js.

Todos los cambios y novedades que incorpora Spring Boot 2 pueden encontrarse en la página con las release notes del framework, incluida una guía de migración que incluye una referencia a un módulo que han implementando de forma explícita para facilitar dicha migración. Lo que sigue de este artículo son unas breves notas sobre un conjunto pequeño de las novedades de Spring Boot 2 que me han resultado interesantes con algunos comentarios de mi parte.

Reactive

La adopción de la versión 5 de Spring Framework permite utilizar Spring Webflux como alternativa a Spring MVC, lo que se traduce en que ahora se pueden construir aplicaciones totalmente asíncronas y no bloqueantes con Spring Boot.

Este cambio es realmente importante dentro del uso de Spring y merece la pena dedicarle un tiempo a entenderlo bien. Lo primero que hay que comprender es que el paradigma tradicional dominante en Java es atender cada petición en un hilo de ejecución (thread) distinto. De forma que si una petición accede a un recurso bloqueante, como cuando realiza una consulta a una base de datos por ejemplo, el hilo detiene su ejecución hasta que obtiene una respuesta de dicho recurso. Mientras un hilo está bloqueado no puede utilizarse para otra petición, por lo que un sistema con muchas peticiones puede llegar a bloquearse si todos sus hilos de ejecución se encuentran bloqueados.

Una implementación alternativa a la expuesta en el párrafo anterior es utilizar un único hilo de ejecución. Es decir, con un servidor que tan sólo dispone de un hilo de ejecución a través del que atiende todas las peticiones. Cuando una petición entra es atendida completamente antes de atender la siguiente, excepto cuando la petición invoca un recurso, en cuyo caso se la deja esperando hasta que el recurso responde. La que espera es la petición, no el hilo de ejecución, que puede seguir atendiendo peticiones.

Lógicamente, la clave de esta implementación es que todos los recursos deben poder ser accedidos de forma no bloqueante. Una consulta de base de datos no debe bloquear la petición, debe retornar inmediatamente y señalizar de alguna forma su resultado cuando disponga del mismo.

En Java ya existían servidores que implementaban el patrón reactivo, Spring 5 simplemente lo ha incorporado a su caja de herramientas y Spring Boot ha facilitado el uso del mismo. No obstante, la adopción de Spring 5 no implica el uso obligatorio de WebFlux con Spring Boot 2, se pueden seguir desarrollando aplicaciones con Spring MVC con Spring Boot 2 como hasta ahora.

Reactive Data

Como se ha explicado en el apartado anterior, implementar una aplicación completamente asíncrona y no bloqueante implica que todos los recursos deben ser también asíncronos y no bloqueantes. En una aplicación tradicional esto supone que el acceso a una base de datos por ejemplo debe realizarse mediante un driver no bloqueante.

Spring Boot ha añadido módulos que facilitan el acceso al API asíncrono de las bases de datos NoSQL más populares como Redis, MongoDB o Cassandra. Sin embargo, las bases de datos relacionales más tradicionales accedidas a través de JDBC o JPA aún necesitan utilizar algún tipo de driver asíncrono, normalmente no oficial, o una librería de terceros que implemente un wrapper sobre el driver original.

Por lo tanto, es importante tener en cuenta que si una aplicación va a estar ligada a uno o más recursos accedidos de forma bloqueante, por ejemplo a través de un API como JDBC o JPA, entonces una mejor opción a WebFlux puede ser utilizar Spring MVC.

Oracle tiene una propuesta en marcha para el desarrollo de un API para el acceso no bloqueante a base de datos, pero de momento no ha llegado a cristalizar.

Netty

El principal cambio de Spring Boot 2 respecto a los servidores embebidos soportados es la incorporación de Netty, el servidor asíncrono no bloqueante por antonomasia, para la implementación de aplicaciones con WebFlux.

Los servidores web tradicionales como Tomcat, Jetty y Undertow siguen siendo opciones disponibles para desarrollar aplicaciones con Spring MVC, pero con el requerimiento de implementar como mínimo la versión 3.1 del API Servlet para utilizar WebFlux, ya que fue la primera versión que incorporó la gestión de operaciones de E/S de forma no bloqueante.

A este respecto, comentar que Spring Boot 2 ha actualizado su dependencia con Tomcat a la versión 8.5, y ahora utiliza HikariCP como pool de conexiones, en vez del pool por defecto de Tomcat.

HTTP/2

HTTP/2 introdujo muchas mejoras al protocolo HTTP, como un formato binario más eficiente de procesar que el formato anterior en texto plano, la capacidad de atender múltiples peticiones a través de una misma conexión en vez de tener que establecer una nueva por cada petición, una reducción en la cantidad de paquetes necesarios para intercambiar las cabeceras HTTP mediante el uso de compresión, e incluso la posibilidad de que los servidores tomen la iniciativa en el envío de información a los clientes.

Mejoras todas ellas que se han reflejado en un aumento del rendimiento a la hora de descargar recursos estáticos de una web, como hojas de estilos e imágenes. Su explotación por parte de los servicios REST de momento parece haberse restringido a una mejora del rendimiento motivado por las propias mejoras de rendimiento en el protocolo.

Spring Boot 2 incorpora la posibilidad de utilizar HTTP/2 con Tomcat, Jetty y Undertow, aunque requiere una configuración específica en cada caso y el uso de alguna librería nativa de terceros en según que caso. La combinación de Tomcat 9 con Java 9 o superior es particularmente interesante, ya que soporta el protocolo sin requerir de ninguna librería adicional.

Actuators

Los actuators son un conjunto de utilidades que pueden añadirse de forma automática a las aplicaciones desarrolladas con Spring Boot, y a las que pueden accederse mediante JMX o HTTP. Permiten comprobar si la aplicación está levantada, consultar sus parámetros de configuración, u obtener métricas en tiempo real, por citar sólo algunos casos de uso.

En Spring Boot 2 se han unificado las rutas HTTP para acceder a dichos servicios a través de un único endpoint “\actuator”, se ha mejorado la calidad y estructura de la información retornada en los JSON, se ha adoptado HATEOAS para el autodescubrimiento de servicios mediante enlaces hipermedia en formato HAL, y se ha cambiado el sistema de métricas propio original configurado por defecto a favor de micrometer.

Esto último acerca de las métricas es más importante de lo que pueda parecer en un principio, sobre todo en el mundo actual donde priman los microservicios. Spring Boot recopila de forma automática una gran cantidad de métricas acerca del funcionamiento de las aplicaciones. Mide aspectos tales como el rendimiento de la máquina virtual de Java, la latencia de los servicios, el uso de la cache, el número de conexiones, el uso de aplicaciones específicas como Tomcat o RabbitMQ, y muchos otros parámetros. Métricas todas ellas que se pueden etiquetar y enviar a un servidor, o varios de ellos, para su procesamiento y monitorización, normalmente de forma agregada.

Para terminar, comentar que otra mejora importante es que se ha añadido a los actuators un API para exponer servicios propios de manera agnóstica, es decir, sin ligarlos a JMX o HTTP. Los beans de Spring anotados con @Endpoint, y con métodos anotados con @ReadOperation, @WriteOperation y @DeleteOperation son automáticamente expuestos por JMX, y por HTTP en el caso de encontrarse dentro de una aplicación web.

Resumiendo, en esencia Spring Boot sigue siendo el mismo que antes, la versión 2 incorpora las novedades de Spring 5 y refleja mejor el estado actual del desarrollo de aplicaciones web con Java.