Servlets de Jakarta y Spring Boot


Qué hay en este artículo ⌄
  • Arquitectura de Spring Boot
  • Diferencias entre Java EE y Jakarta EE
  • Qué son los Servlets Jakarta en Java
  • Ejemplos de contenedores de Servlets Jakarta
  • Servidores de aplicaciones Jakarta vs Contenedores de Servlets
  • Contenedores de Servlets embebidos en Java
  • Aplicaciones web Spring Boot e integración con Servlets Jakarta
  • Spring Scoped Proxy

Java EE vs Jakarta EE

Sabemos que existe Java SE (Edición Estándar) y Java EE (Edición Empresarial).

Los paquetes adicionales en Java EE solían llamarse javax.*, pero después de cierto tiempo fueron renombrados a jakarta.*.

Ahora eso se llama Jakarta EE.


Servlets Jakarta

Una parte importante de Jakarta son los Servlets. Son clases de Java que procesan peticiones HTTP y generan respuestas.

Digamos que son controladores “nativos de Java”, como en Spring MVC.

Los Servlets viven en contenedores que los gestionan. Se les llama Contenedor de Servlets Jakarta o Contenedor Web Jakarta.


Contenedores de Servlets Jakarta

Creamos nuestro propio Servlet y lo registramos en el Contenedor de Servlets Jakarta a través de la clase ServletContext, llamando al método addServlet().

Algunos ejemplos de Contenedores de Servlets Jakarta independientes son: Apache Tomcat y Eclipse Jetty.

Empaquetamos nuestra aplicación con Servlets (y sus registros) en un archivo .war y la desplegamos en el contenedor.


Servidores de Aplicaciones Jakarta

Jakarta EE es más que solo Servlets. También tiene varias otras características.

Los Contenedores de Servlets Jakarta gestionan solo Servlets. Pero los Servidores de Aplicaciones Jakarta soportan todas (o casi todas) las características de Jakarta como EJB, JPA, JMS, CDI, JTA, JAX-RS, etc.

Algunos Servidores de Aplicaciones Jakarta son: Red Hat WildFly, Oracle WebLogic, Eclipse GlassFish.

Si solo necesitamos gestionar Servlets, podemos usar cualquiera de ellos.


Contenedores de Servlets Embebidos

Ya hablamos de que existen Contenedores de Servlets independientes: funcionan como aplicaciones autónomas donde desplegamos nuestra aplicación .war.

Sin embargo, también existen Contenedores de Servlets embebidos. Podemos embeberlos directamente en nuestra aplicación y distribuir eso como un archivo .jar.

El proceso principal sigue siendo el mismo – creamos Servlets y los registramos en ServletContext.

Apache Tomcat y Eclipse Jetty tienen sus versiones embebidas.


Aplicaciones Web Spring Boot

Las capacidades HTTP/REST de Spring Boot no son más que la tecnología de Servlets Jakarta por debajo.

Solo para ser precisos, debemos mencionar que hay dos tipos de aplicaciones Spring Boot:

  • Aplicaciones Web Spring Boot
  • Aplicaciones no Web Spring Boot

Hablando de aplicaciones Web Spring Boot, donde podemos tener controladores REST: embebe un Contenedor de Servlets Jakarta (Tomcat).

Spring tiene su propio Servlet interno llamado DispatcherServlet. Spring lo registra automáticamente en ServletContext.

DispatcherServlet tiene todos los mapeos necesarios para nuestros controladores HTTP/REST basados en Spring.

Por lo tanto, la jerarquía es la siguiente:

Standalone Java Application (Spring Boot Web app)
   ↳ Embedded Servlet Container (e.g., Tomcat)
       ↳ Jakarta ServletContext
           ↳ Spring's DispatcherServlet
               ↳ Controllers / @RequestMapping handlers

Spring Boot MVC

En esta sección extra, mencionemos que una aplicación Spring MVC (controladores HTTP/REST) puede existir en diferentes formatos.

Primera opción: Spring Boot + Spring MVC

  • El enfoque más común y “estándar”
  • Tales aplicaciones pueden distribuirse como archivos .jar regulares.

Segunda opción: Spring MVC + Contenedor de Servlets independiente

  • Sin Spring Boot
  • Contenedor de Servlets Independiente
  • Deben desplegarse como archivos .war en un Contenedor de Servlets Jakarta independiente o en un Servidor de Aplicaciones Jakarta.
  • Debemos registrar manually el DispatcherServlet de Spring en ServletContext.

Tercera opción: Spring MVC + Contenedor de Servlets embebido

  • Embebemos manualmente el Contenedor de Servlets en nuestra aplicación
  • Debemos registrar manualmente el DispatcherServlet de Spring en ServletContext
  • Tales aplicaciones pueden distribuirse como archivos .jar regulares.

ServletContext y ApplicationContext

No confundamos el Contenedor de Servlets (ServletContext) con el contenedor IoC de Spring (ApplicationContext).

El ServletContext es proporcionado por el Contenedor de Servlets y gestiona componentes web como Servlets y Filtros.

El ApplicationContext de Spring es un contenedor específico de Spring que gestiona beans e inyección de dependencias.

El DispatcherServlet de Spring actúa como un puente entre el ServletContext y el ApplicationContext de Spring, enrutando las peticiones HTTP a los controladores gestionados por Spring.


Quinta opción: Proxy con Alcance

Cuando establecemos proxyMode dentro de la anotación @Scope para un bean, Spring reemplaza el bean real con un proxy en el contenedor IoC.

Luego, cada llamada a método es delegada a una instancia real, resuelta desde el alcance (por ejemplo, alcance de solicitud).

Por lo tanto, los proxies con alcance funcionan correctamente en entornos donde el contexto del alcance permanece activo a través de múltiples llamadas a métodos, como:

  • Solicitudes HTTP (con @RequestScope)
  • Sesiones HTTP (con @SessionScope)

En teoría, todavía es posible usarlo con prototipo.

Sin embargo, uno debe recordar que si tenemos un alcance prototipo + proxy con alcance, entonces la nueva instancia se crea cada vez que hacemos una llamada a método.

@Service
@Scope(value = "prototype", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class MyPrototypeBean {

    private final String id = UUID.randomUUID().toString();

    public String getId() {
        return id;
    }
}

Podemos inyectar MyPrototypeBean en MySingletonBean como de costumbre:

@RestController
public class MySingletonBean {

    @Autowired
    private MyPrototypeBean myPrototypeBean;

    @GetMapping("/")
    public String id() {
        return myPrototypeBean.get().getId();
    }
}

Se creará un nuevo bean por cada llamada a método de myPrototypeBean().