Saltar al contenido principal

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 MVC
  • spring-boot-starter-data-jpa → Configura Hibernate, DataSource
  • PostgreSQL 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
RetornoNombre de vista (String)Datos (JSON/XML)
UsoAplicaciones web tradicionalesREST APIs
SerializaciónNoAutomática (Jackson)
@ResponseBodyRequerido en cada métodoImplí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 HTTPAnotaciónAcciónIdempotente
GET@GetMappingLeer✅
POST@PostMappingCrear❌
PUT@PutMappingActualizar (completo)✅
PATCH@PatchMappingActualizar (parcial)❌
DELETE@DeleteMappingEliminar✅

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ónFuenteEjemplo URLUso
@PathVariableRuta URL/users/123Identificadores de recursos
@RequestParamQuery string/users?name=JohnFiltros, paginación
@RequestBodyCuerpo HTTPPOST/PUT bodyEnvío de datos
@RequestHeaderHeaders HTTPAuthorization: Bearer tokenAutenticació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:

  1. Cree una clase HelloController
  2. Anote con @RestController
  3. Cree un método sayHello() que retorne "Hello, Spring Boot!"
  4. Anote con @GetMapping("/hello")
  5. Pruebe con el navegador

Ejercicio 2: Utilizar @PathVariable​

Objetivo: Extraer parámetros de la URL

Instrucciones:

  1. Cree un endpoint /greet/\{name\}
  2. Retorne "Hello, {name}!"
  3. Pruebe con /greet/John

Ejercicio 3: Crear un Servicio​

Objetivo: Separar la lógica de negocio

Instrucciones:

  1. Cree una clase CalculatorService con @Service
  2. Agregue un método add(int a, int b)
  3. Cree un CalculatorController
  4. Inyecte el servicio por constructor
  5. Cree un endpoint /add?a=5&b=3

Ejercicio 4: Arquitectura Completa​

Objetivo: Crear una arquitectura de 3 capas

Instrucciones:

  1. Cree una clase Product (modelo)
  2. Cree ProductService con una lista en memoria
  3. Cree ProductController
  4. Implemente:
    • GET /products (todos)
    • GET /products/{id}
    • POST /products

Preguntas de Repaso​

  1. ¿Qué anotaciones componen @SpringBootApplication?

  2. ¿Cuál es la diferencia entre @Controller y @RestController?

  3. ¿Cuándo usar @Service vs @Component?

  4. ¿Cuál es la diferencia entre @PathVariable y @RequestParam?

  5. ¿Por qué se recomienda la inyección por constructor?

  6. ¿Qué hace la anotación @RequestBody?

  7. ¿Qué método HTTP corresponde a @PostMapping?

  8. ¿Para qué sirve @Repository?


Resumen del Capítulo​

Puntos Clave​

  • @SpringBootApplication combina configuración, auto-configuración y escaneo
  • @RestController = @Controller + @ResponseBody
  • @Service para la lógica de negocio, @Repository para el acceso a datos
  • Anotaciones de mapeo: @GetMapping, @PostMapping, etc.
  • @PathVariable para la URL, @RequestParam para 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

Volver al Módulo 2