Capítulo 4: Anotaciones Básicas de Spring Boot
Introducción
Las anotaciones están en el corazón de Spring Boot. Permiten configurar la aplicación de manera declarativa, sin XML, y hacen que el código sea más legible y mantenible.
Objetivos del Capítulo
Al final de este capítulo, usted será capaz de:
- Comprender el rol de las principales anotaciones de Spring Boot
- Utilizar
@RestController,@Service,@Repository - Dominar las anotaciones de mapeo HTTP
- Inyectar dependencias con
@Autowired - Crear beans de Spring
¿Qué es una Anotación?
Definición
Una anotación es una forma de metadato que proporciona información sobre el código sin formar parte directamente de él.
Formato:
@NombreAnotacion
@NombreAnotacion(parametro=valor)
Ejemplo:
@RestController
@RequestMapping("/api")
public class UserController {
// ...
}
¿Por qué las Anotaciones?
Ventajas:
- Código más conciso
- Configuración cerca del código
- Type-safe (verificación en compilación)
- Facilita el refactorizado
@SpringBootApplication
La Anotación Principal
package com.example.greeting;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class GreetingApplication {
public static void main(String[] args) {
SpringApplication.run(GreetingApplication.class, args);
}
}
Composición de @SpringBootApplication
@SpringBootApplication es una meta-anotación que combina tres anotaciones:
@SpringBootApplication
=
@SpringBootConfiguration
+ @EnableAutoConfiguration
+ @ComponentScan
Detalle de Cada Anotación
1. @SpringBootConfiguration
Rol: Marca la clase como fuente de definiciones de beans
Equivalente a:
@Configuration
Permite definir beans:
@SpringBootApplication
public class GreetingApplication {
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
2. @EnableAutoConfiguration
Rol: Activa la configuración automática de Spring Boot
Ejemplo: Si Spring Boot detecta:
spring-boot-starter-web→ Configura Tomcat, Spring MVCspring-boot-starter-data-jpa→ Configura Hibernate, DataSourcePostgreSQL Driver→ Configura PostgreSQL
Configuración automática:
3. @ComponentScan
Rol: Escanea el paquete y subpaquetes en busca de componentes de Spring
Paquete escaneado:
package com.example.greeting; // Paquete base
// Spring escaneará:
// - com.example.greeting
// - com.example.greeting.controller
// - com.example.greeting.service
// - etc.
Componentes detectados:
@Component@Controller/@RestController@Service@Repository@Configuration
Anotaciones de Estereotipo
Jerarquía de Componentes
@Component
Rol: Anotación genérica para cualquier componente de Spring
Uso:
import org.springframework.stereotype.Component;
@Component
public class EmailValidator {
public boolean isValid(String email) {
return email.contains("@");
}
}
Cuándo usarla: Para clases utilitarias que no encajan en otras categorías
@Controller
Rol: Marca una clase como controlador MVC (retorna vistas)
Uso:
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
@Controller
public class HomeController {
@GetMapping("/")
public String home(Model model) {
model.addAttribute("message", "¡Bienvenido!");
return "home"; // Retorna la vista "home.html"
}
}
Arquitectura:
@RestController
Rol: Controlador REST (retorna datos JSON/XML)
Equivalente a:
@RestController
=
@Controller + @ResponseBody
Uso:
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.GetMapping;
@RestController
public class GreetingController {
@GetMapping("/greeting")
public Greeting greeting() {
return new Greeting(1L, "Hello, World!");
}
}
Arquitectura:
Diferencia @Controller vs @RestController:
| Aspecto | @Controller | @RestController |
|---|---|---|
| Retorno | Nombre de vista (String) | Datos (JSON/XML) |
| Uso | Aplicaciones web tradicionales | REST APIs |
| Serialización | No | Automática (Jackson) |
| @ResponseBody | Requerido en cada método | Implícito |
@Service
Rol: Marca una clase que contiene la lógica de negocio
Uso:
import org.springframework.stereotype.Service;
import java.util.concurrent.atomic.AtomicLong;
@Service
public class GreetingService {
private final AtomicLong counter = new AtomicLong();
private static final String template = "Hello, %s!";
public Greeting createGreeting(String name) {
return new Greeting(
counter.incrementAndGet(),
String.format(template, name)
);
}
}
Responsabilidades:
- Lógica de negocio
- Cálculos complejos
- Orquestación de repositories
- Transacciones
@Repository
Rol: Marca una clase de acceso a datos
Uso con JPA:
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
// Spring Data JPA genera automáticamente la implementación
}
Responsabilidades:
- Consultas a la base de datos
- Operaciones CRUD
- Consultas personalizadas
Ventaja: Traducción automática de excepciones SQL a excepciones de Spring
Anotaciones de Mapeo HTTP
Visión General
@RequestMapping
Rol: Anotación genérica para mapear solicitudes HTTP
Sintaxis:
@RequestMapping(value = "/path", method = RequestMethod.GET)
Uso a nivel de clase:
@RestController
@RequestMapping("/api/users")
public class UserController {
@RequestMapping(value = "/{id}", method = RequestMethod.GET)
public User getUser(@PathVariable Long id) {
// GET /api/users/123
return userService.findById(id);
}
}
@GetMapping
Rol: Atajo para solicitudes GET
Equivalente a:
@GetMapping("/path")
=
@RequestMapping(value = "/path", method = RequestMethod.GET)
Ejemplos:
@RestController
@RequestMapping("/api")
public class ProductController {
// GET /api/products
@GetMapping("/products")
public List<Product> getAllProducts() {
return productService.findAll();
}
// GET /api/products/123
@GetMapping("/products/{id}")
public Product getProduct(@PathVariable Long id) {
return productService.findById(id);
}
// GET /api/products?category=books
@GetMapping("/products")
public List<Product> getProductsByCategory(@RequestParam String category) {
return productService.findByCategory(category);
}
}
@PostMapping
Rol: Atajo para solicitudes POST (creación)
Uso:
@RestController
@RequestMapping("/api/users")
public class UserController {
@PostMapping
public User createUser(@RequestBody User user) {
return userService.save(user);
}
}
Solicitud:
POST /api/users
Content-Type: application/json
{
"name": "John Doe",
"email": "john@example.com"
}
@PutMapping
Rol: Atajo para solicitudes PUT (actualización completa)
Uso:
@PutMapping("/{id}")
public User updateUser(@PathVariable Long id, @RequestBody User user) {
return userService.update(id, user);
}
Solicitud:
PUT /api/users/123
Content-Type: application/json
{
"name": "John Doe Updated",
"email": "john.updated@example.com"
}
@DeleteMapping
Rol: Atajo para solicitudes DELETE (eliminación)
Uso:
@DeleteMapping("/{id}")
public void deleteUser(@PathVariable Long id) {
userService.delete(id);
}
Solicitud:
DELETE /api/users/123
@PatchMapping
Rol: Atajo para solicitudes PATCH (actualización parcial)
Uso:
@PatchMapping("/{id}")
public User partialUpdate(@PathVariable Long id, @RequestBody Map<String, Object> updates) {
return userService.partialUpdate(id, updates);
}
Solicitud:
PATCH /api/users/123
Content-Type: application/json
{
"email": "newemail@example.com"
}
Tabla Resumen
| Método HTTP | Anotación | Acción | Idempotente |
|---|---|---|---|
| GET | @GetMapping | Leer | ✅ |
| POST | @PostMapping | Crear | ❌ |
| PUT | @PutMapping | Actualizar (completo) | ✅ |
| PATCH | @PatchMapping | Actualizar (parcial) | ❌ |
| DELETE | @DeleteMapping | Eliminar | ✅ |
Anotaciones de Parámetros
@PathVariable
Rol: Extraer un valor de la URL
Sintaxis:
@GetMapping("/users/{id}")
public User getUser(@PathVariable Long id) {
// URL: /users/123
// id = 123
return userService.findById(id);
}
Múltiples variables:
@GetMapping("/users/{userId}/orders/{orderId}")
public Order getOrder(
@PathVariable Long userId,
@PathVariable Long orderId
) {
return orderService.findByUserAndId(userId, orderId);
}
Nombre diferente:
@GetMapping("/users/{id}")
public User getUser(@PathVariable("id") Long userId) {
return userService.findById(userId);
}
@RequestParam
Rol: Extraer parámetros del query string
Sintaxis:
@GetMapping("/users")
public List<User> getUsers(@RequestParam String name) {
// URL: /users?name=John
// name = "John"
return userService.findByName(name);
}
Parámetro opcional:
@GetMapping("/users")
public List<User> getUsers(
@RequestParam(required = false) String name
) {
if (name != null) {
return userService.findByName(name);
}
return userService.findAll();
}
Valor por defecto:
@GetMapping("/users")
public List<User> getUsers(
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "10") int size
) {
return userService.findAll(page, size);
}
Múltiples parámetros:
@GetMapping("/users")
public List<User> searchUsers(
@RequestParam String name,
@RequestParam int age,
@RequestParam String city
) {
// URL: /users?name=John&age=30&city=Montreal
return userService.search(name, age, city);
}
@RequestBody
Rol: Mapear el cuerpo de la solicitud HTTP a un objeto Java
Uso:
@PostMapping("/users")
public User createUser(@RequestBody User user) {
return userService.save(user);
}
Solicitud HTTP:
POST /users
Content-Type: application/json
{
"name": "John Doe",
"email": "john@example.com",
"age": 30
}
Objeto Java recibido:
User user = new User();
user.setName("John Doe");
user.setEmail("john@example.com");
user.setAge(30);
@RequestHeader
Rol: Extraer un header HTTP
Uso:
@GetMapping("/secure")
public String secureEndpoint(
@RequestHeader("Authorization") String token
) {
// token = "Bearer eyJhbGciOiJIUzI1NiIs..."
return "¡Autenticado!";
}
Tabla Comparativa
| Anotación | Fuente | Ejemplo URL | Uso |
|---|---|---|---|
| @PathVariable | Ruta URL | /users/123 | Identificadores de recursos |
| @RequestParam | Query string | /users?name=John | Filtros, paginación |
| @RequestBody | Cuerpo HTTP | POST/PUT body | Envío de datos |
| @RequestHeader | Headers HTTP | Authorization: Bearer token | Autenticación |
Inyección de Dependencias
@Autowired
Rol: Inyecta automáticamente una dependencia
Tipos de Inyección
1. Inyección por Constructor (Recomendada)
@RestController
public class UserController {
private final UserService userService;
@Autowired
public UserController(UserService userService) {
this.userService = userService;
}
@GetMapping("/users")
public List<User> getUsers() {
return userService.findAll();
}
}
Desde Spring 4.3: @Autowired es opcional si hay un solo constructor
@RestController
public class UserController {
private final UserService userService;
// @Autowired no es necesario
public UserController(UserService userService) {
this.userService = userService;
}
}
Ventajas:
- Inmutabilidad (final)
- Facilita las pruebas
- Obligatorio (sin NullPointerException)
2. Inyección por Setter
@RestController
public class UserController {
private UserService userService;
@Autowired
public void setUserService(UserService userService) {
this.userService = userService;
}
}
Ventajas:
- Dependencias opcionales
- Reconfiguración posible
Desventajas:
- Mutable
- Riesgo de NullPointerException
3. Inyección por Campo (No Recomendada)
@RestController
public class UserController {
@Autowired
private UserService userService;
}
Desventajas:
- Difícil de probar
- Acoplamiento fuerte
- Imposible crear un objeto inmutable
Recomendación: Siempre utilice la inyección por constructor
Ejemplo Completo de Arquitectura
// Entidad
@Entity
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private String email;
// Getters/Setters
}
// Repository
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
}
// Service
@Service
public class UserService {
private final UserRepository userRepository;
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
public List<User> findAll() {
return userRepository.findAll();
}
public User save(User user) {
return userRepository.save(user);
}
}
// Controller
@RestController
@RequestMapping("/api/users")
public class UserController {
private final UserService userService;
public UserController(UserService userService) {
this.userService = userService;
}
@GetMapping
public List<User> getAllUsers() {
return userService.findAll();
}
@PostMapping
public User createUser(@RequestBody User user) {
return userService.save(user);
}
}
Flujo de dependencias:
Ejercicios Prácticos
Ejercicio 1: Crear un Controlador REST
Objetivo: Utilizar @RestController y @GetMapping
Instrucciones:
- Cree una clase
HelloController - Anote con
@RestController - Cree un método
sayHello()que retorne "Hello, Spring Boot!" - Anote con
@GetMapping("/hello") - Pruebe con el navegador
Ejercicio 2: Utilizar @PathVariable
Objetivo: Extraer parámetros de la URL
Instrucciones:
- Cree un endpoint
/greet/\{name\} - Retorne "Hello, {name}!"
- Pruebe con
/greet/John
Ejercicio 3: Crear un Servicio
Objetivo: Separar la lógica de negocio
Instrucciones:
- Cree una clase
CalculatorServicecon@Service - Agregue un método
add(int a, int b) - Cree un
CalculatorController - Inyecte el servicio por constructor
- Cree un endpoint
/add?a=5&b=3
Ejercicio 4: Arquitectura Completa
Objetivo: Crear una arquitectura de 3 capas
Instrucciones:
- Cree una clase
Product(modelo) - Cree
ProductServicecon una lista en memoria - Cree
ProductController - Implemente:
- GET /products (todos)
- GET /products/{id}
- POST /products
Preguntas de Repaso
-
¿Qué anotaciones componen @SpringBootApplication?
-
¿Cuál es la diferencia entre @Controller y @RestController?
-
¿Cuándo usar @Service vs @Component?
-
¿Cuál es la diferencia entre @PathVariable y @RequestParam?
-
¿Por qué se recomienda la inyección por constructor?
-
¿Qué hace la anotación @RequestBody?
-
¿Qué método HTTP corresponde a @PostMapping?
-
¿Para qué sirve @Repository?
Resumen del Capítulo
Puntos Clave
@SpringBootApplicationcombina configuración, auto-configuración y escaneo@RestController=@Controller+@ResponseBody@Servicepara la lógica de negocio,@Repositorypara el acceso a datos- Anotaciones de mapeo:
@GetMapping,@PostMapping, etc. @PathVariablepara la URL,@RequestParampara los query strings- Se recomienda la inyección por constructor
Buenas Prácticas
- Utilizar anotaciones especializadas (@Service, @Repository) en lugar de @Component
- Inyección por constructor con final
- Un controlador por recurso REST
- Separar claramente las responsabilidades (Controller → Service → Repository)
- Nombrar los endpoints de manera coherente (convenciones REST)
Próximos Pasos
¡Felicidades! Ha completado el Módulo 2.
En el Módulo 3 (próximamente), profundizará en:
- La Inversión de Control en detalle
- El ciclo de vida de los beans
- Los ámbitos
- La configuración avanzada
Poner en Práctica
Antes de pasar al siguiente módulo, asegúrese de:
- Completar el TP2.1: Aplicación Greeting
- Repetir los ejercicios de cada capítulo
- Crear su propio mini-proyecto para consolidar los conocimientos