Spring Boot
Arquitectura, Servlets, Estructura de Carpetas y Ejecución
Spring Boot es un framework diseñado para simplificar el desarrollo de aplicaciones Java de nivel empresarial al proporcionar una capa de auto-configuración y abstracción sobre el ecosistema Spring.
1. Arquitectura de Flujo y Servlets en Spring Boot
En las aplicaciones web tradicionales Java EE, las solicitudes HTTP eran procesadas directamente por Servlets registrados de forma manual en un servidor de aplicaciones. Spring Boot encapsula esta complejidad mediante el servidor web embebido (Apache Tomcat por defecto) y el patrón Front Controller.
1.1. Flujo de una Solicitud HTTP
El flujo ilustra la trayectoria de una solicitud HTTP a través de la arquitectura de la aplicación:
- Cliente (Navegador/Postman): Emite la petición HTTP hacia un endpoint específico.
- Servlet Container (Tomcat Embebido): Recibe el socket TCP y extrae la estructura HTTP.
- DispatcherServlet: Actúa como el Front Controller central. Intercepta todas las solicitudes entrantes y consulta los mapeos de la aplicación para delegar la petición al controlador adecuado.
- Controller Layer: Procesa los parámetros de la solicitud, valida la entrada y delega la ejecución lógica a la capa de servicios.
- Service Layer: Aplica las reglas de negocio, transacciones y orquestación.
- Repository Layer: Abstrae la comunicación con el mecanismo de almacenamiento mediante interfaces de datos.
- Database / Model: Realiza las operaciones físicas sobre la base de datos o estructuras de persistencia y retorna el resultado de vuelta a través del pipeline.
1.2. El Rol de DispatcherServlet y SpringBootServletInitializer
Spring Boot utiliza DispatcherServlet como la columna vertebral del procesamiento de peticiones en Spring MVC:
DispatcherServlet: Es un Servlet especial que sirve de punto de entrada único para las solicitudes HTTP. Realiza la resolución de Handlers, conversión de JSON/XML medianteHttpMessageConvertery manejo global de excepciones.SpringBootServletInitializer: Cuando una aplicación Spring Boot se despliega como un ejecutable JAR independiente, el servidor Tomcat embebido se inicia automáticamente a través de la clase principal@SpringBootApplication. Sin embargo, si se requiere empaquetar la aplicación como un archivo WAR para desplegarla en un servidor de aplicaciones externo tradicional (como Tomcat independiente, WildFly o Payara), la clase principal debe extenderSpringBootServletInitializery sobrescribir el métodoconfigure.
package com.icesi.app;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
// Esta clase permite que un servidor Servlet externo configure la aplicación cuando se despliega un WAR
public class ServletInitializer extends SpringBootServletInitializer {
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
// Enlaza la configuración del servlet externo con la clase principal de Spring Boot
return application.sources(DemoApplication.class);
}
}
2. Arquitectura Multicapa en Spring Boot
Una arquitectura limpia en Spring Boot promueve la separación estricta de responsabilidades dividiendo el proyecto en capas claramente delimitadas: Model, Repository, Service y Controller.
- Model (Modelo/Entidad): Representa los datos del dominio o las tablas de la base de datos.
- Repository (Repositorio): Interfaz que extiende las abstracciones de persistencia (como
JpaRepository) para gestionar operaciones CRUD. - Service (Servicio): Interfaz e implementación que contienen la lógica de negocio pura, aislamiento de transacciones y reglas del dominio.
- Controller (Controlador): Recibe peticiones HTTP, efectúa la validación inicial de parámetros y envía la respuesta en el formato adecuado.
2.1. Capa de Modelo (Model)
package com.icesi.app.model;
public class User {
private Long id;
private String name;
private String email;
public User() {}
public User(Long id, String name, String email) {
this.id = id;
this.name = name;
this.email = email;
}
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public String getEmail() { return email; }
public void setEmail(String email) { this.email = email; }
}
2.2. Capa de Repositorio (Repository Interface)
package com.icesi.app.repository;
import com.icesi.app.model.User;
import java.util.List;
import java.util.Optional;
// Definición de interfaz de repositorio para desacoplar el acceso a datos
public interface UserRepository {
// Guarda o actualiza una entidad en el almacenamiento
User save(User user);
// Busca un usuario por su identificador único
Optional<User> findById(Long id);
// Obtiene la lista completa de usuarios
List<User> findAll();
// Elimina un usuario por su identificador
void deleteById(Long id);
}
2.3. Capa de Servicio (Service Interface e Implementación)
Interfaz del Servicio:
package com.icesi.app.service;
import com.icesi.app.model.User;
import java.util.List;
// Contrato de servicio que define los casos de uso del dominio
public interface UserService {
// Registra un nuevo usuario aplicando reglas de negocio
User createUser(User user);
// Obtiene un usuario especifico por ID
User getUserById(Long id);
// Obtiene el listado de todos los usuarios
List<User> getAllUsers();
}
Implementación del Servicio:
package com.icesi.app.service.impl;
import com.icesi.app.model.User;
import com.icesi.app.repository.UserRepository;
import com.icesi.app.service.UserService;
import org.springframework.stereotype.Service;
import java.util.List;
// Marca la clase como componente de servicio de Spring para inyección de dependencias
@Service
public class UserServiceImpl implements UserService {
private final UserRepository userRepository;
// Inyección de dependencias por constructor
public UserServiceImpl(UserRepository userRepository) {
this.userRepository = userRepository;
}
@Override
public User createUser(User user) {
if (user.getEmail() == null || user.getEmail().isBlank()) {
throw new IllegalArgumentException("El email del usuario es obligatorio");
}
return userRepository.save(user);
}
@Override
public User getUserById(Long id) {
return userRepository.findById(id)
.orElseThrow(() -> new RuntimeException("Usuario no encontrado con ID: " + id));
}
@Override
public List<User> getAllUsers() {
return userRepository.findAll();
}
}
2.4. Capa de Controlador (Controller Interface e Implementación)
package com.icesi.app.controller;
import com.icesi.app.model.User;
import com.icesi.app.service.UserService;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;
// Define un controlador REST que expone endpoints JSON
@RestController
// Establece el mapeo de ruta base para los endpoints de usuarios
@RequestMapping("/api/v1/users")
public class UserController {
private final UserService userService;
// Inyección de dependencias por constructor
public UserController(UserService userService) {
this.userService = userService;
}
@PostMapping
public ResponseEntity<User> createUser(@RequestBody User user) {
User createdUser = userService.createUser(user);
return new ResponseEntity<>(createdUser, HttpStatus.CREATED);
}
@GetMapping("/{id}")
public ResponseEntity<User> getUserById(@PathVariable Long id) {
User user = userService.getUserById(id);
return ResponseEntity.ok(user);
}
@GetMapping
public ResponseEntity<List<User>> getAllUsers() {
List<User> users = userService.getAllUsers();
return ResponseEntity.ok(users);
}
}
3. Estructura Típica de Carpetas en un Proyecto Spring Boot
Un proyecto estándar de Spring Boot sigue la convención de paquetes estructurada dentro de la carpeta src/main/java/:
demo-app/
├── .mvn/ # Archivos de configuración del Maven Wrapper
├── mvnw # Script ejecutable de Maven Wrapper (Linux/macOS)
├── mvnw.cmd # Script ejecutable de Maven Wrapper (Windows)
├── pom.xml # Archivo de gestión de dependencias Maven (o build.gradle)
├── src/
│ ├── main/
│ │ ├── java/
│ │ │ └── com/icesi/app/
│ │ │ ├── DemoApplication.java # Clase principal anotada con @SpringBootApplication
│ │ │ ├── controller/ # Controladores REST (@RestController)
│ │ │ ├── service/ # Interfaces de servicios de negocio
│ │ │ │ └── impl/ # Implementaciones de servicios (@Service)
│ │ │ ├── repository/ # Interfaces de persistencia (@Repository / JpaRepository)
│ │ │ ├── model/ # Entidades JPA (@Entity) y modelos de dominio
│ │ │ ├── dto/ # Data Transfer Objects para peticiones/respuestas
│ │ │ ├── config/ # Clases de configuración (@Configuration, CORS, Security)
│ │ │ └── exception/ # Manejadores globales de excepciones (@ControllerAdvice)
│ │ └── resources/
│ │ ├── application.properties # Configuración principal de la aplicación
│ │ ├── application-dev.properties # Configuración del perfil de Desarrollo
│ │ ├── application-prod.properties# Configuración del perfil de Producción
│ │ ├── static/ # Archivos estáticos (CSS, JS, imágenes)
│ │ └── templates/ # Plantillas HTML de vista (Thymeleaf, si aplica)
│ └── test/
│ └── java/com/icesi/app/ # Pruebas unitarias e integración (@SpringBootTest)
La clase principal DemoApplication.java (anotada con @SpringBootApplication) debe colocarse en el paquete raíz com.icesi.app. Spring Boot realiza un escaneo automático de componentes (@ComponentScan) de manera descendente a partir del paquete donde reside esta clase. Si colocas un controlador fuera de este árbol de paquetes, Spring Boot no lo detectará.
4. Ejecución de la Aplicación y Gestión de Perfiles
4.1. Cómo Ejecutar la Aplicación desde la Consola
Spring Boot incluye ejecutables Wrapper (mvnw y gradlew) para permitir la compilación y ejecución directa sin necesidad de tener Maven o Gradle instalados previamente en el sistema operativo.
- Maven Wrapper
- Gradle Wrapper
# En Linux / macOS:
./mvnw clean spring-boot:run
# En Windows (PowerShell / CMD):
.\mvnw.cmd clean spring-boot:run
# En Linux / macOS:
./gradlew clean bootRun
# En Windows (PowerShell / CMD):
.\gradlew.bat clean bootRun
Antes de arrancar el servidor web con spring-boot:run o bootRun, se recomienda comprobar que todas las dependencias del proyecto y complementos se descarguen y compilen sin errores ejecutando:
- Maven:
./mvnw clean install(omvn clean install) - Gradle:
./gradlew build(o./gradlew --refresh-dependencies)
4.2. Configuración con application.properties
# Nombre de la aplicación
spring.application.name=demo-icesi-app
# Puerto de escucha del servidor web embebido (Tomcat)
server.port=8080
# Context Path: Prefijo global para la redirección de todas las solicitudes HTTP
# Ejemplo: Un endpoint @GetMapping("/users") responderá en http://localhost:8080/demo-api/users
server.servlet.context-path=/demo-api
# Configuración del sistema de logs
logging.level.root=INFO
logging.level.com.icesi.app=DEBUG
logging.file.name=logs/application.log
Configurar server.servlet.context-path=/demo-api añade un prefijo obligatorio a todos los endpoints. Esto es fundamental para el redireccionamiento de solicitudes cuando la aplicación se despliega detrás de un Reverse Proxy (como Nginx) o cuando coexiste con otros servicios.
4.3. Activación de Perfiles de Entorno mediante Línea de Comandos (CLI)
Además de definir spring.profiles.active=dev en el archivo application.properties, es muy común activar perfiles dinámicamente al momento de la ejecución desde la terminal o entornos de CI/CD:
- Maven Wrapper
- Gradle Wrapper
- Ejecutable JAR
# En Linux / macOS:
./mvnw spring-boot:run -Dspring-boot.run.profiles=dev
# En Windows (PowerShell / CMD):
.\mvnw.cmd spring-boot:run -Dspring-boot.run.profiles=dev
# En Linux / macOS:
./gradlew bootRun --args='--spring.profiles.active=dev'
# En Windows (PowerShell / CMD):
.\gradlew.bat bootRun --args="--spring.profiles.active=dev"
# Opción 1: Mediante propiedad del sistema (-D)
java -Dspring.profiles.active=dev -jar target/demo-app-0.0.1-SNAPSHOT.jar
# Opción 2: Mediante argumento de aplicación (--)
java -jar target/demo-app-0.0.1-SNAPSHOT.jar --spring.profiles.active=dev
# Opción 3: Mediante Variable de Entorno del Sistema Operativo
export SPRING_PROFILES_ACTIVE=dev
java -jar target/demo-app-0.0.1-SNAPSHOT.jar